From d2a9fa8632bf2ef926dcb4bfc1dd6baf80218d35 Mon Sep 17 00:00:00 2001 From: azerr Date: Fri, 8 Dec 2017 15:39:05 +0100 Subject: [PATCH 001/746] extend nginx redirect regex to https --- scripts/jobs/cron_tasks.inc.http.30.nginx.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/jobs/cron_tasks.inc.http.30.nginx.php b/scripts/jobs/cron_tasks.inc.http.30.nginx.php index bc7f23fc..a4fbb630 100644 --- a/scripts/jobs/cron_tasks.inc.http.30.nginx.php +++ b/scripts/jobs/cron_tasks.inc.http.30.nginx.php @@ -224,7 +224,7 @@ class nginx extends HttpConfigBase } else { $_sslport = $this->checkAlternativeSslPort(); $mypath = 'https://' . Settings::Get('system.hostname') . $_sslport . '/'; - $this->nginx_data[$vhost_filename] .= "\t" . 'if ($request_uri !~ ^/.well-known/acme-challenge/\w+$) {' . "\n"; + $this->nginx_data[$vhost_filename] .= "\t" . 'if ($request_uri !~ ^/.well-known/acme-challenge/[\w-]+$) {' . "\n"; $this->nginx_data[$vhost_filename] .= "\t\t" . 'return 301 ' . $mypath . '$request_uri;' . "\n"; $this->nginx_data[$vhost_filename] .= "\t" . '}' . "\n"; } @@ -473,7 +473,7 @@ class nginx extends HttpConfigBase // Get domain's redirect code $code = getDomainRedirectCode($domain['id'], '301'); - $vhost_content .= "\t" . 'if ($request_uri !~ ^/.well-known/acme-challenge/\w+$) {' . "\n"; + $vhost_content .= "\t" . 'if ($request_uri !~ ^/.well-known/acme-challenge/[\w-]+$) {' . "\n"; $vhost_content .= "\t\t" . 'return ' . $code .' ' . $uri . '$request_uri;' . "\n"; $vhost_content .= "\t" . '}' . "\n"; } else { From 297f3f638c0ad14279f67296caf00f8cfa2b185e Mon Sep 17 00:00:00 2001 From: azerr Date: Fri, 8 Dec 2017 17:47:09 +0100 Subject: [PATCH 002/746] change sign direction --- scripts/jobs/cron_tasks.inc.http.30.nginx.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/jobs/cron_tasks.inc.http.30.nginx.php b/scripts/jobs/cron_tasks.inc.http.30.nginx.php index a4fbb630..4ff9dbd0 100644 --- a/scripts/jobs/cron_tasks.inc.http.30.nginx.php +++ b/scripts/jobs/cron_tasks.inc.http.30.nginx.php @@ -224,7 +224,7 @@ class nginx extends HttpConfigBase } else { $_sslport = $this->checkAlternativeSslPort(); $mypath = 'https://' . Settings::Get('system.hostname') . $_sslport . '/'; - $this->nginx_data[$vhost_filename] .= "\t" . 'if ($request_uri !~ ^/.well-known/acme-challenge/[\w-]+$) {' . "\n"; + $this->nginx_data[$vhost_filename] .= "\t" . 'if ($request_uri !~ ^/.well-known/acme-challenge/[-\w]+$) {' . "\n"; $this->nginx_data[$vhost_filename] .= "\t\t" . 'return 301 ' . $mypath . '$request_uri;' . "\n"; $this->nginx_data[$vhost_filename] .= "\t" . '}' . "\n"; } @@ -473,7 +473,7 @@ class nginx extends HttpConfigBase // Get domain's redirect code $code = getDomainRedirectCode($domain['id'], '301'); - $vhost_content .= "\t" . 'if ($request_uri !~ ^/.well-known/acme-challenge/[\w-]+$) {' . "\n"; + $vhost_content .= "\t" . 'if ($request_uri !~ ^/.well-known/acme-challenge/[-\w]+$) {' . "\n"; $vhost_content .= "\t\t" . 'return ' . $code .' ' . $uri . '$request_uri;' . "\n"; $vhost_content .= "\t" . '}' . "\n"; } else { From 6f91bece17d5568c72c3e0001a22d6528bd8caeb Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 15 Feb 2018 07:44:54 +0100 Subject: [PATCH 003/746] fix postfix config for postfix/courier on precise and trusty, fixes #516 Signed-off-by: Michael Kaufmann (d00p) --- lib/configfiles/precise.xml | 2 +- lib/configfiles/trusty.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/configfiles/precise.xml b/lib/configfiles/precise.xml index 9983959d..806d86e6 100644 --- a/lib/configfiles/precise.xml +++ b/lib/configfiles/precise.xml @@ -726,7 +726,7 @@ smtpd_sasl_local_domain = $myhostname broken_sasl_auth_clients = yes # Virtual delivery settings -virtual_mailbox_base = +virtual_mailbox_base = / virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual_mailbox_maps.cf virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual_mailbox_domains.cf virtual_alias_maps = mysql:/etc/postfix/mysql-virtual_alias_maps.cf diff --git a/lib/configfiles/trusty.xml b/lib/configfiles/trusty.xml index 2df05eeb..0dc4c61f 100644 --- a/lib/configfiles/trusty.xml +++ b/lib/configfiles/trusty.xml @@ -761,7 +761,7 @@ smtpd_sasl_local_domain = $myhostname broken_sasl_auth_clients = yes # Virtual delivery settings -virtual_mailbox_base = +virtual_mailbox_base = / virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual_mailbox_maps.cf virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual_mailbox_domains.cf virtual_alias_maps = mysql:/etc/postfix/mysql-virtual_alias_maps.cf From dd371c72a2c9a3241f04cfc7d364bb9223137d4e Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 15 Feb 2018 07:47:35 +0100 Subject: [PATCH 004/746] start api implementation Signed-off-by: Michael Kaufmann (d00p) --- api.php | 69 ++++++++++ install/froxlor.sql | 15 ++ lib/classes/api/abstract.ApiCommand.php | 129 ++++++++++++++++++ lib/classes/api/api_includes.inc.php | 6 + lib/classes/api/class.FroxlorRPC.php | 100 ++++++++++++++ lib/classes/database/class.Database.php | 14 +- .../output/function.standard_error.php | 5 +- lib/functions/validate/function.validate.php | 4 +- .../validate/function.validate_ip.php | 10 +- 9 files changed, 339 insertions(+), 13 deletions(-) create mode 100644 api.php create mode 100644 lib/classes/api/abstract.ApiCommand.php create mode 100644 lib/classes/api/api_includes.inc.php create mode 100644 lib/classes/api/class.FroxlorRPC.php diff --git a/api.php b/api.php new file mode 100644 index 00000000..42bfaae4 --- /dev/null +++ b/api.php @@ -0,0 +1,69 @@ +$method(); +} catch (Exception $e) { + json_response($e->getCode(), $e->getMessage()); +} + +exit(); + +/** + * output json result + * + * @param int $status + * @param string $status_message + * @param mixed $data + * + * @return void + */ +function json_response($status, $status_message, $data = null) +{ + header("HTTP/1.1 " . $status); + + $response['status'] = $status; + $response['status_message'] = $status_message; + $response['data'] = $data; + + $json_response = json_encode($response, JSON_PRETTY_PRINT); + echo $json_response; + exit(); +} diff --git a/install/froxlor.sql b/install/froxlor.sql index 870e30d4..5a262c55 100644 --- a/install/froxlor.sql +++ b/install/froxlor.sql @@ -1042,3 +1042,18 @@ CREATE TABLE `panel_plans` ( KEY adminid (adminid) ) ENGINE=MyISAM CHARSET=utf8 COLLATE=utf8_general_ci; + +DROP TABLE IF EXISTS `api_keys`; +CREATE TABLE `api_keys` ( + `id` int(11) NOT NULL auto_increment, + `adminid` int(11) NOT NULL default '0', + `customerid` int(11) NOT NULL default '0', + `apikey` varchar(500) NOT NULL default '', + `secret` varchar(500) NOT NULL default '', + `allowed_from` text NOT NULL, + `valid_until` int(15) NOT NULL default '0', + PRIMARY KEY (id), + KEY adminid (adminid), + KEY customerid (customerid) +) ENGINE=MyISAM CHARSET=utf8 COLLATE=utf8_general_ci; + diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php new file mode 100644 index 00000000..43a5b688 --- /dev/null +++ b/lib/classes/api/abstract.ApiCommand.php @@ -0,0 +1,129 @@ +cmd_params = $params; + if (! empty($header)) { + $this->readUserData($header); + } elseif (! empty($userinfo)) { + $this->user_data = $userinfo; + $this->is_admin = ($userinfo['adminsession'] == 1 && $userinfo['adminid'] > 0) ? true : false; + } else { + throw new Exception("Invalid user data", 500); + } + $this->logger = FroxlorLogger::getInstanceOf($this->user_data); + } + + public static function getLocal($userinfo = null, $params = null) + { + return new static(null, $params, $userinfo); + } + + /** + * admin flag + * + * @return boolean + */ + protected function isAdmin() + { + return $this->is_admin; + } + + /** + * return field from user-table + * + * @param string $detail + * + * @return string + */ + protected function getUserDetail($detail = null) + { + return (isset($this->user_data[$detail]) ? $this->user_data[$detail] : null); + } + + /** + * receive field from parameter-list + * + * @param string $param + * + * @throws Exception + * @return mixed + */ + protected function getParam($param = null) + { + if (isset($this->cmd_params[$param])) { + return $this->cmd_params[$param]; + } + return null; + } + + /** + * return logger instance + * + * @return FroxlorLogger + */ + protected function logger() + { + return $this->logger; + } + + protected function response($status, $status_message, $data = null) + { + header("HTTP/1.1 " . $status); + + $response['status'] = $status; + $response['status_message'] = $status_message; + $response['data'] = $data; + + $json_response = json_encode($response, JSON_PRETTY_PRINT); + return $json_response; + } + + public abstract function list(); + + public abstract function get(); + + public abstract function add(); + + public abstract function update(); + + public abstract function delete(); + + private function readUserData($header = null) + { + $sel_stmt = Database::prepare("SELECT * FROM `api_keys` WHERE `apikey` = :ak AND `secret` = :as"); + $result = Database::pexecute_first($sel_stmt, array( + 'ak' => $header['apikey'], + 'as' => $header['secret'] + ), true, true); + if ($result) { + // admin or customer? + if ($result['customerid'] == 0) { + $this->is_admin = true; + $table = 'panel_admins'; + $key = "adminid"; + } else { + $this->is_admin = false; + $table = 'panel_customers'; + $key = "customerid"; + } + $sel_stmt = Database::prepare("SELECT * FROM `" . $table . "` WHERE `" . $key . "` = :id"); + $this->user_data = Database::pexecute_first($sel_stmt, array( + 'id' => ($this->is_admin ? $result['adminid'] : $result['customerid']) + ), true, true); + return true; + } + throw new Exception("Invalid API credentials", 400); + } +} diff --git a/lib/classes/api/api_includes.inc.php b/lib/classes/api/api_includes.inc.php new file mode 100644 index 00000000..09b3cd84 --- /dev/null +++ b/lib/classes/api/api_includes.inc.php @@ -0,0 +1,6 @@ + $key, + 'as' => $secret + ), true, true); + if ($result) { + if ($result['apikey'] == $key && $result['secret'] == $secret && ($result['valid_until'] == -1 || $result['valid_until'] >= time())) { + if (!empty($result['allowed_from'])) { + $ip_list = explode(",", $result['allowed_from']); + $access_ip = $_SERVER['REMOTE_ADDR']; + // @fixme finish me + } + return true; + } + } + throw new Exception("Invalid authorization credentials", 400); + } + + /** + * validates the given command + * + * @param array $body + * + * @throws Exception + * @return boolean + */ + private static function validateBody($request) + { + // check body + if (! isset($request['body']) || empty($request['body'])) { + throw new Exception("Invalid request body", 400); + } + + // check command exists + if (! isset($request['body']['command']) || empty($request['body']['command'])) { + throw new Exception("No command given", 400); + } + + $command = explode(".", $request['body']['command']); + + if (count($command) != 2) { + throw new Exception("Invalid command", 400); + } + // simply check for file-existance, as we do not want to use our autoloader because this way + // it will recognize non-api classes+methods as valid commands + $apiclass = FROXLOR_INSTALL_DIR . '/lib/classes/api/commands/class.' . $command[0] . '.php'; + if (! file_exists($apiclass) || ! @method_exists($command[0], $command[1])) { + // there will be an exception from the autoloader for class_exists hence the try-catch-block + throw new Exception("Unknown command", 400); + } + return array( + 'command' => array( + 'class' => $command[0], + 'method' => $command[1] + ), + 'params' => isset($request['body']['params']) ? $request['body']['params'] : null + ); + } +} diff --git a/lib/classes/database/class.Database.php b/lib/classes/database/class.Database.php index 3174c2b3..7f6c7d46 100644 --- a/lib/classes/database/class.Database.php +++ b/lib/classes/database/class.Database.php @@ -67,11 +67,11 @@ class Database { * @param array $params (optional) * @param bool $showerror suppress errordisplay (default true) */ - public static function pexecute(&$stmt, $params = null, $showerror = true) { + public static function pexecute(&$stmt, $params = null, $showerror = true, $json_response = false) { try { $stmt->execute($params); } catch (PDOException $e) { - self::_showerror($e, $showerror); + self::_showerror($e, $showerror, $json_response); } } @@ -86,8 +86,8 @@ class Database { * * @return array */ - public static function pexecute_first(&$stmt, $params = null, $showerror = true) { - self::pexecute($stmt, $params, $showerror); + public static function pexecute_first(&$stmt, $params = null, $showerror = true, $json_response = false) { + self::pexecute($stmt, $params, $showerror, $json_response); return $stmt->fetch(PDO::FETCH_ASSOC); } @@ -309,7 +309,7 @@ class Database { * @param PDOException $error * @param bool $showerror if set to false, the error will be logged but we go on */ - private static function _showerror($error, $showerror = true) { + private static function _showerror($error, $showerror = true, $json_response = false) { global $userinfo, $theme, $linker; // include userdata.inc.php @@ -367,6 +367,10 @@ class Database { @fwrite($errlog, "|TRACE\n".$error_trace."\n"); @fclose($errlog); + if ($showerror && $json_response) { + throw new Exception($error_message, 500); + } + if ($showerror) { if (empty($sql['debug'])) { $error_trace = ''; diff --git a/lib/functions/output/function.standard_error.php b/lib/functions/output/function.standard_error.php index 03fb272f..9e8bd7fb 100644 --- a/lib/functions/output/function.standard_error.php +++ b/lib/functions/output/function.standard_error.php @@ -25,7 +25,7 @@ * @author Florian Lippert * @author Ron Brand */ -function standard_error($errors = '', $replacer = '') { +function standard_error($errors = '', $replacer = '', $throw_exception = false) { global $userinfo, $s, $header, $footer, $lng, $theme; @@ -60,6 +60,9 @@ function standard_error($errors = '', $replacer = '') { } } + if ($throw_exception) { + throw new Exception($error); + } eval("echo \"" . getTemplate('misc/error', '1') . "\";"); exit; } diff --git a/lib/functions/validate/function.validate.php b/lib/functions/validate/function.validate.php index eddb8c3c..355f48e1 100644 --- a/lib/functions/validate/function.validate.php +++ b/lib/functions/validate/function.validate.php @@ -31,7 +31,7 @@ * */ -function validate($str, $fieldname, $pattern = '', $lng = '', $emptydefault = array()) { +function validate($str, $fieldname, $pattern = '', $lng = '', $emptydefault = array(), $throw_exception = false) { global $log, $theme; @@ -74,6 +74,6 @@ function validate($str, $fieldname, $pattern = '', $lng = '', $emptydefault = ar $lng = 'stringformaterror'; } - standard_error($lng, $fieldname); + standard_error($lng, $fieldname, $throw_exception); exit; } diff --git a/lib/functions/validate/function.validate_ip.php b/lib/functions/validate/function.validate_ip.php index f3caa492..2a30150b 100644 --- a/lib/functions/validate/function.validate_ip.php +++ b/lib/functions/validate/function.validate_ip.php @@ -23,7 +23,7 @@ * @return mixed ip address on success, standard_error on failure * @deprecated use validate_ip2 */ -function validate_ip($ip, $return_bool = false, $lng = 'invalidip') { +function validate_ip($ip, $return_bool = false, $lng = 'invalidip', $throw_exception = false) { if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false @@ -32,7 +32,7 @@ function validate_ip($ip, $return_bool = false, $lng = 'invalidip') { if ($return_bool) { return false; } else { - standard_error($lng, $ip); + standard_error($lng, $ip, $throw_exception); exit(); } } else { @@ -53,7 +53,7 @@ function validate_ip($ip, $return_bool = false, $lng = 'invalidip') { * * @return string|bool ip address on success, false on failure */ -function validate_ip2($ip, $return_bool = false, $lng = 'invalidip', $allow_localhost = false, $allow_priv = false, $allow_cidr = false) { +function validate_ip2($ip, $return_bool = false, $lng = 'invalidip', $allow_localhost = false, $allow_priv = false, $allow_cidr = false, $throw_exception = false) { $cidr = ""; if ($allow_cidr) { @@ -69,7 +69,7 @@ function validate_ip2($ip, $return_bool = false, $lng = 'invalidip', $allow_loca if ($return_bool) { return false; } else { - standard_error($lng, $ip); + standard_error($lng, $ip, $throw_exception); exit(); } } @@ -91,7 +91,7 @@ function validate_ip2($ip, $return_bool = false, $lng = 'invalidip', $allow_loca if ($return_bool) { return false; } else { - standard_error($lng, $ip); + standard_error($lng, $ip, $throw_exception); exit(); } } From 4663f8c6ec1d30b4155bb742842ecfb234a7bb54 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 15 Feb 2018 07:48:03 +0100 Subject: [PATCH 005/746] converted IpsAndPorts to API Signed-off-by: Michael Kaufmann (d00p) --- admin_ipsandports.php | 435 ++++-------------- .../api/commands/class.IpsAndPorts.php | 393 ++++++++++++++++ 2 files changed, 470 insertions(+), 358 deletions(-) create mode 100644 lib/classes/api/commands/class.IpsAndPorts.php diff --git a/admin_ipsandports.php b/admin_ipsandports.php index eddcf429..c9165618 100644 --- a/admin_ipsandports.php +++ b/admin_ipsandports.php @@ -16,27 +16,24 @@ * @package Panel * */ - define('AREA', 'admin'); require './lib/init.php'; if (isset($_POST['id'])) { $id = intval($_POST['id']); -} elseif(isset($_GET['id'])) { +} elseif (isset($_GET['id'])) { $id = intval($_GET['id']); } -if ($page == 'ipsandports' - || $page == 'overview' -) { +if ($page == 'ipsandports' || $page == 'overview') { // Do not display attributes that are not used by the current webserver $websrv = Settings::Get('system.webserver'); $is_nginx = ($websrv == 'nginx'); $is_apache = ($websrv == 'apache2'); $is_apache24 = $is_apache && (Settings::Get('system.apache24') === '1'); - + if ($action == '') { - + $log->logAction(ADM_ACTION, LOG_NOTICE, "viewed admin_ipsandports"); $fields = array( 'ip' => $lng['admin']['ipsandports']['ip'], @@ -53,384 +50,106 @@ if ($page == 'ipsandports' $pagingcode = $paging->getHtmlPagingCode($filename . '?page=' . $page . '&s=' . $s); $i = 0; $count = 0; - + while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { - + if ($paging->checkDisplay($i)) { $row = htmlentities_array($row); if (filter_var($row['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { $row['ip'] = '[' . $row['ip'] . ']'; } eval("\$ipsandports.=\"" . getTemplate("ipsandports/ipsandports_ipandport") . "\";"); - $count++; + $count ++; } - $i++; + $i ++; } - eval("echo \"" . getTemplate("ipsandports/ipsandports") . "\";"); - - } elseif($action == 'delete' - && $id != 0 - ) { - $result_stmt = Database::prepare("SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :id"); - $result = Database::pexecute_first($result_stmt, array('id' => $id)); - - if (isset($result['id']) - && $result['id'] == $id - ) { - $result_checkdomain_stmt = Database::prepare(" - SELECT `id_domain` as `id` FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_ipandports` = :id" - ); - $result_checkdomain = Database::pexecute_first($result_checkdomain_stmt, array('id' => $id)); - - if ($result_checkdomain['id'] == '') { - if (!in_array($result['id'], explode(',', Settings::Get('system.defaultip')))) { - - $result_sameipotherport_stmt = Database::prepare(" - SELECT `id` FROM `" . TABLE_PANEL_IPSANDPORTS . "` - WHERE `ip` = :ip AND `id` <> :id" - ); - $result_sameipotherport = Database::pexecute_first($result_sameipotherport_stmt, array('id' => $id, 'ip' => $result['ip'])); - - if (($result['ip'] != Settings::Get('system.ipaddress')) - || ($result['ip'] == Settings::Get('system.ipaddress') - && $result_sameipotherport['id'] != '') - ) { - $result_stmt = Database::prepare(" - SELECT `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` - WHERE `id` = :id" - ); - $result = Database::pexecute_first($result_stmt, array('id' => $id)); - - if ($result['ip'] != '') { - - if (isset($_POST['send']) - && $_POST['send'] == 'send' - ) { - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_PANEL_IPSANDPORTS . "` - WHERE `id` = :id" - ); - Database::pexecute($del_stmt, array('id' => $id)); - - // also, remove connections to domains (multi-stack) - $del_stmt = Database::prepare(" - DELETE FROM `".TABLE_DOMAINTOIP."` WHERE `id_ipandports` = :id" - ); - Database::pexecute($del_stmt, array('id' => $id)); - - $log->logAction(ADM_ACTION, LOG_WARNING, "deleted IP/port '" . $result['ip'] . ":" . $result['port'] . "'"); - inserttask('1'); - // Using nameserver, insert a task which rebuilds the server config - inserttask('4'); - - redirectTo($filename, array('page' => $page, 's' => $s)); - - } else { - ask_yesno('admin_ip_reallydelete', $filename, array('id' => $id, 'page' => $page, 'action' => $action), $result['ip'] . ':' . $result['port']); - } - } - } else { - standard_error('cantdeletesystemip'); - } - } else { - standard_error('cantdeletedefaultip'); + } elseif ($action == 'delete' && $id != 0) { + try { + $json_result = IpsAndPorts::getLocal($userinfo, array( + 'id' => $id + ))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; + + if (isset($result['id']) && $result['id'] == $id) { + if (isset($_POST['send']) && $_POST['send'] == 'send') { + + try { + IpsAndPorts::getLocal($userinfo, array( + 'id' => $id + ))->delete(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } + + redirectTo($filename, array( + 'page' => $page, + 's' => $s + )); } else { - standard_error('ipstillhasdomains'); + ask_yesno('admin_ip_reallydelete', $filename, array( + 'id' => $id, + 'page' => $page, + 'action' => $action + ), $result['ip'] . ':' . $result['port']); } } - - } elseif($action == 'add') { - - if (isset($_POST['send']) - && $_POST['send'] == 'send' - ) { - - $ip = validate_ip($_POST['ip']); - $port = validate($_POST['port'], 'port', '/^(([1-9])|([1-9][0-9])|([1-9][0-9][0-9])|([1-9][0-9][0-9][0-9])|([1-5][0-9][0-9][0-9][0-9])|(6[0-4][0-9][0-9][0-9])|(65[0-4][0-9][0-9])|(655[0-2][0-9])|(6553[0-5]))$/Di', array('stringisempty', 'myport')); - $listen_statement = isset($_POST['listen_statement']) ? 1 : 0; - $namevirtualhost_statement = isset($_POST['namevirtualhost_statement']) ? 1 : 0; - $vhostcontainer = isset($_POST['vhostcontainer']) ? 1 : 0; - $specialsettings = validate(str_replace("\r\n", "\n", $_POST['specialsettings']), 'specialsettings', '/^[^\0]*$/'); - $vhostcontainer_servername_statement = isset($_POST['vhostcontainer_servername_statement']) ? 1 : 0; - $default_vhostconf_domain = validate(str_replace("\r\n", "\n", $_POST['default_vhostconf_domain']), 'default_vhostconf_domain', '/^[^\0]*$/'); - $docroot = validate($_POST['docroot'], 'docroot'); - - if ((int)Settings::Get('system.use_ssl') == 1) { - $ssl = isset($_POST['ssl']) ? intval($_POST['ssl']) : 0; - $ssl_cert_file = validate($_POST['ssl_cert_file'], 'ssl_cert_file'); - $ssl_key_file = validate($_POST['ssl_key_file'], 'ssl_key_file'); - $ssl_ca_file = validate($_POST['ssl_ca_file'], 'ssl_ca_file'); - $ssl_cert_chainfile = validate($_POST['ssl_cert_chainfile'], 'ssl_cert_chainfile'); - } else { - $ssl = 0; - $ssl_cert_file = ''; - $ssl_key_file = ''; - $ssl_ca_file = ''; - $ssl_cert_chainfile = ''; + } elseif ($action == 'add') { + if (isset($_POST['send']) && $_POST['send'] == 'send') { + try { + IpsAndPorts::getLocal($userinfo, $_POST)->add(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - - if ($listen_statement != '1') { - $listen_statement = '0'; - } - - if ($namevirtualhost_statement != '1') { - $namevirtualhost_statement = '0'; - } - - if ($vhostcontainer != '1') { - $vhostcontainer = '0'; - } - - if ($vhostcontainer_servername_statement != '1') { - $vhostcontainer_servername_statement = '0'; - } - - if ($ssl != '1') { - $ssl = '0'; - } - - if ($ssl_cert_file != '') { - $ssl_cert_file = makeCorrectFile($ssl_cert_file); - } - - if ($ssl_key_file != '') { - $ssl_key_file = makeCorrectFile($ssl_key_file); - } - - if ($ssl_ca_file != '') { - $ssl_ca_file = makeCorrectFile($ssl_ca_file); - } - - if ($ssl_cert_chainfile != '') { - $ssl_cert_chainfile = makeCorrectFile($ssl_cert_chainfile); - } - - if (strlen(trim($docroot)) > 0) { - $docroot = makeCorrectDir($docroot); - } else { - $docroot = ''; - } - - $result_checkfordouble_stmt = Database::prepare(" - SELECT `id` FROM `" . TABLE_PANEL_IPSANDPORTS . "` - WHERE `ip` = :ip AND `port` = :port" - ); - $result_checkfordouble = Database::pexecute_first($result_checkfordouble_stmt, array('ip' => $ip, 'port' => $port)); - - if ($result_checkfordouble['id'] != '') { - standard_error('myipnotdouble'); - } else { - $ins_stmt = Database::prepare(" - INSERT INTO `" . TABLE_PANEL_IPSANDPORTS . "` - SET - `ip` = :ip, `port` = :port, `listen_statement` = :ls, - `namevirtualhost_statement` = :nvhs, `vhostcontainer` = :vhc, - `vhostcontainer_servername_statement` = :vhcss, - `specialsettings` = :ss, `ssl` = :ssl, - `ssl_cert_file` = :ssl_cert, `ssl_key_file` = :ssl_key, - `ssl_ca_file` = :ssl_ca, `ssl_cert_chainfile` = :ssl_chain, - `default_vhostconf_domain` = :dvhd, `docroot` = :docroot; - "); - $ins_data = array( - 'ip' => $ip, - 'port' => $port, - 'ls' => $listen_statement, - 'nvhs' => $namevirtualhost_statement, - 'vhc' => $vhostcontainer, - 'vhcss' => $vhostcontainer_servername_statement, - 'ss' => $specialsettings, - 'ssl' => $ssl, - 'ssl_cert' => $ssl_cert_file, - 'ssl_key' => $ssl_key_file, - 'ssl_ca' => $ssl_ca_file, - 'ssl_chain' => $ssl_cert_chainfile, - 'dvhd' => $default_vhostconf_domain, - 'docroot' => $docroot - ); - Database::pexecute($ins_stmt, $ins_data); - - if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { - $ip = '[' . $ip . ']'; - } - - $log->logAction(ADM_ACTION, LOG_WARNING, "added IP/port '" . $ip . ":" . $port . "'"); - inserttask('1'); - // Using nameserver, insert a task which rebuilds the server config - inserttask('4'); - redirectTo($filename, Array('page' => $page, 's' => $s)); - } - + redirectTo($filename, array( + 'page' => $page, + 's' => $s + )); } else { - - $ipsandports_add_data = include_once dirname(__FILE__).'/lib/formfields/admin/ipsandports/formfield.ipsandports_add.php'; + + $ipsandports_add_data = include_once dirname(__FILE__) . '/lib/formfields/admin/ipsandports/formfield.ipsandports_add.php'; $ipsandports_add_form = htmlform::genHTMLForm($ipsandports_add_data); - + $title = $ipsandports_add_data['ipsandports_add']['title']; $image = $ipsandports_add_data['ipsandports_add']['image']; - + eval("echo \"" . getTemplate("ipsandports/ipsandports_add") . "\";"); } - - } elseif($action == 'edit' - && $id != 0 - ) { - $result_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :id" - ); - $result = Database::pexecute_first($result_stmt, array('id' => $id)); - + } elseif ($action == 'edit' && $id != 0) { + try { + $json_result = IpsAndPorts::getLocal($userinfo, array( + 'id' => $id + ))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; + if ($result['ip'] != '') { - - if (isset($_POST['send']) - && $_POST['send'] == 'send' - ) { - - $ip = validate_ip($_POST['ip']); - $port = validate($_POST['port'], 'port', '/^(([1-9])|([1-9][0-9])|([1-9][0-9][0-9])|([1-9][0-9][0-9][0-9])|([1-5][0-9][0-9][0-9][0-9])|(6[0-4][0-9][0-9][0-9])|(65[0-4][0-9][0-9])|(655[0-2][0-9])|(6553[0-5]))$/Di', array('stringisempty', 'myport')); - $listen_statement = isset($_POST['listen_statement']) ? 1 : 0; - $namevirtualhost_statement = isset($_POST['namevirtualhost_statement']) ? 1 : 0; - $vhostcontainer = isset($_POST['vhostcontainer']) ? 1 : 0; - $specialsettings = validate(str_replace("\r\n", "\n", $_POST['specialsettings']), 'specialsettings', '/^[^\0]*$/'); - $vhostcontainer_servername_statement = isset($_POST['vhostcontainer_servername_statement']) ? 1 : 0; - $default_vhostconf_domain = validate(str_replace("\r\n", "\n", $_POST['default_vhostconf_domain']), 'default_vhostconf_domain', '/^[^\0]*$/'); - $docroot = validate($_POST['docroot'], 'docroot'); - - $result_checkfordouble_stmt = Database::prepare(" - SELECT `id` FROM `" . TABLE_PANEL_IPSANDPORTS . "` - WHERE `ip` = :ip AND `port` = :port" - ); - $result_checkfordouble = Database::pexecute_first($result_checkfordouble_stmt, array('ip' => $ip, 'port' => $port)); - - $result_sameipotherport_stmt = Database::prepare(" - SELECT `id` FROM `" . TABLE_PANEL_IPSANDPORTS . "` - WHERE `ip` = :ip AND `id` <> :id" - ); - $result_sameipotherport = Database::pexecute_first($result_sameipotherport_stmt, array('ip' => $ip, 'id' => $id)); - - if ((int)Settings::Get('system.use_ssl') == 1 - && isset($_POST['ssl']) - && $_POST['ssl'] != 0 - ) { - $ssl = 1; - $ssl_cert_file = validate($_POST['ssl_cert_file'], 'ssl_cert_file'); - $ssl_key_file = validate($_POST['ssl_key_file'], 'ssl_key_file'); - $ssl_ca_file = validate($_POST['ssl_ca_file'], 'ssl_ca_file'); - $ssl_cert_chainfile = validate($_POST['ssl_cert_chainfile'], 'ssl_cert_chainfile'); - } else { - $ssl = 0; - $ssl_cert_file = ''; - $ssl_key_file = ''; - $ssl_ca_file = ''; - $ssl_cert_chainfile = ''; - } - - if ($listen_statement != '1') { - $listen_statement = '0'; - } - - if ($namevirtualhost_statement != '1') { - $namevirtualhost_statement = '0'; - } - - if ($vhostcontainer != '1') { - $vhostcontainer = '0'; - } - - if ($vhostcontainer_servername_statement != '1') { - $vhostcontainer_servername_statement = '0'; - } - - if ($ssl != '1') { - $ssl = '0'; - } - - if ($ssl_cert_file != '') { - $ssl_cert_file = makeCorrectFile($ssl_cert_file); - } - - if ($ssl_key_file != '') { - $ssl_key_file = makeCorrectFile($ssl_key_file); - } - - if ($ssl_ca_file != '') { - $ssl_ca_file = makeCorrectFile($ssl_ca_file); - } - - if ($ssl_cert_chainfile != '') { - $ssl_cert_chainfile = makeCorrectFile($ssl_cert_chainfile); - } - - if (strlen(trim($docroot)) > 0) { - $docroot = makeCorrectDir($docroot); - } else { - $docroot = ''; - } - - if ($result['ip'] != $ip - && $result['ip'] == Settings::Get('system.ipaddress') - && $result_sameipotherport['id'] == '' - ) { - standard_error('cantchangesystemip'); - - } elseif($result_checkfordouble['id'] != '' - && $result_checkfordouble['id'] != $id - ) { - standard_error('myipnotdouble'); - - } else { - - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_IPSANDPORTS . "` - SET - `ip` = :ip, `port` = :port, `listen_statement` = :ls, - `namevirtualhost_statement` = :nvhs, `vhostcontainer` = :vhc, - `vhostcontainer_servername_statement` = :vhcss, - `specialsettings` = :ss, `ssl` = :ssl, - `ssl_cert_file` = :ssl_cert, `ssl_key_file` = :ssl_key, - `ssl_ca_file` = :ssl_ca, `ssl_cert_chainfile` = :ssl_chain, - `default_vhostconf_domain` = :dvhd, `docroot` = :docroot - WHERE `id` = :id; - "); - $upd_data = array( - 'ip' => $ip, - 'port' => $port, - 'ls' => $listen_statement, - 'nvhs' => $namevirtualhost_statement, - 'vhc' => $vhostcontainer, - 'vhcss' => $vhostcontainer_servername_statement, - 'ss' => $specialsettings, - 'ssl' => $ssl, - 'ssl_cert' => $ssl_cert_file, - 'ssl_key' => $ssl_key_file, - 'ssl_ca' => $ssl_ca_file, - 'ssl_chain' => $ssl_cert_chainfile, - 'dvhd' => $default_vhostconf_domain, - 'docroot' => $docroot, - 'id' => $id - ); - Database::pexecute($upd_stmt, $upd_data); - - $log->logAction(ADM_ACTION, LOG_WARNING, "changed IP/port from '" . $result['ip'] . ":" . $result['port'] . "' to '" . $ip . ":" . $port . "'"); - inserttask('1'); - - // Using nameserver, insert a task which rebuilds the server config - inserttask('4'); - - redirectTo($filename, Array('page' => $page, 's' => $s)); - } - + + if (isset($_POST['send']) && $_POST['send'] == 'send') { + try { + IpsAndPorts::getLocal($userinfo, $_POST)->update(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + redirectTo($filename, array( + 'page' => $page, + 's' => $s + )); } else { - + $result = htmlentities_array($result); - - $ipsandports_edit_data = include_once dirname(__FILE__).'/lib/formfields/admin/ipsandports/formfield.ipsandports_edit.php'; + + $ipsandports_edit_data = include_once dirname(__FILE__) . '/lib/formfields/admin/ipsandports/formfield.ipsandports_edit.php'; $ipsandports_edit_form = htmlform::genHTMLForm($ipsandports_edit_data); - + $title = $ipsandports_edit_data['ipsandports_edit']['title']; $image = $ipsandports_edit_data['ipsandports_edit']['image']; - + eval("echo \"" . getTemplate("ipsandports/ipsandports_edit") . "\";"); } } diff --git a/lib/classes/api/commands/class.IpsAndPorts.php b/lib/classes/api/commands/class.IpsAndPorts.php new file mode 100644 index 00000000..502d68a1 --- /dev/null +++ b/lib/classes/api/commands/class.IpsAndPorts.php @@ -0,0 +1,393 @@ +isAdmin() && $this->getUserDetail('change_serversettings')) { + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] list ips and ports"); + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_IPSANDPORTS . "` ORDER BY `ip` ASC, `port` ASC + "); + Database::pexecute($result_stmt); + $result = array(); + while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { + $result[] = $row; + } + return $this->response(200, "successfull", array( + 'count' => count($result), + 'list' => $result + )); + } + throw new Exception("Not allowed to execute given command.", 403); + } + + public function get() + { + if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { + $id = $this->getParam('id'); + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] get ip and port #" . $id); + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :id + "); + $result = Database::pexecute_first($result_stmt, array( + 'id' => $id + ), true, true); + if ($result) { + return $this->response(200, "successfull", $result); + } + throw new Exception("IP/port with id #" . $id . " could not be found"); + } + throw new Exception("Not allowed to execute given command.", 403); + } + + public function add() + { + if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { + $ip = validate_ip2($this->getParam('ip'), false, 'invalidip', false, false, false, true); + $port = validate($this->getParam('port'), 'port', '/^(([1-9])|([1-9][0-9])|([1-9][0-9][0-9])|([1-9][0-9][0-9][0-9])|([1-5][0-9][0-9][0-9][0-9])|(6[0-4][0-9][0-9][0-9])|(65[0-4][0-9][0-9])|(655[0-2][0-9])|(6553[0-5]))$/Di', array( + 'stringisempty', + 'myport' + ), array(), true); + $listen_statement = ! empty($this->getParam('listen_statement')) ? 1 : 0; + $namevirtualhost_statement = ! empty($this->getParam('namevirtualhost_statement')) ? 1 : 0; + $vhostcontainer = ! empty($this->getParam('vhostcontainer')) ? 1 : 0; + $specialsettings = validate(str_replace("\r\n", "\n", $this->getParam('specialsettings')), 'specialsettings', '/^[^\0]*$/', '', array(), true); + $vhostcontainer_servername_statement = ! empty($this->getParam('vhostcontainer_servername_statement')) ? 1 : 0; + $default_vhostconf_domain = validate(str_replace("\r\n", "\n", $this->getParam('default_vhostconf_domain')), 'default_vhostconf_domain', '/^[^\0]*$/', '', array(), true); + $docroot = validate($this->getParam('docroot'), 'docroot', '', '', array(), true); + + if ((int) Settings::Get('system.use_ssl') == 1) { + $ssl = ! empty($this->getParam('ssl')) ? intval($this->getParam('ssl')) : 0; + $ssl_cert_file = validate($this->getParam('ssl_cert_file'), 'ssl_cert_file', '', '', array(), true); + $ssl_key_file = validate($this->getParam('ssl_key_file'), 'ssl_key_file', '', '', array(), true); + $ssl_ca_file = validate($this->getParam('ssl_ca_file'), 'ssl_ca_file', '', '', array(), true); + $ssl_cert_chainfile = validate($this->getParam('ssl_cert_chainfile'), 'ssl_cert_chainfile', '', '', array(), true); + } else { + $ssl = 0; + $ssl_cert_file = ''; + $ssl_key_file = ''; + $ssl_ca_file = ''; + $ssl_cert_chainfile = ''; + } + + if ($listen_statement != '1') { + $listen_statement = '0'; + } + + if ($namevirtualhost_statement != '1') { + $namevirtualhost_statement = '0'; + } + + if ($vhostcontainer != '1') { + $vhostcontainer = '0'; + } + + if ($vhostcontainer_servername_statement != '1') { + $vhostcontainer_servername_statement = '0'; + } + + if ($ssl != '1') { + $ssl = '0'; + } + + if ($ssl_cert_file != '') { + $ssl_cert_file = makeCorrectFile($ssl_cert_file); + } + + if ($ssl_key_file != '') { + $ssl_key_file = makeCorrectFile($ssl_key_file); + } + + if ($ssl_ca_file != '') { + $ssl_ca_file = makeCorrectFile($ssl_ca_file); + } + + if ($ssl_cert_chainfile != '') { + $ssl_cert_chainfile = makeCorrectFile($ssl_cert_chainfile); + } + + if (strlen(trim($docroot)) > 0) { + $docroot = makeCorrectDir($docroot); + } else { + $docroot = ''; + } + + $result_checkfordouble_stmt = Database::prepare(" + SELECT `id` FROM `" . TABLE_PANEL_IPSANDPORTS . "` + WHERE `ip` = :ip AND `port` = :port"); + $result_checkfordouble = Database::pexecute_first($result_checkfordouble_stmt, array( + 'ip' => $ip, + 'port' => $port + )); + + if ($result_checkfordouble['id'] != '') { + standard_error('myipnotdouble', '', true); + } + + $ins_stmt = Database::prepare(" + INSERT INTO `" . TABLE_PANEL_IPSANDPORTS . "` + SET + `ip` = :ip, `port` = :port, `listen_statement` = :ls, + `namevirtualhost_statement` = :nvhs, `vhostcontainer` = :vhc, + `vhostcontainer_servername_statement` = :vhcss, + `specialsettings` = :ss, `ssl` = :ssl, + `ssl_cert_file` = :ssl_cert, `ssl_key_file` = :ssl_key, + `ssl_ca_file` = :ssl_ca, `ssl_cert_chainfile` = :ssl_chain, + `default_vhostconf_domain` = :dvhd, `docroot` = :docroot; + "); + $ins_data = array( + 'ip' => $ip, + 'port' => $port, + 'ls' => $listen_statement, + 'nvhs' => $namevirtualhost_statement, + 'vhc' => $vhostcontainer, + 'vhcss' => $vhostcontainer_servername_statement, + 'ss' => $specialsettings, + 'ssl' => $ssl, + 'ssl_cert' => $ssl_cert_file, + 'ssl_key' => $ssl_key_file, + 'ssl_ca' => $ssl_ca_file, + 'ssl_chain' => $ssl_cert_chainfile, + 'dvhd' => $default_vhostconf_domain, + 'docroot' => $docroot + ); + Database::pexecute($ins_stmt, $ins_data); + $ins_data['id'] = Database::lastInsertId(); + + inserttask('1'); + // Using nameserver, insert a task which rebuilds the server config + inserttask('4'); + + if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + $ip = '[' . $ip . ']'; + } + $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] added IP/port '" . $ip . ":" . $port . "'"); + return $this->response(200, "successfull", $ins_data); + } + throw new Exception("Not allowed to execute given command.", 403); + } + + public function update() + { + if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { + $id = $this->getParam('id'); + + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :id + "); + $result = Database::pexecute_first($result_stmt, array( + 'id' => $id + ), true, true); + + $ip = validate_ip2($this->getParam('ip'), false, 'invalidip', false, false, false, true); + $port = validate($this->getParam('port'), 'port', '/^(([1-9])|([1-9][0-9])|([1-9][0-9][0-9])|([1-9][0-9][0-9][0-9])|([1-5][0-9][0-9][0-9][0-9])|(6[0-4][0-9][0-9][0-9])|(65[0-4][0-9][0-9])|(655[0-2][0-9])|(6553[0-5]))$/Di', array( + 'stringisempty', + 'myport' + ), array(), true); + $listen_statement = ! empty($this->getParam('listen_statement')) ? 1 : 0; + $namevirtualhost_statement = ! empty($this->getParam('namevirtualhost_statement')) ? 1 : 0; + $vhostcontainer = ! empty($this->getParam('vhostcontainer')) ? 1 : 0; + $specialsettings = validate(str_replace("\r\n", "\n", $this->getParam('specialsettings')), 'specialsettings', '/^[^\0]*$/', '', array(), true); + $vhostcontainer_servername_statement = ! empty($this->getParam('vhostcontainer_servername_statement')) ? 1 : 0; + $default_vhostconf_domain = validate(str_replace("\r\n", "\n", $this->getParam('default_vhostconf_domain')), 'default_vhostconf_domain', '/^[^\0]*$/', '', array(), true); + $docroot = validate($this->getParam('docroot'), 'docroot', '', '', array(), true); + + if ((int) Settings::Get('system.use_ssl') == 1) { + $ssl = ! empty($this->getParam('ssl')) ? intval($this->getParam('ssl')) : 0; + $ssl_cert_file = validate($this->getParam('ssl_cert_file'), 'ssl_cert_file', '', '', array(), true); + $ssl_key_file = validate($this->getParam('ssl_key_file'), 'ssl_key_file', '', '', array(), true); + $ssl_ca_file = validate($this->getParam('ssl_ca_file'), 'ssl_ca_file', '', '', array(), true); + $ssl_cert_chainfile = validate($this->getParam('ssl_cert_chainfile'), 'ssl_cert_chainfile', '', '', array(), true); + } else { + $ssl = 0; + $ssl_cert_file = ''; + $ssl_key_file = ''; + $ssl_ca_file = ''; + $ssl_cert_chainfile = ''; + } + + $result_checkfordouble_stmt = Database::prepare(" + SELECT `id` FROM `" . TABLE_PANEL_IPSANDPORTS . "` + WHERE `ip` = :ip AND `port` = :port + "); + $result_checkfordouble = Database::pexecute_first($result_checkfordouble_stmt, array( + 'ip' => $ip, + 'port' => $port + )); + + $result_sameipotherport_stmt = Database::prepare(" + SELECT `id` FROM `" . TABLE_PANEL_IPSANDPORTS . "` + WHERE `ip` = :ip AND `id` <> :id + "); + $result_sameipotherport = Database::pexecute_first($result_sameipotherport_stmt, array( + 'ip' => $ip, + 'id' => $id + )); + + if ($listen_statement != '1') { + $listen_statement = '0'; + } + + if ($namevirtualhost_statement != '1') { + $namevirtualhost_statement = '0'; + } + + if ($vhostcontainer != '1') { + $vhostcontainer = '0'; + } + + if ($vhostcontainer_servername_statement != '1') { + $vhostcontainer_servername_statement = '0'; + } + + if ($ssl != '1') { + $ssl = '0'; + } + + if ($ssl_cert_file != '') { + $ssl_cert_file = makeCorrectFile($ssl_cert_file); + } + + if ($ssl_key_file != '') { + $ssl_key_file = makeCorrectFile($ssl_key_file); + } + + if ($ssl_ca_file != '') { + $ssl_ca_file = makeCorrectFile($ssl_ca_file); + } + + if ($ssl_cert_chainfile != '') { + $ssl_cert_chainfile = makeCorrectFile($ssl_cert_chainfile); + } + + if (strlen(trim($docroot)) > 0) { + $docroot = makeCorrectDir($docroot); + } else { + $docroot = ''; + } + + if ($result['ip'] != $ip && $result['ip'] == Settings::Get('system.ipaddress') && $result_sameipotherport['id'] == '') { + standard_error('cantchangesystemip', '', true); + } elseif ($result_checkfordouble['id'] != '' && $result_checkfordouble['id'] != $id) { + standard_error('myipnotdouble', '', true); + } else { + + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_IPSANDPORTS . "` + SET + `ip` = :ip, `port` = :port, `listen_statement` = :ls, + `namevirtualhost_statement` = :nvhs, `vhostcontainer` = :vhc, + `vhostcontainer_servername_statement` = :vhcss, + `specialsettings` = :ss, `ssl` = :ssl, + `ssl_cert_file` = :ssl_cert, `ssl_key_file` = :ssl_key, + `ssl_ca_file` = :ssl_ca, `ssl_cert_chainfile` = :ssl_chain, + `default_vhostconf_domain` = :dvhd, `docroot` = :docroot + WHERE `id` = :id; + "); + $upd_data = array( + 'ip' => $ip, + 'port' => $port, + 'ls' => $listen_statement, + 'nvhs' => $namevirtualhost_statement, + 'vhc' => $vhostcontainer, + 'vhcss' => $vhostcontainer_servername_statement, + 'ss' => $specialsettings, + 'ssl' => $ssl, + 'ssl_cert' => $ssl_cert_file, + 'ssl_key' => $ssl_key_file, + 'ssl_ca' => $ssl_ca_file, + 'ssl_chain' => $ssl_cert_chainfile, + 'dvhd' => $default_vhostconf_domain, + 'docroot' => $docroot, + 'id' => $id + ); + Database::pexecute($upd_stmt, $upd_data); + + inserttask('1'); + // Using nameserver, insert a task which rebuilds the server config + inserttask('4'); + + $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] changed IP/port from '" . $result['ip'] . ":" . $result['port'] . "' to '" . $ip . ":" . $port . "'"); + return $this->response(200, "successfull", $upd_data); + } + } + throw new Exception("Not allowed to execute given command.", 403); + } + + public function delete() + { + if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { + $id = $this->getParam('id'); + + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :id + "); + $result = Database::pexecute_first($result_stmt, array( + 'id' => $id + ), true, true); + + $result_checkdomain_stmt = Database::prepare(" + SELECT `id_domain` as `id` FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_ipandports` = :id + "); + $result_checkdomain = Database::pexecute_first($result_checkdomain_stmt, array( + 'id' => $id + ), true, true); + + if ($result_checkdomain['id'] == '') { + if (! in_array($result['id'], explode(',', Settings::Get('system.defaultip')))) { + + $result_sameipotherport_stmt = Database::prepare(" + SELECT `id` FROM `" . TABLE_PANEL_IPSANDPORTS . "` + WHERE `ip` = :ip AND `id` <> :id"); + $result_sameipotherport = Database::pexecute_first($result_sameipotherport_stmt, array( + 'id' => $id, + 'ip' => $result['ip'] + )); + + if (($result['ip'] != Settings::Get('system.ipaddress')) || ($result['ip'] == Settings::Get('system.ipaddress') && $result_sameipotherport['id'] != '')) { + $result_stmt = Database::prepare(" + SELECT `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` + WHERE `id` = :id"); + $result = Database::pexecute_first($result_stmt, array( + 'id' => $id + )); + if ($result['ip'] != '') { + + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_PANEL_IPSANDPORTS . "` + WHERE `id` = :id + "); + Database::pexecute($del_stmt, array( + 'id' => $id + )); + + // also, remove connections to domains (multi-stack) + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_ipandports` = :id + "); + Database::pexecute($del_stmt, array( + 'id' => $id + )); + + inserttask('1'); + // Using nameserver, insert a task which rebuilds the server config + inserttask('4'); + + $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] deleted IP/port '" . $result['ip'] . ":" . $result['port'] . "'"); + return $this->response(200, "successfull", $result); + } + } else { + standard_error('cantdeletesystemip', '', true); + } + } else { + standard_error('cantdeletedefaultip', '', true); + } + } else { + standard_error('ipstillhasdomains', '', true); + } + } + throw new Exception("Not allowed to execute given command.", 403); + } +} From d068477a935d4782225fdbfaeb2468b15e4524e9 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 15 Feb 2018 07:54:21 +0100 Subject: [PATCH 006/746] set version to 0.10.0 Signed-off-by: Michael Kaufmann (d00p) --- install/froxlor.sql | 5 ++-- .../updates/froxlor/0.9/update_0.10.inc.php | 28 +++++++++++++++++++ .../updates/froxlor/0.9/update_0.9.inc.php | 26 +++++++++++++++++ install/updatesql.php | 1 + lib/tables.inc.php | 1 + lib/version.inc.php | 4 +-- 6 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 install/updates/froxlor/0.9/update_0.10.inc.php diff --git a/install/froxlor.sql b/install/froxlor.sql index 5a262c55..6a8534a8 100644 --- a/install/froxlor.sql +++ b/install/froxlor.sql @@ -656,6 +656,7 @@ opcache.interned_strings_buffer'), ('system', 'nssextrausers', '0'), ('system', 'disable_le_selfcheck', '0'), ('system', 'ssl_protocols', 'TLSv1,TLSv1.2'), + ('api', 'enabled', '0'), ('panel', 'decimal_places', '4'), ('panel', 'adminmail', 'admin@SERVERNAME'), ('panel', 'phpmyadmin_url', ''), @@ -687,8 +688,8 @@ opcache.interned_strings_buffer'), ('panel', 'password_special_char_required', '0'), ('panel', 'password_special_char', '!?<>§$%+#=@'), ('panel', 'customer_hide_options', ''), - ('panel', 'version', '0.9.39.5'), - ('panel', 'db_version', '201802130'); + ('panel', 'version', '0.10.0'), + ('panel', 'db_version', '201802150'); DROP TABLE IF EXISTS `panel_tasks`; diff --git a/install/updates/froxlor/0.9/update_0.10.inc.php b/install/updates/froxlor/0.9/update_0.10.inc.php new file mode 100644 index 00000000..7e796501 --- /dev/null +++ b/install/updates/froxlor/0.9/update_0.10.inc.php @@ -0,0 +1,28 @@ + (2010-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package Install + * + */ +if (!defined('_CRON_UPDATE')) { + if (! defined('AREA') || (defined('AREA') && AREA != 'admin') || ! isset($userinfo['loginname']) || (isset($userinfo['loginname']) && $userinfo['loginname'] == '')) { + header('Location: ../../../../index.php'); + exit(); + } +} + +if (isFroxlorVersion('0.9.39.5')) { + + showUpdateStep("Updating from 0.9.39.5 to 0.10.0", false); + updateToVersion('0.10.0'); +} diff --git a/install/updates/froxlor/0.9/update_0.9.inc.php b/install/updates/froxlor/0.9/update_0.9.inc.php index 0abab0fe..2656d31d 100644 --- a/install/updates/froxlor/0.9/update_0.9.inc.php +++ b/install/updates/froxlor/0.9/update_0.9.inc.php @@ -3939,3 +3939,29 @@ if (isFroxlorVersion('0.9.39.4')) { showUpdateStep("Updating from 0.9.39.4 to 0.9.39.5", false); updateToVersion('0.9.39.5'); } + +if (isDatabaseVersion('201802130')) { + + showUpdateStep("Adding new api keys table"); + Database::query("DROP TABLE IF EXISTS `api_keys`;"); + $sql = "CREATE TABLE `api_keys` ( + `id` int(11) NOT NULL auto_increment, + `adminid` int(11) NOT NULL default '0', + `customerid` int(11) NOT NULL default '0', + `apikey` varchar(500) NOT NULL default '', + `secret` varchar(500) NOT NULL default '', + `allowed_from` text NOT NULL, + `valid_until` int(15) NOT NULL default '0', + PRIMARY KEY (id), + KEY adminid (adminid), + KEY customerid (customerid) + ) ENGINE=MyISAM CHARSET=utf8 COLLATE=utf8_general_ci;"; + Database::query($sql); + lastStepStatus(0); + + showUpdateStep("Adding new api settings"); + Settings::AddNew('api.enabled', 0); + lastStepStatus(0); + + updateToDbVersion('201802150'); +} diff --git a/install/updatesql.php b/install/updatesql.php index 4cc7417f..55d954f4 100644 --- a/install/updatesql.php +++ b/install/updatesql.php @@ -55,6 +55,7 @@ if(!isFroxlor()) { if (isFroxlor()) { include_once (makeCorrectFile(dirname(__FILE__).'/updates/froxlor/0.9/update_0.9.inc.php')); + include_once (makeCorrectFile(dirname(__FILE__).'/updates/froxlor/0.10/update_0.10.inc.php')); // Check Froxlor - database integrity (only happens after all updates are done, so we know the db-layout is okay) showUpdateStep("Checking database integrity"); diff --git a/lib/tables.inc.php b/lib/tables.inc.php index 207612f0..2a6dc924 100644 --- a/lib/tables.inc.php +++ b/lib/tables.inc.php @@ -53,5 +53,6 @@ define('TABLE_DOMAINTOIP', 'panel_domaintoip'); define('TABLE_DOMAIN_DNS', 'domain_dns_entries'); define('TABLE_PANEL_FPMDAEMONS', 'panel_fpmdaemons'); define('TABLE_PANEL_PLANS', 'panel_plans'); +define('TABLE_API_KEYS', 'api_keys'); require dirname(__FILE__).'/version.inc.php'; diff --git a/lib/version.inc.php b/lib/version.inc.php index 49935fb7..8870fe02 100644 --- a/lib/version.inc.php +++ b/lib/version.inc.php @@ -16,10 +16,10 @@ */ // Main version variable -$version = '0.9.39.5'; +$version = '0.10.0'; // Database version (YYYYMMDDC where C is a daily counter) -$dbversion = '201802130'; +$dbversion = '201802150'; // Distribution branding-tag (used for Debian etc.) $branding = ''; From 2c1f76e6a44c665e37cce5b06f031564e4a502f7 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 15 Feb 2018 07:56:27 +0100 Subject: [PATCH 007/746] definetly require json extension now Signed-off-by: Michael Kaufmann (d00p) --- install/lib/class.FroxlorInstall.php | 19 ++++++++++--------- install/lng/english.lng.php | 1 - install/lng/german.lng.php | 1 - 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/install/lib/class.FroxlorInstall.php b/install/lib/class.FroxlorInstall.php index 128a7104..757af606 100644 --- a/install/lib/class.FroxlorInstall.php +++ b/install/lib/class.FroxlorInstall.php @@ -1013,7 +1013,17 @@ class FroxlorInstall } else { $content .= $this->_status_message('green', $this->_lng['requirements']['installed']); } + + // check for json extension + $content .= $this->_status_message('begin', $this->_lng['requirements']['phpjson']); + if (! extension_loaded('json')) { + $content .= $this->_status_message('red', $this->_lng['requirements']['notinstalled']); + $_die = true; + } else { + $content .= $this->_status_message('green', $this->_lng['requirements']['installed']); + } + // check for bcmath extension $content .= $this->_status_message('begin', $this->_lng['requirements']['phpbcmath']); @@ -1032,15 +1042,6 @@ class FroxlorInstall $content .= $this->_status_message('green', $this->_lng['requirements']['installed']); } - // check for json extension - $content .= $this->_status_message('begin', $this->_lng['requirements']['phpjson']); - - if (! extension_loaded('json')) { - $content .= $this->_status_message('orange', $this->_lng['requirements']['notinstalled'] . "
" . $this->_lng['requirements']['jsondescription']); - } else { - $content .= $this->_status_message('green', $this->_lng['requirements']['installed']); - } - // check for open_basedir $content .= $this->_status_message('begin', $this->_lng['requirements']['openbasedir']); $php_ob = @ini_get("open_basedir"); diff --git a/install/lng/english.lng.php b/install/lng/english.lng.php index 8651bb83..a4cea2dc 100644 --- a/install/lng/english.lng.php +++ b/install/lng/english.lng.php @@ -38,7 +38,6 @@ $lng['requirements']['phpzip'] = 'PHP zip-extension...'; $lng['requirements']['phpjson'] = 'PHP json-extension...'; $lng['requirements']['bcmathdescription'] = 'Traffic-calculation related functions will not work correctly!'; $lng['requirements']['zipdescription'] = 'The auto-update feature requires the zip extension.'; -$lng['requirements']['jsondescription'] = 'The settings import/export feature requires the json extension.'; $lng['requirements']['openbasedir'] = 'open_basedir...'; $lng['requirements']['openbasedirenabled'] = 'Froxlor will not work properly with open_basedir enabled. Please disable open_basedir for Froxlor in the coresponding php.ini'; $lng['requirements']['diedbecauseofrequirements'] = 'Cannot install Froxlor without these requirements! Try to fix them and retry.'; diff --git a/install/lng/german.lng.php b/install/lng/german.lng.php index d18e94c0..4e4638db 100644 --- a/install/lng/german.lng.php +++ b/install/lng/german.lng.php @@ -38,7 +38,6 @@ $lng['requirements']['phpzip'] = 'PHP zip-Erweiterung...'; $lng['requirements']['phpjson'] = 'PHP json-Erweiterung...'; $lng['requirements']['bcmathdescription'] = 'Traffic-Berechnungs bezogene Funktionen stehen nicht vollständig zur Verfügung!'; $lng['requirements']['zipdescription'] = 'Die Auto-Update Funktion benötigt die zip Erweiterung.'; -$lng['requirements']['jsondescription'] = 'Die Einstellungen Import/Export Funktion benötigt die json Erweiterung.'; $lng['requirements']['openbasedir'] = 'open_basedir genutzt wird...'; $lng['requirements']['openbasedirenabled'] = 'Froxlor wird mit aktiviertem open_basedir nicht vollständig funktionieren. Bitte deaktivieren Sie open_basedir für Froxlor in der entsprechenden php.ini'; $lng['requirements']['diedbecauseofrequirements'] = 'Kann Froxlor ohne diese Voraussetzungen nicht installieren! Beheben Sie die angezeigten Probleme und versuchen Sie es erneut.'; From a82d5cf7642d70935d6b98a6287b95cccf228e20 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 15 Feb 2018 11:37:38 +0100 Subject: [PATCH 008/746] minor fixes Signed-off-by: Michael Kaufmann (d00p) --- api.php | 7 +--- lib/classes/api/abstract.ApiCommand.php | 40 ++++++++++++++++--- .../api/commands/class.IpsAndPorts.php | 12 +++--- 3 files changed, 42 insertions(+), 17 deletions(-) diff --git a/api.php b/api.php index 42bfaae4..c792d18b 100644 --- a/api.php +++ b/api.php @@ -13,10 +13,7 @@ if (Settings::Get('api.enabled') != 1) { header("Content-Type:application/json"); // get our request -$request = isset($_GET['request']) ? $_GET['request'] : null; -if (empty($request)) { - $request = isset($_POST['request']) ? $_POST['request'] : null; -} +$request = @file_get_contents('php://input'); // check if present if (empty($request)) { @@ -63,7 +60,7 @@ function json_response($status, $status_message, $data = null) $response['status_message'] = $status_message; $response['data'] = $data; - $json_response = json_encode($response, JSON_PRETTY_PRINT); + $json_response = json_encode($response, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); echo $json_response; exit(); } diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index 43a5b688..dba62676 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -52,20 +52,50 @@ abstract class ApiCommand return (isset($this->user_data[$detail]) ? $this->user_data[$detail] : null); } + /** + * return user-data array + * + * @return array + */ + protected function getUserData() + { + return $this->user_data; + } + /** * receive field from parameter-list * * @param string $param - * + * @param mixed $default + * set if param is not found + * * @throws Exception * @return mixed */ - protected function getParam($param = null) + protected function getParam($param = null, $default = null) { if (isset($this->cmd_params[$param])) { return $this->cmd_params[$param]; } - return null; + return $default; + } + + /** + * update value of parameter + * + * @param string $param + * @param mixed $value + * + * @throws Exception + * @return boolean + */ + protected function updateParam($param, $value = null) + { + if (isset($this->cmd_params[$param])) { + $this->cmd_params[$param] = $value; + return true; + } + throw new Exception("Unable to update parameter '" . $param . "' as it does not exist", 500); } /** @@ -86,7 +116,7 @@ abstract class ApiCommand $response['status_message'] = $status_message; $response['data'] = $data; - $json_response = json_encode($response, JSON_PRETTY_PRINT); + $json_response = json_encode($response, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); return $json_response; } @@ -95,7 +125,7 @@ abstract class ApiCommand public abstract function get(); public abstract function add(); - + public abstract function update(); public abstract function delete(); diff --git a/lib/classes/api/commands/class.IpsAndPorts.php b/lib/classes/api/commands/class.IpsAndPorts.php index 502d68a1..7aa13794 100644 --- a/lib/classes/api/commands/class.IpsAndPorts.php +++ b/lib/classes/api/commands/class.IpsAndPorts.php @@ -321,16 +321,14 @@ class IpsAndPorts extends ApiCommand if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { $id = $this->getParam('id'); - $result_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :id - "); - $result = Database::pexecute_first($result_stmt, array( + $json_result = IpsAndPorts::getLocal($this->getUserData(), array( 'id' => $id - ), true, true); + ))->get(); + $result = json_decode($json_result, true)['data']; $result_checkdomain_stmt = Database::prepare(" - SELECT `id_domain` as `id` FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_ipandports` = :id - "); + SELECT `id_domain` as `id` FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_ipandports` = :id + "); $result_checkdomain = Database::pexecute_first($result_checkdomain_stmt, array( 'id' => $id ), true, true); From 0fc2fbaf09d76bc202584f1457e1807e4951a500 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 15 Feb 2018 12:59:34 +0100 Subject: [PATCH 009/746] add language strings (english only currently) Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/api_includes.inc.php | 35 ++++++++++ .../api/commands/class.IpsAndPorts.php | 68 +++++++++---------- .../output/function.standard_error.php | 2 +- 3 files changed, 70 insertions(+), 35 deletions(-) diff --git a/lib/classes/api/api_includes.inc.php b/lib/classes/api/api_includes.inc.php index 09b3cd84..322502ef 100644 --- a/lib/classes/api/api_includes.inc.php +++ b/lib/classes/api/api_includes.inc.php @@ -4,3 +4,38 @@ if (! defined('FROXLOR_INSTALL_DIR')) { require_once FROXLOR_INSTALL_DIR . '/lib/tables.inc.php'; require_once FROXLOR_INSTALL_DIR . '/lib/functions.php'; } + +// query the whole table +$result_stmt = Database::query("SELECT * FROM `" . TABLE_PANEL_LANGUAGE . "`"); + +$langs = array(); +// presort languages +while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { + $langs[$row['language']][] = $row; + // check for row[iso] cause older froxlor + // versions didn't have that and it will + // lead to a lot of undfined variables + // before the admin can even update + if (isset($row['iso'])) { + $iso[$row['iso']] = $row['language']; + } +} + +// set default language before anything else to +// ensure that we can display messages +$language = Settings::Get('panel.standardlanguage'); + +// include every english language file we can get +foreach ($langs['English'] as $key => $value) { + include_once makeSecurePath($value['file']); +} + +// now include the selected language if its not english +if ($language != 'English') { + foreach ($langs[$language] as $key => $value) { + include_once makeSecurePath($value['file']); + } +} + +// last but not least include language references file +include_once makeSecurePath('lng/lng_references.php'); diff --git a/lib/classes/api/commands/class.IpsAndPorts.php b/lib/classes/api/commands/class.IpsAndPorts.php index 7aa13794..309f15b2 100644 --- a/lib/classes/api/commands/class.IpsAndPorts.php +++ b/lib/classes/api/commands/class.IpsAndPorts.php @@ -175,31 +175,31 @@ class IpsAndPorts extends ApiCommand $id = $this->getParam('id'); $result_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :id - "); + SELECT * FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :id + "); $result = Database::pexecute_first($result_stmt, array( 'id' => $id ), true, true); - $ip = validate_ip2($this->getParam('ip'), false, 'invalidip', false, false, false, true); - $port = validate($this->getParam('port'), 'port', '/^(([1-9])|([1-9][0-9])|([1-9][0-9][0-9])|([1-9][0-9][0-9][0-9])|([1-5][0-9][0-9][0-9][0-9])|(6[0-4][0-9][0-9][0-9])|(65[0-4][0-9][0-9])|(655[0-2][0-9])|(6553[0-5]))$/Di', array( + $ip = validate_ip2($this->getParam('ip', $result['ip']), false, 'invalidip', false, false, false, true); + $port = validate($this->getParam('port', $result['port']), 'port', '/^(([1-9])|([1-9][0-9])|([1-9][0-9][0-9])|([1-9][0-9][0-9][0-9])|([1-5][0-9][0-9][0-9][0-9])|(6[0-4][0-9][0-9][0-9])|(65[0-4][0-9][0-9])|(655[0-2][0-9])|(6553[0-5]))$/Di', array( 'stringisempty', 'myport' ), array(), true); - $listen_statement = ! empty($this->getParam('listen_statement')) ? 1 : 0; - $namevirtualhost_statement = ! empty($this->getParam('namevirtualhost_statement')) ? 1 : 0; - $vhostcontainer = ! empty($this->getParam('vhostcontainer')) ? 1 : 0; - $specialsettings = validate(str_replace("\r\n", "\n", $this->getParam('specialsettings')), 'specialsettings', '/^[^\0]*$/', '', array(), true); - $vhostcontainer_servername_statement = ! empty($this->getParam('vhostcontainer_servername_statement')) ? 1 : 0; - $default_vhostconf_domain = validate(str_replace("\r\n", "\n", $this->getParam('default_vhostconf_domain')), 'default_vhostconf_domain', '/^[^\0]*$/', '', array(), true); - $docroot = validate($this->getParam('docroot'), 'docroot', '', '', array(), true); + $listen_statement = $this->getParam('listen_statement', $result['listen_statement']); + $namevirtualhost_statement = $this->getParam('namevirtualhost_statement', $result['namevirtualhost_statement']); + $vhostcontainer = $this->getParam('vhostcontainer', $result['vhostcontainer']); + $specialsettings = validate(str_replace("\r\n", "\n", $this->getParam('specialsettings', $result['specialsettings'])), 'specialsettings', '/^[^\0]*$/', '', array(), true); + $vhostcontainer_servername_statement = $this->getParam('vhostcontainer_servername_statement', $result['vhostcontainer_servername_statement']); + $default_vhostconf_domain = validate(str_replace("\r\n", "\n", $this->getParam('default_vhostconf_domain', $result['default_vhostconf_domain'])), 'default_vhostconf_domain', '/^[^\0]*$/', '', array(), true); + $docroot = validate($this->getParam('docroot', $result['docroot']), 'docroot', '', '', array(), true); if ((int) Settings::Get('system.use_ssl') == 1) { - $ssl = ! empty($this->getParam('ssl')) ? intval($this->getParam('ssl')) : 0; - $ssl_cert_file = validate($this->getParam('ssl_cert_file'), 'ssl_cert_file', '', '', array(), true); - $ssl_key_file = validate($this->getParam('ssl_key_file'), 'ssl_key_file', '', '', array(), true); - $ssl_ca_file = validate($this->getParam('ssl_ca_file'), 'ssl_ca_file', '', '', array(), true); - $ssl_cert_chainfile = validate($this->getParam('ssl_cert_chainfile'), 'ssl_cert_chainfile', '', '', array(), true); + $ssl = $this->getParam('ssl', $result['ssl']); + $ssl_cert_file = validate($this->getParam('ssl_cert_file', $result['ssl_cert_file']), 'ssl_cert_file', '', '', array(), true); + $ssl_key_file = validate($this->getParam('ssl_key_file', $result['ssl_key_file']), 'ssl_key_file', '', '', array(), true); + $ssl_ca_file = validate($this->getParam('ssl_ca_file', $result['ssl_ca_file']), 'ssl_ca_file', '', '', array(), true); + $ssl_cert_chainfile = validate($this->getParam('ssl_cert_chainfile', $result['ssl_cert_chainfile']), 'ssl_cert_chainfile', '', '', array(), true); } else { $ssl = 0; $ssl_cert_file = ''; @@ -209,22 +209,22 @@ class IpsAndPorts extends ApiCommand } $result_checkfordouble_stmt = Database::prepare(" - SELECT `id` FROM `" . TABLE_PANEL_IPSANDPORTS . "` - WHERE `ip` = :ip AND `port` = :port - "); + SELECT `id` FROM `" . TABLE_PANEL_IPSANDPORTS . "` + WHERE `ip` = :ip AND `port` = :port + "); $result_checkfordouble = Database::pexecute_first($result_checkfordouble_stmt, array( 'ip' => $ip, 'port' => $port )); $result_sameipotherport_stmt = Database::prepare(" - SELECT `id` FROM `" . TABLE_PANEL_IPSANDPORTS . "` - WHERE `ip` = :ip AND `id` <> :id - "); + SELECT `id` FROM `" . TABLE_PANEL_IPSANDPORTS . "` + WHERE `ip` = :ip AND `id` <> :id + "); $result_sameipotherport = Database::pexecute_first($result_sameipotherport_stmt, array( 'ip' => $ip, 'id' => $id - )); + ), true, true); if ($listen_statement != '1') { $listen_statement = '0'; @@ -275,17 +275,17 @@ class IpsAndPorts extends ApiCommand } else { $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_IPSANDPORTS . "` - SET - `ip` = :ip, `port` = :port, `listen_statement` = :ls, - `namevirtualhost_statement` = :nvhs, `vhostcontainer` = :vhc, - `vhostcontainer_servername_statement` = :vhcss, - `specialsettings` = :ss, `ssl` = :ssl, - `ssl_cert_file` = :ssl_cert, `ssl_key_file` = :ssl_key, - `ssl_ca_file` = :ssl_ca, `ssl_cert_chainfile` = :ssl_chain, - `default_vhostconf_domain` = :dvhd, `docroot` = :docroot - WHERE `id` = :id; - "); + UPDATE `" . TABLE_PANEL_IPSANDPORTS . "` + SET + `ip` = :ip, `port` = :port, `listen_statement` = :ls, + `namevirtualhost_statement` = :nvhs, `vhostcontainer` = :vhc, + `vhostcontainer_servername_statement` = :vhcss, + `specialsettings` = :ss, `ssl` = :ssl, + `ssl_cert_file` = :ssl_cert, `ssl_key_file` = :ssl_key, + `ssl_ca_file` = :ssl_ca, `ssl_cert_chainfile` = :ssl_chain, + `default_vhostconf_domain` = :dvhd, `docroot` = :docroot + WHERE `id` = :id; + "); $upd_data = array( 'ip' => $ip, 'port' => $port, diff --git a/lib/functions/output/function.standard_error.php b/lib/functions/output/function.standard_error.php index 9e8bd7fb..9bcb3397 100644 --- a/lib/functions/output/function.standard_error.php +++ b/lib/functions/output/function.standard_error.php @@ -61,7 +61,7 @@ function standard_error($errors = '', $replacer = '', $throw_exception = false) } if ($throw_exception) { - throw new Exception($error); + throw new Exception($error, 400); } eval("echo \"" . getTemplate('misc/error', '1') . "\";"); exit; From df5fb963c16e5365fcea32d73df818abb3f5c385 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 15 Feb 2018 13:03:30 +0100 Subject: [PATCH 010/746] make language strings the language the user uses Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/abstract.ApiCommand.php | 37 +++++++++++++++++++++++++ lib/classes/api/api_includes.inc.php | 35 +---------------------- 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index dba62676..d11347fc 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -13,6 +13,8 @@ abstract class ApiCommand public function __construct($header = null, $params = null, $userinfo = null) { + global $lng; + $this->cmd_params = $params; if (! empty($header)) { $this->readUserData($header); @@ -23,6 +25,41 @@ abstract class ApiCommand throw new Exception("Invalid user data", 500); } $this->logger = FroxlorLogger::getInstanceOf($this->user_data); + + // query the whole table + $result_stmt = Database::query("SELECT * FROM `" . TABLE_PANEL_LANGUAGE . "`"); + + $langs = array(); + // presort languages + while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { + $langs[$row['language']][] = $row; + } + + // set default language before anything else to + // ensure that we can display messages + $language = Settings::Get('panel.standardlanguage'); + + if (isset($this->user_data['language']) && isset($languages[$this->user_data['language']])) { + // default: use language from session, #277 + $language = $this->user_data['language']; + } elseif (isset($this->user_data['def_language'])) { + $language = $this->user_data['def_language']; + } + + // include every english language file we can get + foreach ($langs['English'] as $key => $value) { + include_once makeSecurePath($value['file']); + } + + // now include the selected language if its not english + if ($language != 'English') { + foreach ($langs[$language] as $key => $value) { + include_once makeSecurePath($value['file']); + } + } + + // last but not least include language references file + include_once makeSecurePath(FROXLOR_INSTALL_DIR . '/lng/lng_references.php'); } public static function getLocal($userinfo = null, $params = null) diff --git a/lib/classes/api/api_includes.inc.php b/lib/classes/api/api_includes.inc.php index 322502ef..0e0eb0ad 100644 --- a/lib/classes/api/api_includes.inc.php +++ b/lib/classes/api/api_includes.inc.php @@ -5,37 +5,4 @@ if (! defined('FROXLOR_INSTALL_DIR')) { require_once FROXLOR_INSTALL_DIR . '/lib/functions.php'; } -// query the whole table -$result_stmt = Database::query("SELECT * FROM `" . TABLE_PANEL_LANGUAGE . "`"); - -$langs = array(); -// presort languages -while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { - $langs[$row['language']][] = $row; - // check for row[iso] cause older froxlor - // versions didn't have that and it will - // lead to a lot of undfined variables - // before the admin can even update - if (isset($row['iso'])) { - $iso[$row['iso']] = $row['language']; - } -} - -// set default language before anything else to -// ensure that we can display messages -$language = Settings::Get('panel.standardlanguage'); - -// include every english language file we can get -foreach ($langs['English'] as $key => $value) { - include_once makeSecurePath($value['file']); -} - -// now include the selected language if its not english -if ($language != 'English') { - foreach ($langs[$language] as $key => $value) { - include_once makeSecurePath($value['file']); - } -} - -// last but not least include language references file -include_once makeSecurePath('lng/lng_references.php'); +$lng = array(); From 81602f17bec500db6ac6e05abd34f1a447ae062c Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 15 Feb 2018 14:44:48 +0100 Subject: [PATCH 011/746] add Domains api module Signed-off-by: Michael Kaufmann (d00p) --- admin_domains.php | 2125 +++----------------- lib/classes/api/commands/class.Domains.php | 1635 +++++++++++++++ 2 files changed, 1889 insertions(+), 1871 deletions(-) create mode 100644 lib/classes/api/commands/class.Domains.php diff --git a/admin_domains.php b/admin_domains.php index f731d59c..89c61f4b 100644 --- a/admin_domains.php +++ b/admin_domains.php @@ -35,9 +35,9 @@ if ($page == 'domains' || $page == 'overview') { } $countcustomers = Database::pexecute_first($stmt, $params); $countcustomers = (int) $countcustomers['countcustomers']; - + if ($action == '') { - + $log->logAction(ADM_ACTION, LOG_NOTICE, "viewed admin_domains"); $fields = array( 'd.domain' => $lng['domains']['domainname'], @@ -49,17 +49,12 @@ if ($page == 'domains' || $page == 'overview') { ); $paging = new paging($userinfo, TABLE_PANEL_DOMAINS, $fields); $domains = ""; - $syshostname = ""; - if (Settings::Get('system.hostname_id')) - { - $syshostname = "AND `d`.`id` <> " . Settings::Get('system.hostname_id'); - } $result_stmt = Database::prepare(" SELECT `d`.*, `c`.`loginname`, `c`.`deactivated`, `c`.`name`, `c`.`firstname`, `c`.`company`, `c`.`standardsubdomain`, `ad`.`id` AS `aliasdomainid`, `ad`.`domain` AS `aliasdomain` FROM `" . TABLE_PANEL_DOMAINS . "` `d` LEFT JOIN `" . TABLE_PANEL_CUSTOMERS . "` `c` USING(`customerid`) LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` `ad` ON `d`.`aliasdomain`=`ad`.`id` - WHERE `d`.`parentdomainid`='0' " . $syshostname . ($userinfo['customers_see_all'] ? '' : " AND `d`.`adminid` = :adminid ") . " " . $paging->getSqlWhere(true) . " " . $paging->getSqlOrderBy() . " " . $paging->getSqlLimit()); + WHERE `d`.`parentdomainid`='0' " . ($userinfo['customers_see_all'] ? '' : " AND `d`.`adminid` = :adminid ") . " " . $paging->getSqlWhere(true) . " " . $paging->getSqlOrderBy() . " " . $paging->getSqlLimit()); $params = array(); if ($userinfo['customers_see_all'] == '0') { $params['adminid'] = $userinfo['adminid']; @@ -72,17 +67,17 @@ if ($page == 'domains' || $page == 'overview') { $searchcode = $paging->getHtmlSearchCode($lng); $pagingcode = $paging->getHtmlPagingCode($filename . '?page=' . $page . '&s=' . $s); $domain_array = array(); - + while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { - + formatDomainEntry($row, $idna_convert); - + if (! isset($domain_array[$row['domain']])) { $domain_array[$row['domain']] = $row; } else { $domain_array[$row['domain']] = array_merge($row, $domain_array[$row['domain']]); } - + if (isset($row['aliasdomainid']) && $row['aliasdomainid'] != null && isset($row['aliasdomain']) && $row['aliasdomain'] != '') { if (! isset($domain_array[$row['aliasdomain']])) { $domain_array[$row['aliasdomain']] = array(); @@ -91,7 +86,7 @@ if ($page == 'domains' || $page == 'overview') { $domain_array[$row['aliasdomain']]['domainalias'] = $row['domain']; } } - + /** * We need ksort/krsort here to make sure idna-domains are also sorted correctly */ @@ -100,26 +95,11 @@ if ($page == 'domains' || $page == 'overview') { } elseif ($paging->sortfield == 'd.domain' && $paging->sortorder == 'desc') { krsort($domain_array); } - - // show froxlor hostname as first entry - if (Settings::Get('system.hostname_id')) - { - $syshost_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `id` = :did"); - $row = Database::pexecute_first($syshost_stmt, array( - 'did' => Settings::Get('system.hostname_id') - )); - formatDomainEntry($row, $idna_convert); - $row['customername'] = 'Froxlor hostname'; - $row['loginname'] = null; - $row['termination_css'] = 'domain-hostname'; - $row['ipandport'] = str_replace("\n", "
", $row['ipandport']); - eval("\$domains.=\"" . getTemplate("domains/domains_domain") . "\";"); - } - + $i = 0; $count = 0; foreach ($domain_array as $row) { - + if (isset($row['domain']) && $row['domain'] != '' && $paging->checkDisplay($i)) { $row['customername'] = getCorrectFullUserDetails($row); $row = htmlentities_array($row); @@ -130,154 +110,41 @@ if ($page == 'domains' || $page == 'overview') { } $i ++; } - + $domainscount = $numrows_domains; - + // Display the list eval("echo \"" . getTemplate("domains/domains") . "\";"); } elseif ($action == 'delete' && $id != 0) { - - $result_stmt = Database::prepare(" - SELECT `d`.* FROM `" . TABLE_PANEL_DOMAINS . "` `d`, `" . TABLE_PANEL_CUSTOMERS . "` `c` - WHERE `d`.`id` = :id AND `d`.`id` <> `c`.`standardsubdomain`" . ($userinfo['customers_see_all'] ? '' : " AND `d`.`adminid` = :adminid")); - $params = array( - 'id' => $id - ); - if ($userinfo['customers_see_all'] == '0') { - $params['adminid'] = $userinfo['adminid']; + + try { + $json_result = Domains::getLocal($userinfo, array( + 'id' => $id, + 'no_std_subdomain' => true + ))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - $result = Database::pexecute_first($result_stmt, $params); - + $result = json_decode($json_result, true)['data']; + $alias_check_stmt = Database::prepare(" SELECT COUNT(`id`) AS `count` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `aliasdomain`= :id"); $alias_check = Database::pexecute_first($alias_check_stmt, array( 'id' => $id )); - - if ($result['domain'] != '' && $alias_check['count'] == 0) { - if (isset($_POST['send']) && $_POST['send'] == 'send') { - // check for deletion of main-domains which are logically subdomains, #329 - $rsd_sql = ''; - $remove_subbutmain_domains = isset($_POST['delete_userfiles']) ? 1 : 0; - if ($remove_subbutmain_domains == 1) { - $rsd_sql .= " OR `ismainbutsubto` = :id"; + + if ($result['domain'] != '') { + if (isset($_POST['send']) && $_POST['send'] == 'send' && $alias_check['count'] == 0) { + + try { + Domains::getLocal($userinfo, array_merge(array( + 'id' => $id + ), $_POST))->delete(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - - $subresult_stmt = Database::prepare(" - SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` - WHERE (`id` = :id OR `parentdomainid` = :id " . $rsd_sql . ")"); - Database::pexecute($subresult_stmt, array( - 'id' => $id - )); - $idString = array(); - $paramString = array(); - while ($subRow = $subresult_stmt->fetch(PDO::FETCH_ASSOC)) { - $idString[] = "`domainid` = :domain_" . (int) $subRow['id']; - $paramString['domain_' . $subRow['id']] = $subRow['id']; - } - - $idString = implode(' OR ', $idString); - - if ($idString != '') { - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_MAIL_USERS . "` WHERE " . $idString); - Database::pexecute($del_stmt, $paramString); - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_MAIL_VIRTUAL . "` WHERE " . $idString); - Database::pexecute($del_stmt, $paramString); - $log->logAction(ADM_ACTION, LOG_NOTICE, "deleted domain/s from mail-tables"); - } - - // if mainbutsubto-domains are not to be deleted, re-assign the (ismainbutsubto value of the main - // domain which is being deleted) as their new ismainbutsubto value - if ($remove_subbutmain_domains !== 1) { - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_DOMAINS . "` SET - `ismainbutsubto` = :newIsMainButSubtoValue - WHERE `ismainbutsubto` = :deletedMainDomainId - "); - Database::pexecute($upd_stmt, array( - 'newIsMainButSubtoValue' => $result['ismainbutsubto'], - 'deletedMainDomainId' => $id, - )); - } - - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_PANEL_DOMAINS . "` - WHERE `id` = :id OR `parentdomainid` = :id " . $rsd_sql); - Database::pexecute($del_stmt, array( - 'id' => $id - )); - - $deleted_domains = $del_stmt->rowCount(); - - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET - `subdomains_used` = `subdomains_used` - :domaincount - WHERE `customerid` = :customerid"); - Database::pexecute($upd_stmt, array( - 'domaincount' => ($deleted_domains - 1), - 'customerid' => $result['customerid'] - )); - - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_ADMINS . "` SET - `domains_used` = `domains_used` - 1 - WHERE `adminid` = :adminid"); - Database::pexecute($upd_stmt, array( - 'adminid' => $userinfo['adminid'] - )); - - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET - `standardsubdomain` = '0' - WHERE `standardsubdomain` = :id AND `customerid` = :customerid"); - Database::pexecute($upd_stmt, array( - 'id' => $result['id'], - 'customerid' => $result['customerid'] - )); - - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_DOMAINTOIP . "` - WHERE `id_domain` = :domainid"); - Database::pexecute($del_stmt, array( - 'domainid' => $id - )); - - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_PANEL_DOMAINREDIRECTS . "` - WHERE `did` = :domainid"); - Database::pexecute($del_stmt, array( - 'domainid' => $id - )); - - // remove certificate from domain_ssl_settings, fixes #1596 - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` - WHERE `domainid` = :domainid"); - Database::pexecute($del_stmt, array( - 'domainid' => $id - )); - - // remove possible existing DNS entries - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_DOMAIN_DNS . "` - WHERE `domain_id` = :domainid - "); - Database::pexecute($del_stmt, array( - 'domainid' => $id - )); - - triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $log); - - $log->logAction(ADM_ACTION, LOG_INFO, "deleted domain/subdomains (#" . $result['id'] . ")"); - updateCounters(); - inserttask('1'); - - // Using nameserver, insert a task which rebuilds the server config - inserttask('4'); - + redirectTo($filename, array( 'page' => $page, 's' => $s @@ -285,7 +152,7 @@ if ($page == 'domains' || $page == 'overview') { } elseif ($alias_check['count'] > 0) { standard_error('domains_cantdeletedomainwithaliases'); } else { - + $showcheck = false; if (domainHasMainSubDomains($id)) { $showcheck = true; @@ -298,850 +165,196 @@ if ($page == 'domains' || $page == 'overview') { } } } elseif ($action == 'add') { - - if ($userinfo['domains_used'] < $userinfo['domains'] || $userinfo['domains'] == '-1') { - if (isset($_POST['send']) && $_POST['send'] == 'send') { - - if ($_POST['domain'] == Settings::Get('system.hostname')) { - standard_error('admin_domain_emailsystemhostname'); - } - - if (substr($_POST['domain'], 0, 4) == 'xn--') { - standard_error('domain_nopunycode'); - } - - $domain = $idna_convert->encode(preg_replace(array( - '/\:(\d)+$/', - '/^https?\:\/\//' - ), '', validate($_POST['domain'], 'domain'))); - - // Check whether domain validation is enabled and if, validate the domain - if (Settings::Get('system.validate_domain') && ! validateDomain($domain)) { - standard_error(array( - 'stringiswrong', - 'mydomain' - )); - } - - $subcanemaildomain = intval($_POST['subcanemaildomain']); - - $isemaildomain = 0; - if (isset($_POST['isemaildomain'])) { - $isemaildomain = intval($_POST['isemaildomain']); - } - - $email_only = 0; - if (isset($_POST['email_only'])) { - $email_only = intval($_POST['email_only']); - } - - $serveraliasoption = 0; - if (isset($_POST['selectserveralias'])) { - $serveraliasoption = intval($_POST['selectserveralias']); - } - - $speciallogfile = 0; - if (isset($_POST['speciallogfile'])) { - $speciallogfile = intval($_POST['speciallogfile']); - } - - $aliasdomain = intval($_POST['alias']); - $issubof = intval($_POST['issubof']); - $customerid = intval($_POST['customerid']); - $customer_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_CUSTOMERS . "` - WHERE `customerid` = :customerid " . ($userinfo['customers_see_all'] ? '' : " AND `adminid` = :adminid")); - $params = array( - 'customerid' => $customerid - ); - if ($userinfo['customers_see_all'] == '0') { - $params['adminid'] = $userinfo['adminid']; - } - $customer = Database::pexecute_first($customer_stmt, $params); - - if (empty($customer) || $customer['customerid'] != $customerid) { - standard_error('customerdoesntexist'); - } - - if ($userinfo['customers_see_all'] == '1') { - - $adminid = intval($_POST['adminid']); - $admin_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_ADMINS . "` - WHERE `adminid` = :adminid AND (`domains_used` < `domains` OR `domains` = '-1')"); - $admin = Database::pexecute_first($admin_stmt, array( - 'adminid' => $adminid - )); - - if (empty($admin) || $admin['adminid'] != $adminid) { - standard_error('admindoesntexist'); - } - } else { - $adminid = $userinfo['adminid']; - $admin = $userinfo; - } - - // set default path if admin/reseller has "change_serversettings == false" but we still - // need to respect the documentroot_use_default_value - setting - $path_suffix = ''; - if (Settings::Get('system.documentroot_use_default_value') == 1) { - $path_suffix = '/' . $domain; - } - $documentroot = makeCorrectDir($customer['documentroot'] . $path_suffix); - - $registration_date = trim($_POST['registration_date']); - $registration_date = validate($registration_date, 'registration_date', '/^(19|20)\d\d[-](0[1-9]|1[012])[-](0[1-9]|[12][0-9]|3[01])$/', '', array( - '0000-00-00', - '0', - '' - )); - if ($registration_date == '0000-00-00') { - $registration_date = null; - } - - $termination_date = trim($_POST['termination_date']); - $termination_date = validate($termination_date, 'termination_date', '/^(19|20)\d\d[-](0[1-9]|1[012])[-](0[1-9]|[12][0-9]|3[01])$/', '', array( - '0000-00-00', - '0', - '' - )); - if ($termination_date == '0000-00-00') { - $termination_date = null; - } - - if ($userinfo['change_serversettings'] == '1') { - - $caneditdomain = isset($_POST['caneditdomain']) ? intval($_POST['caneditdomain']) : 0; - - $isbinddomain = '0'; - $zonefile = ''; - if (Settings::Get('system.bind_enable') == '1') { - if (isset($_POST['isbinddomain'])) { - $isbinddomain = intval($_POST['isbinddomain']); - } - $zonefile = validate($_POST['zonefile'], 'zonefile'); - } - - if (isset($_POST['dkim'])) { - $dkim = intval($_POST['dkim']); - } else { - $dkim = '1'; - } - - $specialsettings = validate(str_replace("\r\n", "\n", $_POST['specialsettings']), 'specialsettings', '/^[^\0]*$/'); - $notryfiles = isset($_POST['notryfiles']) && (int)$_POST['notryfiles'] == 1 ? 1 : 0; - validate($_POST['documentroot'], 'documentroot'); - - // If path is empty and 'Use domain name as default value for DocumentRoot path' is enabled in settings, - // set default path to subdomain or domain name - if (isset($_POST['documentroot']) && $_POST['documentroot'] != '') { - if (substr($_POST['documentroot'], 0, 1) != '/' && ! preg_match('/^https?\:\/\//', $_POST['documentroot'])) { - $documentroot .= '/' . $_POST['documentroot']; - } else { - $documentroot = $_POST['documentroot']; - } - } elseif (isset($_POST['documentroot']) && ($_POST['documentroot'] == '') && (Settings::Get('system.documentroot_use_default_value') == 1)) { - $documentroot = makeCorrectDir($customer['documentroot'] . '/' . $domain); - } - } else { - $isbinddomain = '0'; - if (Settings::Get('system.bind_enable') == '1') { - $isbinddomain = '1'; - } - $caneditdomain = '1'; - $zonefile = ''; - $dkim = '1'; - $specialsettings = ''; - $notryfiles = '0'; - } - - if ($userinfo['caneditphpsettings'] == '1' || $userinfo['change_serversettings'] == '1') { - - $phpenabled = isset($_POST['phpenabled']) ? intval($_POST['phpenabled']) : 0; - $openbasedir = isset($_POST['openbasedir']) ? intval($_POST['openbasedir']) : 0; - - if ((int) Settings::Get('system.mod_fcgid') == 1 || (int) Settings::Get('phpfpm.enabled') == 1) { - $phpsettingid = (int) $_POST['phpsettingid']; - $phpsettingid_check_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_PHPCONFIGS . "` - WHERE `id` = :phpsettingid"); - $phpsettingid_check = Database::pexecute_first($phpsettingid_check_stmt, array( - 'phpsettingid' => $phpsettingid - )); - - if (! isset($phpsettingid_check['id']) || $phpsettingid_check['id'] == '0' || $phpsettingid_check['id'] != $phpsettingid) { - standard_error('phpsettingidwrong'); - } - - if ((int) Settings::Get('system.mod_fcgid') == 1) { - $mod_fcgid_starter = validate($_POST['mod_fcgid_starter'], 'mod_fcgid_starter', '/^[0-9]*$/', '', array( - '-1', - '' - )); - $mod_fcgid_maxrequests = validate($_POST['mod_fcgid_maxrequests'], 'mod_fcgid_maxrequests', '/^[0-9]*$/', '', array( - '-1', - '' - )); - } else { - $mod_fcgid_starter = '-1'; - $mod_fcgid_maxrequests = '-1'; - } - } else { - - if ((int) Settings::Get('phpfpm.enabled') == 1) { - $phpsettingid = Settings::Get('phpfpm.defaultini'); - } else { - $phpsettingid = Settings::Get('system.mod_fcgid_defaultini'); - } - $mod_fcgid_starter = '-1'; - $mod_fcgid_maxrequests = '-1'; - } - } else { - - $phpenabled = '1'; - $openbasedir = '1'; - - if ((int) Settings::Get('phpfpm.enabled') == 1) { - $phpsettingid = Settings::Get('phpfpm.defaultini'); - } else { - $phpsettingid = Settings::Get('system.mod_fcgid_defaultini'); - } - $mod_fcgid_starter = '-1'; - $mod_fcgid_maxrequests = '-1'; - } - - if ($userinfo['ip'] != "-1") { - $admin_ip_stmt = Database::prepare(" - SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` - WHERE `id` = :id ORDER BY `ip`, `port` ASC"); - $admin_ip = Database::pexecute_first($admin_ip_stmt, array( - 'id' => $userinfo['ip'] - )); - $additional_ip_condition = " AND `ip` = :adminip "; - $aip_param = array( - 'adminip' => $admin_ip['ip'] - ); - } else { - $additional_ip_condition = ''; - $aip_param = array(); - } - - $ipandports = array(); - if (isset($_POST['ipandport']) && ! is_array($_POST['ipandport'])) { - $_POST['ipandport'] = unserialize($_POST['ipandport']); - } - - if (isset($_POST['ipandport']) && is_array($_POST['ipandport'])) { - foreach ($_POST['ipandport'] as $ipandport) { - $ipandport = intval($ipandport); - $ipandport_check_stmt = Database::prepare(" - SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` - WHERE `id` = :id " . $additional_ip_condition); - $ip_params = null; - $ip_params = array_merge(array( - 'id' => $ipandport - ), $aip_param); - $ipandport_check = Database::pexecute_first($ipandport_check_stmt, $ip_params); - - if (! isset($ipandport_check['id']) || $ipandport_check['id'] == '0' || $ipandport_check['id'] != $ipandport) { - standard_error('ipportdoesntexist'); - } else { - $ipandports[] = $ipandport; - } - } - } - - if (Settings::Get('system.use_ssl') == "1" && isset($_POST['ssl_ipandport'])) { - $ssl_redirect = 0; - if (isset($_POST['ssl_redirect'])) { - $ssl_redirect = (int) $_POST['ssl_redirect']; - } - - $letsencrypt = 0; - if (isset($_POST['letsencrypt'])) { - $letsencrypt = (int) $_POST['letsencrypt']; - } - - $ssl_ipandports = array(); - if (isset($_POST['ssl_ipandport']) && ! is_array($_POST['ssl_ipandport'])) { - $_POST['ssl_ipandport'] = unserialize($_POST['ssl_ipandport']); - } - - // Verify SSL-Ports - if (isset($_POST['ssl_ipandport']) && is_array($_POST['ssl_ipandport'])) { - foreach ($_POST['ssl_ipandport'] as $ssl_ipandport) { - if (trim($ssl_ipandport) == "") - continue; - // fix if no ssl-ip/port is checked - if (trim($ssl_ipandport) < 1) - continue; - $ssl_ipandport = intval($ssl_ipandport); - $ssl_ipandport_check_stmt = Database::prepare(" - SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` - WHERE `id` = :id " . $additional_ip_condition); - $ip_params = null; - $ip_params = array_merge(array( - 'id' => $ssl_ipandport - ), $aip_param); - $ssl_ipandport_check = Database::pexecute_first($ssl_ipandport_check_stmt, $ip_params); - - if (! isset($ssl_ipandport_check['id']) || $ssl_ipandport_check['id'] == '0' || $ssl_ipandport_check['id'] != $ssl_ipandport) { - standard_error('ipportdoesntexist'); - } else { - $ssl_ipandports[] = $ssl_ipandport; - } - } - - $http2 = isset($_POST['http2']) && (int)$_POST['http2'] == 1 ? 1 : 0; - - // HSTS - $hsts_maxage = isset($_POST['hsts_maxage']) ? (int)$_POST['hsts_maxage'] : 0; - $hsts_sub = isset($_POST['hsts_sub']) && (int)$_POST['hsts_sub'] == 1 ? 1 : 0; - $hsts_preload = isset($_POST['hsts_preload']) && (int)$_POST['hsts_preload'] == 1 ? 1 : 0; - - // OCSP stapling - $ocsp_stapling = isset($_POST['ocsp_stapling']) && (int)$_POST['ocsp_stapling'] == 1 ? 1 : 0; - - } else { - $ssl_redirect = 0; - $letsencrypt = 0; - $http2 = 0; - // we need this for the serialize - // if ssl is disabled or no ssl-ip/port exists - $ssl_ipandports[] = - 1; - - // HSTS - $hsts_maxage = 0; - $hsts_sub = 0; - $hsts_preload = 0; - - // OCSP stapling - $ocsp_stapling = 0; - } - } else { - $ssl_redirect = 0; - $letsencrypt = 0; - $http2 = 0; - // we need this for the serialize - // if ssl is disabled or no ssl-ip/port exists - $ssl_ipandports[] = - 1; - - // HSTS - $hsts_maxage = 0; - $hsts_sub = 0; - $hsts_preload = 0; - - // OCSP stapling - $ocsp_stapling = 0; - } - - // We can't enable let's encrypt for wildcard - domains if using acme-v1 - if ($serveraliasoption == '0' && $letsencrypt == '1' && Settings::Get('system.leapiversion') == '1') { - standard_error('nowildcardwithletsencrypt'); - } - // if using acme-v2 we cannot issue wildcard-certificates - // because they currently only support the dns-01 challenge - if ($serveraliasoption == '0' && $letsencrypt == '1' && Settings::Get('system.leapiversion') == '2') { - standard_error('nowildcardwithletsencryptv2'); - } - - // Temporarily deactivate ssl_redirect until Let's Encrypt certificate was generated - if ($ssl_redirect > 0 && $letsencrypt == 1) { - $ssl_redirect = 2; - } - - if (! preg_match('/^https?\:\/\//', $documentroot)) { - if (strstr($documentroot, ":") !== false) { - standard_error('pathmaynotcontaincolon'); - } else { - $documentroot = makeCorrectDir($documentroot); - } - } - - $domain_check_stmt = Database::prepare(" - SELECT `id`, `domain` FROM `" . TABLE_PANEL_DOMAINS . "` - WHERE `domain` = :domain"); - $domain_check = Database::pexecute_first($domain_check_stmt, array( - 'domain' => strtolower($domain) - )); - - $aliasdomain_check = array( - 'id' => 0 - ); - - if ($aliasdomain != 0) { - // Overwrite given ipandports with these of the "main" domain - $ipandports = array(); - $ssl_ipandports = array(); - $origipresult_stmt = Database::prepare(" - SELECT `id_ipandports` FROM `" . TABLE_DOMAINTOIP . "` - WHERE `id_domain` = :id"); - Database::pexecute($origipresult_stmt, array( - 'id' => $aliasdomain - )); - $ipdata_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :ipid"); - while ($origip = $origipresult_stmt->fetch(PDO::FETCH_ASSOC)) { - $_origip_tmp = Database::pexecute_first($ipdata_stmt, array( - 'ipid' => $origip['id_ipandports'] - )); - if ($_origip_tmp['ssl'] == 0) { - $ipandports[] = $origip['id_ipandports']; - } else { - $ssl_ipandports[] = $origip['id_ipandports']; - } - } - - if (count($ssl_ipandports) == 0) { - // we need this for the serialize - // if ssl is disabled or no ssl-ip/port exists - $ssl_ipandports[] = - 1; - } - - $aliasdomain_check_stmt = Database::prepare(" - SELECT `d`.`id` FROM `" . TABLE_PANEL_DOMAINS . "` `d`, `" . TABLE_PANEL_CUSTOMERS . "` `c` - WHERE `d`.`customerid` = :customerid - AND `d`.`aliasdomain` IS NULL AND `d`.`id` <> `c`.`standardsubdomain` - AND `c`.`customerid` = :customerid - AND `d`.`id` = :aliasdomainid"); - $alias_params = array( - 'customerid' => $customerid, - 'aliasdomainid' => $aliasdomain - ); - $aliasdomain_check = Database::pexecute_first($aliasdomain_check_stmt, $alias_params); - } - - if (count($ipandports) == 0) { - standard_error('noipportgiven'); - } - - if ($phpenabled != '1') { - $phpenabled = '0'; - } - - if ($openbasedir != '1') { - $openbasedir = '0'; - } - - if ($speciallogfile != '1') { - $speciallogfile = '0'; - } - - if ($isbinddomain != '1') { - $isbinddomain = '0'; - } - - if ($isemaildomain != '1') { - $isemaildomain = '0'; - } - - if ($email_only == '1') { - $isemaildomain = '1'; - } else { - $email_only = '0'; - } - - if ($subcanemaildomain != '1' && $subcanemaildomain != '2' && $subcanemaildomain != '3') { - $subcanemaildomain = '0'; - } - - if ($dkim != '1') { - $dkim = '0'; - } - - if ($serveraliasoption != '1' && $serveraliasoption != '2') { - $serveraliasoption = '0'; - } - - if ($caneditdomain != '1') { - $caneditdomain = '0'; - } - - if ($issubof <= '0') { - $issubof = '0'; - } - - if ($domain == '') { - standard_error(array( - 'stringisempty', - 'mydomain' - )); - } elseif ($documentroot == '') { - standard_error(array( - 'stringisempty', - 'mydocumentroot' - )); - } elseif ($customerid == 0) { - standard_error('adduserfirst'); - } elseif (strtolower($domain_check['domain']) == strtolower($domain)) { - standard_error('domainalreadyexists', $idna_convert->decode($domain)); - } elseif ($aliasdomain_check['id'] != $aliasdomain) { - standard_error('domainisaliasorothercustomer'); - } else { - $params = array( - 'page' => $page, - 'action' => $action, - 'domain' => $domain, - 'customerid' => $customerid, - 'adminid' => $adminid, - 'documentroot' => $documentroot, - 'alias' => $aliasdomain, - 'isbinddomain' => $isbinddomain, - 'isemaildomain' => $isemaildomain, - 'email_only' => $email_only, - 'subcanemaildomain' => $subcanemaildomain, - 'caneditdomain' => $caneditdomain, - 'zonefile' => $zonefile, - 'dkim' => $dkim, - 'speciallogfile' => $speciallogfile, - 'selectserveralias' => $serveraliasoption, - 'ipandport' => serialize($ipandports), - 'ssl_redirect' => $ssl_redirect, - 'ssl_ipandport' => serialize($ssl_ipandports), - 'phpenabled' => $phpenabled, - 'openbasedir' => $openbasedir, - 'phpsettingid' => $phpsettingid, - 'mod_fcgid_starter' => $mod_fcgid_starter, - 'mod_fcgid_maxrequests' => $mod_fcgid_maxrequests, - 'specialsettings' => $specialsettings, - 'notryfiles' => $notryfiles, - 'registration_date' => $registration_date, - 'termination_date' => $termination_date, - 'issubof' => $issubof, - 'letsencrypt' => $letsencrypt, - 'http2' => $http2, - 'hsts_maxage' => $hsts_maxage, - 'hsts_sub' => $hsts_sub, - 'hsts_preload' => $hsts_preload, - 'ocsp_stapling' => $ocsp_stapling - ); - - $security_questions = array( - 'reallydisablesecuritysetting' => ($openbasedir == '0' && $userinfo['change_serversettings'] == '1'), - 'reallydocrootoutofcustomerroot' => (substr($documentroot, 0, strlen($customer['documentroot'])) != $customer['documentroot'] && ! preg_match('/^https?\:\/\//', $documentroot)) - ); - $question_nr = 1; - foreach ($security_questions as $question_name => $question_launch) { - if ($question_launch !== false) { - $params[$question_name] = $question_name; - - if (! isset($_POST[$question_name]) || $_POST[$question_name] != $question_name) { - ask_yesno('admin_domain_' . $question_name, $filename, $params, $question_nr); - } - } - $question_nr ++; - } - - $wwwserveralias = ($serveraliasoption == '1') ? '1' : '0'; - $iswildcarddomain = ($serveraliasoption == '0') ? '1' : '0'; - - $ins_data = array( - 'domain' => $domain, - 'customerid' => $customerid, - 'adminid' => $adminid, - 'documentroot' => $documentroot, - 'aliasdomain' => ($aliasdomain != 0 ? $aliasdomain : null), - 'zonefile' => $zonefile, - 'dkim' => $dkim, - 'wwwserveralias' => $wwwserveralias, - 'iswildcarddomain' => $iswildcarddomain, - 'isbinddomain' => $isbinddomain, - 'isemaildomain' => $isemaildomain, - 'email_only' => $email_only, - 'subcanemaildomain' => $subcanemaildomain, - 'caneditdomain' => $caneditdomain, - 'phpenabled' => $phpenabled, - 'openbasedir' => $openbasedir, - 'speciallogfile' => $speciallogfile, - 'specialsettings' => $specialsettings, - 'notryfiles' => $notryfiles, - 'ssl_redirect' => $ssl_redirect, - 'add_date' => time(), - 'registration_date' => $registration_date, - 'termination_date' => $termination_date, - 'phpsettingid' => $phpsettingid, - 'mod_fcgid_starter' => $mod_fcgid_starter, - 'mod_fcgid_maxrequests' => $mod_fcgid_maxrequests, - 'ismainbutsubto' => $issubof, - 'letsencrypt' => $letsencrypt, - 'http2' => $http2, - 'hsts' => $hsts_maxage, - 'hsts_sub' => $hsts_sub, - 'hsts_preload' => $hsts_preload, - 'ocsp_stapling' => $ocsp_stapling - ); - - $ins_stmt = Database::prepare(" - INSERT INTO `" . TABLE_PANEL_DOMAINS . "` SET - `domain` = :domain, - `customerid` = :customerid, - `adminid` = :adminid, - `documentroot` = :documentroot, - `aliasdomain` = :aliasdomain, - `zonefile` = :zonefile, - `dkim` = :dkim, - `dkim_id` = '0', - `dkim_privkey` = '', - `dkim_pubkey` = '', - `wwwserveralias` = :wwwserveralias, - `iswildcarddomain` = :iswildcarddomain, - `isbinddomain` = :isbinddomain, - `isemaildomain` = :isemaildomain, - `email_only` = :email_only, - `subcanemaildomain` = :subcanemaildomain, - `caneditdomain` = :caneditdomain, - `phpenabled` = :phpenabled, - `openbasedir` = :openbasedir, - `speciallogfile` = :speciallogfile, - `specialsettings` = :specialsettings, - `notryfiles` = :notryfiles, - `ssl_redirect` = :ssl_redirect, - `add_date` = :add_date, - `registration_date` = :registration_date, - `termination_date` = :termination_date, - `phpsettingid` = :phpsettingid, - `mod_fcgid_starter` = :mod_fcgid_starter, - `mod_fcgid_maxrequests` = :mod_fcgid_maxrequests, - `ismainbutsubto` = :ismainbutsubto, - `letsencrypt` = :letsencrypt, - `http2` = :http2, - `hsts` = :hsts, - `hsts_sub` = :hsts_sub, - `hsts_preload` = :hsts_preload, - `ocsp_stapling` = :ocsp_stapling - "); - Database::pexecute($ins_stmt, $ins_data); - $domainid = Database::lastInsertId(); - - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_ADMINS . "` SET `domains_used` = `domains_used` + 1 - WHERE `adminid` = :adminid"); - Database::pexecute($upd_stmt, array( - 'adminid' => $adminid - )); - - $ins_stmt = Database::prepare(" - INSERT INTO `" . TABLE_DOMAINTOIP . "` SET - `id_domain` = :domainid, - `id_ipandports` = :ipandportsid - "); - - foreach ($ipandports as $ipportid) { - $ins_data = array( - 'domainid' => $domainid, - 'ipandportsid' => $ipportid - ); - Database::pexecute($ins_stmt, $ins_data); - } - - foreach ($ssl_ipandports as $ssl_ipportid) { - if ($ssl_ipportid > 0) { - $ins_data = array( - 'domainid' => $domainid, - 'ipandportsid' => $ssl_ipportid - ); - Database::pexecute($ins_stmt, $ins_data); - } - } - - triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $log); - - $log->logAction(ADM_ACTION, LOG_INFO, "added domain '" . $domain . "'"); - inserttask('1'); - - // Using nameserver, insert a task which rebuilds the server config - inserttask('4'); - - redirectTo($filename, array( - 'page' => $page, - 's' => $s - )); - } - } else { - - $customers = makeoption($lng['panel']['please_choose'], 0, 0, true); - $result_customers_stmt = Database::prepare(" + + if (isset($_POST['send']) && $_POST['send'] == 'send') { + try { + Domains::getLocal($userinfo, $_POST)->add(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + redirectTo($filename, array( + 'page' => $page, + 's' => $s + )); + } else { + + $customers = makeoption($lng['panel']['please_choose'], 0, 0, true); + $result_customers_stmt = Database::prepare(" SELECT `customerid`, `loginname`, `name`, `firstname`, `company` FROM `" . TABLE_PANEL_CUSTOMERS . "` " . ($userinfo['customers_see_all'] ? '' : " WHERE `adminid` = '" . (int) $userinfo['adminid'] . "' ") . " ORDER BY COALESCE(NULLIF(`name`,''), `company`) ASC"); - $params = array(); - if ($userinfo['customers_see_all'] == '0') { - $params['adminid'] = $userinfo['adminid']; - } - Database::pexecute($result_customers_stmt, $params); - - while ($row_customer = $result_customers_stmt->fetch(PDO::FETCH_ASSOC)) { - $customers .= makeoption(getCorrectFullUserDetails($row_customer) . ' (' . $row_customer['loginname'] . ')', $row_customer['customerid']); - } - - $admins = ''; - if ($userinfo['customers_see_all'] == '1') { - - $result_admins_stmt = Database::query(" + $params = array(); + if ($userinfo['customers_see_all'] == '0') { + $params['adminid'] = $userinfo['adminid']; + } + Database::pexecute($result_customers_stmt, $params); + + while ($row_customer = $result_customers_stmt->fetch(PDO::FETCH_ASSOC)) { + $customers .= makeoption(getCorrectFullUserDetails($row_customer) . ' (' . $row_customer['loginname'] . ')', $row_customer['customerid']); + } + + $admins = ''; + if ($userinfo['customers_see_all'] == '1') { + + $result_admins_stmt = Database::query(" SELECT `adminid`, `loginname`, `name` FROM `" . TABLE_PANEL_ADMINS . "` WHERE `domains_used` < `domains` OR `domains` = '-1' ORDER BY `name` ASC"); - - while ($row_admin = $result_admins_stmt->fetch(PDO::FETCH_ASSOC)) { - $admins .= makeoption(getCorrectFullUserDetails($row_admin) . ' (' . $row_admin['loginname'] . ')', $row_admin['adminid'], $userinfo['adminid']); - } + + while ($row_admin = $result_admins_stmt->fetch(PDO::FETCH_ASSOC)) { + $admins .= makeoption(getCorrectFullUserDetails($row_admin) . ' (' . $row_admin['loginname'] . ')', $row_admin['adminid'], $userinfo['adminid']); } - - if ($userinfo['ip'] == "-1") { - $result_ipsandports_stmt = Database::query(" + } + + if ($userinfo['ip'] == "-1") { + $result_ipsandports_stmt = Database::query(" SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `ssl`='0' ORDER BY `ip`, `port` ASC "); - $result_ssl_ipsandports_stmt = Database::query(" + $result_ssl_ipsandports_stmt = Database::query(" SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `ssl`='1' ORDER BY `ip`, `port` ASC "); - } else { - $admin_ip_stmt = Database::prepare(" + } else { + $admin_ip_stmt = Database::prepare(" SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :ipid ORDER BY `ip`, `port` ASC "); - $admin_ip = Database::pexecute_first($admin_ip_stmt, array( - 'ipid' => $userinfo['ip'] - )); - - $result_ipsandports_stmt = Database::prepare(" + $admin_ip = Database::pexecute_first($admin_ip_stmt, array( + 'ipid' => $userinfo['ip'] + )); + + $result_ipsandports_stmt = Database::prepare(" SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `ssl`='0' AND `ip` = :ipid ORDER BY `ip`, `port` ASC "); - Database::pexecute($result_ipsandports_stmt, array( - 'ipid' => $admin_ip['ip'] - )); - - $result_ssl_ipsandports_stmt = Database::prepare(" + Database::pexecute($result_ipsandports_stmt, array( + 'ipid' => $admin_ip['ip'] + )); + + $result_ssl_ipsandports_stmt = Database::prepare(" SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `ssl`='1' AND `ip` = :ipid ORDER BY `ip`, `port` ASC "); - Database::pexecute($result_ssl_ipsandports_stmt, array( - 'ipid' => $admin_ip['ip'] - )); + Database::pexecute($result_ssl_ipsandports_stmt, array( + 'ipid' => $admin_ip['ip'] + )); + } + + // Build array holding all IPs and Ports available to this admin + $ipsandports = array(); + while ($row_ipandport = $result_ipsandports_stmt->fetch(PDO::FETCH_ASSOC)) { + + if (filter_var($row_ipandport['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + $row_ipandport['ip'] = '[' . $row_ipandport['ip'] . ']'; } - - // Build array holding all IPs and Ports available to this admin - $ipsandports = array(); - while ($row_ipandport = $result_ipsandports_stmt->fetch(PDO::FETCH_ASSOC)) { - - if (filter_var($row_ipandport['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { - $row_ipandport['ip'] = '[' . $row_ipandport['ip'] . ']'; - } - - $ipsandports[] = array( - 'label' => $row_ipandport['ip'] . ':' . $row_ipandport['port'] . '
', - 'value' => $row_ipandport['id'] - ); + + $ipsandports[] = array( + 'label' => $row_ipandport['ip'] . ':' . $row_ipandport['port'] . '
', + 'value' => $row_ipandport['id'] + ); + } + + $ssl_ipsandports = array(); + while ($row_ssl_ipandport = $result_ssl_ipsandports_stmt->fetch(PDO::FETCH_ASSOC)) { + + if (filter_var($row_ssl_ipandport['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + $row_ssl_ipandport['ip'] = '[' . $row_ssl_ipandport['ip'] . ']'; } - - $ssl_ipsandports = array(); - while ($row_ssl_ipandport = $result_ssl_ipsandports_stmt->fetch(PDO::FETCH_ASSOC)) { - - if (filter_var($row_ssl_ipandport['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { - $row_ssl_ipandport['ip'] = '[' . $row_ssl_ipandport['ip'] . ']'; - } - - $ssl_ipsandports[] = array( - 'label' => $row_ssl_ipandport['ip'] . ':' . $row_ssl_ipandport['port'] . '
', - 'value' => $row_ssl_ipandport['id'] - ); - } - - $standardsubdomains = array(); - $result_standardsubdomains_stmt = Database::query(" + + $ssl_ipsandports[] = array( + 'label' => $row_ssl_ipandport['ip'] . ':' . $row_ssl_ipandport['port'] . '
', + 'value' => $row_ssl_ipandport['id'] + ); + } + + $standardsubdomains = array(); + $result_standardsubdomains_stmt = Database::query(" SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` `d`, `" . TABLE_PANEL_CUSTOMERS . "` `c` WHERE `d`.`id` = `c`.`standardsubdomain` "); - - while ($row_standardsubdomain = $result_standardsubdomains_stmt->fetch(PDO::FETCH_ASSOC)) { - $standardsubdomains[] = $row_standardsubdomain['id']; - } - - if (count($standardsubdomains) > 0) { - $standardsubdomains = " AND `d`.`id` NOT IN (" . join(',', $standardsubdomains) . ") "; - } else { - $standardsubdomains = ''; - } - - $domains = makeoption($lng['domains']['noaliasdomain'], 0, NULL, true); - $result_domains_stmt = Database::prepare(" + + while ($row_standardsubdomain = $result_standardsubdomains_stmt->fetch(PDO::FETCH_ASSOC)) { + $standardsubdomains[] = $row_standardsubdomain['id']; + } + + if (count($standardsubdomains) > 0) { + $standardsubdomains = " AND `d`.`id` NOT IN (" . join(',', $standardsubdomains) . ") "; + } else { + $standardsubdomains = ''; + } + + $domains = makeoption($lng['domains']['noaliasdomain'], 0, NULL, true); + $result_domains_stmt = Database::prepare(" SELECT `d`.`id`, `d`.`domain`, `c`.`loginname` FROM `" . TABLE_PANEL_DOMAINS . "` `d`, `" . TABLE_PANEL_CUSTOMERS . "` `c` WHERE `d`.`aliasdomain` IS NULL AND `d`.`parentdomainid` = 0" . $standardsubdomains . ($userinfo['customers_see_all'] ? '' : " AND `d`.`adminid` = :adminid") . " AND `d`.`customerid`=`c`.`customerid` ORDER BY `loginname`, `domain` ASC "); - $params = array(); - if ($userinfo['customers_see_all'] == '0') { - $params['adminid'] = $userinfo['adminid']; - } - Database::pexecute($result_domains_stmt, $params); - - while ($row_domain = $result_domains_stmt->fetch(PDO::FETCH_ASSOC)) { - $domains .= makeoption($idna_convert->decode($row_domain['domain']) . ' (' . $row_domain['loginname'] . ')', $row_domain['id']); - } - - $subtodomains = makeoption($lng['domains']['nosubtomaindomain'], 0, NULL, true); - $result_domains_stmt = Database::prepare(" + $params = array(); + if ($userinfo['customers_see_all'] == '0') { + $params['adminid'] = $userinfo['adminid']; + } + Database::pexecute($result_domains_stmt, $params); + + while ($row_domain = $result_domains_stmt->fetch(PDO::FETCH_ASSOC)) { + $domains .= makeoption($idna_convert->decode($row_domain['domain']) . ' (' . $row_domain['loginname'] . ')', $row_domain['id']); + } + + $subtodomains = makeoption($lng['domains']['nosubtomaindomain'], 0, NULL, true); + $result_domains_stmt = Database::prepare(" SELECT `d`.`id`, `d`.`domain`, `c`.`loginname` FROM `" . TABLE_PANEL_DOMAINS . "` `d`, `" . TABLE_PANEL_CUSTOMERS . "` `c` WHERE `d`.`aliasdomain` IS NULL AND `d`.`parentdomainid` = 0 AND `d`.`ismainbutsubto` = 0 " . $standardsubdomains . ($userinfo['customers_see_all'] ? '' : " AND `d`.`adminid` = :adminid") . " AND `d`.`customerid`=`c`.`customerid` ORDER BY `loginname`, `domain` ASC "); - // params from above still valid - Database::pexecute($result_domains_stmt, $params); - - while ($row_domain = $result_domains_stmt->fetch(PDO::FETCH_ASSOC)) { - $subtodomains .= makeoption($idna_convert->decode($row_domain['domain']) . ' (' . $row_domain['loginname'] . ')', $row_domain['id']); - } - - $phpconfigs = ''; - $configs = Database::query(" + // params from above still valid + Database::pexecute($result_domains_stmt, $params); + + while ($row_domain = $result_domains_stmt->fetch(PDO::FETCH_ASSOC)) { + $subtodomains .= makeoption($idna_convert->decode($row_domain['domain']) . ' (' . $row_domain['loginname'] . ')', $row_domain['id']); + } + + $phpconfigs = ''; + $configs = Database::query(" SELECT c.*, fc.description as interpreter FROM `" . TABLE_PANEL_PHPCONFIGS . "` c LEFT JOIN `" . TABLE_PANEL_FPMDAEMONS . "` fc ON fc.id = c.fpmsettingid "); - - while ($row = $configs->fetch(PDO::FETCH_ASSOC)) { - if ((int) Settings::Get('phpfpm.enabled') == 1) { - $phpconfigs .= makeoption($row['description'] . " [".$row['interpreter']."]", $row['id'], Settings::Get('phpfpm.defaultini'), true, true); - } else { - $phpconfigs .= makeoption($row['description'], $row['id'], Settings::Get('system.mod_fcgid_defaultini'), true, true); - } + + while ($row = $configs->fetch(PDO::FETCH_ASSOC)) { + if ((int) Settings::Get('phpfpm.enabled') == 1) { + $phpconfigs .= makeoption($row['description'] . " [" . $row['interpreter'] . "]", $row['id'], Settings::Get('phpfpm.defaultini'), true, true); + } else { + $phpconfigs .= makeoption($row['description'], $row['id'], Settings::Get('system.mod_fcgid_defaultini'), true, true); } - - // create serveralias options - $serveraliasoptions = ""; - $serveraliasoptions .= makeoption($lng['domains']['serveraliasoption_wildcard'], '0', '0', true, true); - $serveraliasoptions .= makeoption($lng['domains']['serveraliasoption_www'], '1', '0', true, true); - $serveraliasoptions .= makeoption($lng['domains']['serveraliasoption_none'], '2', '0', true, true); - - $subcanemaildomain = makeoption($lng['admin']['subcanemaildomain']['never'], '0', '0', true, true); - $subcanemaildomain .= makeoption($lng['admin']['subcanemaildomain']['choosableno'], '1', '0', true, true); - $subcanemaildomain .= makeoption($lng['admin']['subcanemaildomain']['choosableyes'], '2', '0', true, true); - $subcanemaildomain .= makeoption($lng['admin']['subcanemaildomain']['always'], '3', '0', true, true); - - $add_date = date('Y-m-d'); - - $domain_add_data = include_once dirname(__FILE__) . '/lib/formfields/admin/domains/formfield.domains_add.php'; - $domain_add_form = htmlform::genHTMLForm($domain_add_data); - - $title = $domain_add_data['domain_add']['title']; - $image = $domain_add_data['domain_add']['image']; - - eval("echo \"" . getTemplate("domains/domains_add") . "\";"); } + + // create serveralias options + $serveraliasoptions = ""; + $serveraliasoptions .= makeoption($lng['domains']['serveraliasoption_wildcard'], '0', '0', true, true); + $serveraliasoptions .= makeoption($lng['domains']['serveraliasoption_www'], '1', '0', true, true); + $serveraliasoptions .= makeoption($lng['domains']['serveraliasoption_none'], '2', '0', true, true); + + $subcanemaildomain = makeoption($lng['admin']['subcanemaildomain']['never'], '0', '0', true, true); + $subcanemaildomain .= makeoption($lng['admin']['subcanemaildomain']['choosableno'], '1', '0', true, true); + $subcanemaildomain .= makeoption($lng['admin']['subcanemaildomain']['choosableyes'], '2', '0', true, true); + $subcanemaildomain .= makeoption($lng['admin']['subcanemaildomain']['always'], '3', '0', true, true); + + $add_date = date('Y-m-d'); + + $domain_add_data = include_once dirname(__FILE__) . '/lib/formfields/admin/domains/formfield.domains_add.php'; + $domain_add_form = htmlform::genHTMLForm($domain_add_data); + + $title = $domain_add_data['domain_add']['title']; + $image = $domain_add_data['domain_add']['image']; + + eval("echo \"" . getTemplate("domains/domains_add") . "\";"); } } elseif ($action == 'edit' && $id != 0) { - - $result_stmt = Database::prepare(" - SELECT `d`.*, `c`.`customerid` - FROM `" . TABLE_PANEL_DOMAINS . "` `d` - LEFT JOIN `" . TABLE_PANEL_CUSTOMERS . "` `c` USING(`customerid`) - WHERE `d`.`parentdomainid` = '0' - AND `d`.`id` = :id" . ($userinfo['customers_see_all'] ? '' : " AND `d`.`adminid` = :adminid") - ); - $params = array( - 'id' => $id - ); - if ($userinfo['customers_see_all'] == '0') { - $params['adminid'] = $userinfo['adminid']; + + try { + $json_result = Domains::getLocal($userinfo, array( + 'id' => $id + ))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - $result = Database::pexecute_first($result_stmt, $params); - + $result = json_decode($json_result, true)['data']; + if ($result['domain'] != '') { - + $subdomains_stmt = Database::prepare(" SELECT COUNT(`id`) AS count FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `parentdomainid` = :resultid @@ -1150,7 +363,7 @@ if ($page == 'domains' || $page == 'overview') { 'resultid' => $result['id'] )); $subdomains = $subdomains['count']; - + $alias_check_stmt = Database::prepare(" SELECT COUNT(`id`) AS count FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `aliasdomain` = :resultid @@ -1159,7 +372,7 @@ if ($page == 'domains' || $page == 'overview') { 'resultid' => $result['id'] )); $alias_check = $alias_check['count']; - + $domain_emails_result_stmt = Database::prepare(" SELECT `email`, `email_full`, `destination`, `popaccountid` AS `number_email_forwarders` FROM `" . TABLE_MAIL_VIRTUAL . "` WHERE `customerid` = :customerid AND `domainid` = :id @@ -1168,876 +381,49 @@ if ($page == 'domains' || $page == 'overview') { 'customerid' => $result['customerid'], 'id' => $result['id'] )); - + $emails = Database::num_rows(); $email_forwarders = 0; $email_accounts = 0; - + while ($domain_emails_row = $domain_emails_result_stmt->fetch(PDO::FETCH_ASSOC)) { - + if ($domain_emails_row['destination'] != '') { - + $domain_emails_row['destination'] = explode(' ', makeCorrectDestination($domain_emails_row['destination'])); $email_forwarders += count($domain_emails_row['destination']); - + if (in_array($domain_emails_row['email_full'], $domain_emails_row['destination'])) { $email_forwarders -= 1; $email_accounts ++; } } } - + $ipsresult_stmt = Database::prepare(" SELECT `id_ipandports` FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :id "); Database::pexecute($ipsresult_stmt, array( 'id' => $result['id'] )); - + $usedips = array(); while ($ipsresultrow = $ipsresult_stmt->fetch(PDO::FETCH_ASSOC)) { $usedips[] = $ipsresultrow['id_ipandports']; } - + if (isset($_POST['send']) && $_POST['send'] == 'send') { - - $customer_stmt = Database::prepare(" - SELECT * FROM " . TABLE_PANEL_CUSTOMERS . " WHERE `customerid` = :customerid - "); - $customer = Database::pexecute_first($customer_stmt, array( - 'customerid' => $result['customerid'] - )); - - $customerid = - 1; - if (isset($_POST['customerid'])) { - $customerid = intval($_POST['customerid']); - } - - if ($customerid > 0 && $customerid != $result['customerid'] && Settings::Get('panel.allow_domain_change_customer') == '1') { - - $customer_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_CUSTOMERS . "` - WHERE `customerid` = :customerid - AND (`subdomains_used` + :subdomains <= `subdomains` OR `subdomains` = '-1' ) - AND (`emails_used` + :emails <= `emails` OR `emails` = '-1' ) - AND (`email_forwarders_used` + :forwarders <= `email_forwarders` OR `email_forwarders` = '-1' ) - AND (`email_accounts_used` + :accounts <= `email_accounts` OR `email_accounts` = '-1' ) " . ($userinfo['customers_see_all'] ? '' : " AND `adminid` = :adminid")); - - $params = array( - 'customerid' => $customerid, - 'subdomains' => $subdomains, - 'emails' => $emails, - 'forwarders' => $email_forwarders, - 'accounts' => $email_accounts - ); - if ($userinfo['customers_see_all'] == '0') { - $params['adminid'] = $userinfo['adminid']; - } - - $customer = Database::pexecute_first($customer_stmt, $params); - if (empty($customer) || $customer['customerid'] != $customerid) { - standard_error('customerdoesntexist'); - } - } else { - $customerid = $result['customerid']; - } - - $customer_stmt = Database::prepare(" - SELECT * FROM " . TABLE_PANEL_ADMINS . " WHERE `adminid` = :adminid - "); - $admin = Database::pexecute_first($customer_stmt, array( - 'adminid' => $result['adminid'] - )); - - if ($userinfo['customers_see_all'] == '1') { - - $adminid = - 1; - if (isset($_POST['adminid'])) { - $adminid = intval($_POST['adminid']); - } - - if ($adminid > 0 && $adminid != $result['adminid'] && Settings::Get('panel.allow_domain_change_admin') == '1') { - - $admin_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_ADMINS . "` - WHERE `adminid` = :adminid AND ( `domains_used` < `domains` OR `domains` = '-1' ) - "); - $admin = Database::pexecute_first($admin_stmt, array( - 'adminid' => $adminid - )); - - if (empty($admin) || $admin['adminid'] != $adminid) { - standard_error('admindoesntexist'); - } - } else { - $adminid = $result['adminid']; - } - } else { - $adminid = $result['adminid']; - } - - $aliasdomain = isset($_POST['alias']) ? intval($_POST['alias']) : 0; - $issubof = intval($_POST['issubof']); - $subcanemaildomain = intval($_POST['subcanemaildomain']); - $caneditdomain = isset($_POST['caneditdomain']) ? intval($_POST['caneditdomain']) : 0; - $registration_date = trim($_POST['registration_date']); - $registration_date = validate($registration_date, 'registration_date', '/^(19|20)\d\d[-](0[1-9]|1[012])[-](0[1-9]|[12][0-9]|3[01])$/', '', array( - '0000-00-00', - '0', - '' - )); - if ($registration_date == '0000-00-00') { - $registration_date = null; - } - $termination_date = trim($_POST['termination_date']); - $termination_date = validate($termination_date, 'termination_date', '/^(19|20)\d\d[-](0[1-9]|1[012])[-](0[1-9]|[12][0-9]|3[01])$/', '', array( - '0000-00-00', - '0', - '' - )); - if ($termination_date == '0000-00-00') { - $termination_date = null; - } - - $isemaildomain = 0; - if (isset($_POST['isemaildomain'])) { - $isemaildomain = intval($_POST['isemaildomain']); - } - - $email_only = 0; - if (isset($_POST['email_only'])) { - $email_only = intval($_POST['email_only']); - } - - $serveraliasoption = '2'; - if ($result['iswildcarddomain'] == '1') { - $serveraliasoption = '0'; - } elseif ($result['wwwserveralias'] == '1') { - $serveraliasoption = '1'; - } - if (isset($_POST['selectserveralias'])) { - $serveraliasoption = intval($_POST['selectserveralias']); - } - - $speciallogfile = 0; - if (isset($_POST['speciallogfile'])) - $speciallogfile = intval($_POST['speciallogfile']); - - if ($userinfo['change_serversettings'] == '1') { - $isbinddomain = $result['isbinddomain']; - $zonefile = $result['zonefile']; - if (Settings::Get('system.bind_enable') == '1') { - if (isset($_POST['isbinddomain'])) { - $isbinddomain = (int) $_POST['isbinddomain']; - } else { - $isbinddomain = 0; - } - $zonefile = validate($_POST['zonefile'], 'zonefile'); - } - - if (Settings::Get('dkim.use_dkim') == '1') { - $dkim = isset($_POST['dkim']) ? 1 : 0; - } else { - $dkim = $result['dkim']; - } - - $specialsettings = validate(str_replace("\r\n", "\n", $_POST['specialsettings']), 'specialsettings', '/^[^\0]*$/'); - $ssfs = (isset($_POST['specialsettingsforsubdomains']) && intval($_POST['specialsettingsforsubdomains']) == 1) ? 1 : 0; - $notryfiles = isset($_POST['notryfiles']) && (int)$_POST['notryfiles'] == 1 ? 1 : 0; - $documentroot = validate($_POST['documentroot'], 'documentroot'); - - if ($documentroot == '') { - // If path is empty and 'Use domain name as default value for DocumentRoot path' is enabled in settings, - // set default path to subdomain or domain name - if (Settings::Get('system.documentroot_use_default_value') == 1) { - $documentroot = makeCorrectDir($customer['documentroot'] . '/' . $result['domain']); - } else { - $documentroot = $customer['documentroot']; - } - } - - if (! preg_match('/^https?\:\/\//', $documentroot) && strstr($documentroot, ":") !== false) { - standard_error('pathmaynotcontaincolon'); - } - } else { - $isbinddomain = $result['isbinddomain']; - $zonefile = $result['zonefile']; - $dkim = $result['dkim']; - $specialsettings = $result['specialsettings']; - $ssfs = (empty($specialsettings) ? 0 : 1); - $notryfiles = $result['notryfiles']; - $documentroot = $result['documentroot']; - } - - $speciallogverified = (isset($_POST['speciallogverified']) ? (int) $_POST['speciallogverified'] : 0); - - if ($userinfo['caneditphpsettings'] == '1' || $userinfo['change_serversettings'] == '1') { - - $phpenabled = isset($_POST['phpenabled']) ? intval($_POST['phpenabled']) : 0; - $openbasedir = isset($_POST['openbasedir']) ? intval($_POST['openbasedir']) : 0; - $phpfs = (isset($_POST['phpsettingsforsubdomains']) && intval($_POST['phpsettingsforsubdomains']) == 1) ? 1 : 0; - - if ((int) Settings::Get('system.mod_fcgid') == 1 || (int) Settings::Get('phpfpm.enabled') == 1) { - $phpsettingid = (int) $_POST['phpsettingid']; - $phpsettingid_check_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_PHPCONFIGS . "` WHERE `id` = :phpid - "); - $phpsettingid_check = Database::pexecute_first($phpsettingid_check_stmt, array( - 'phpid' => $phpsettingid - )); - - if (! isset($phpsettingid_check['id']) || $phpsettingid_check['id'] == '0' || $phpsettingid_check['id'] != $phpsettingid) { - standard_error('phpsettingidwrong'); - } - - if ((int) Settings::Get('system.mod_fcgid') == 1) { - $mod_fcgid_starter = validate($_POST['mod_fcgid_starter'], 'mod_fcgid_starter', '/^[0-9]*$/', '', array( - '-1', - '' - )); - $mod_fcgid_maxrequests = validate($_POST['mod_fcgid_maxrequests'], 'mod_fcgid_maxrequests', '/^[0-9]*$/', '', array( - '-1', - '' - )); - } else { - $mod_fcgid_starter = $result['mod_fcgid_starter']; - $mod_fcgid_maxrequests = $result['mod_fcgid_maxrequests']; - } - } else { - $phpsettingid = $result['phpsettingid']; - $phpfs = 1; - $mod_fcgid_starter = $result['mod_fcgid_starter']; - $mod_fcgid_maxrequests = $result['mod_fcgid_maxrequests']; - } - } else { - $phpenabled = $result['phpenabled']; - $openbasedir = $result['openbasedir']; - $phpsettingid = $result['phpsettingid']; - $phpfs = 1; - $mod_fcgid_starter = $result['mod_fcgid_starter']; - $mod_fcgid_maxrequests = $result['mod_fcgid_maxrequests']; - } - - $ipandports = array(); - if (isset($_POST['ipandport']) && ! is_array($_POST['ipandport'])) { - $_POST['ipandport'] = unserialize($_POST['ipandport']); - } - if (isset($_POST['ipandport']) && is_array($_POST['ipandport'])) { - - $ipandport_check_stmt = Database::prepare(" - SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :ipandport - "); - foreach ($_POST['ipandport'] as $ipandport) { - if (trim($ipandport) == "") - continue; - $ipandport = intval($ipandport); - $ipandport_check = Database::pexecute_first($ipandport_check_stmt, array( - 'ipandport' => $ipandport - )); - if (! isset($ipandport_check['id']) || $ipandport_check['id'] == '0' || $ipandport_check['id'] != $ipandport) { - standard_error('ipportdoesntexist'); - } else { - $ipandports[] = $ipandport; - } - } - } - - if (Settings::Get('system.use_ssl') == '1' && isset($_POST['ssl_ipandport'])) { - $ssl = 1; // if ssl is set and != 0, it can only be 1 - $ssl_redirect = 0; - if (isset($_POST['ssl_redirect'])) { - $ssl_redirect = (int) $_POST['ssl_redirect']; - } - - $letsencrypt = 0; - if (isset($_POST['letsencrypt'])) { - $letsencrypt = (int) $_POST['letsencrypt']; - } - - $http2 = isset($_POST['http2']) && (int)$_POST['http2'] == 1 ? 1 : 0; - - // HSTS - $hsts_maxage = isset($_POST['hsts_maxage']) ? (int)$_POST['hsts_maxage'] : 0; - $hsts_sub = isset($_POST['hsts_sub']) && (int)$_POST['hsts_sub'] == 1 ? 1 : 0; - $hsts_preload = isset($_POST['hsts_preload']) && (int)$_POST['hsts_preload'] == 1 ? 1 : 0; - - // OCSP stapling - $ocsp_stapling = isset($_POST['ocsp_stapling']) && (int)$_POST['ocsp_stapling'] == 1 ? 1 : 0; - - $ssl_ipandports = array(); - if (isset($_POST['ssl_ipandport']) && ! is_array($_POST['ssl_ipandport'])) { - $_POST['ssl_ipandport'] = unserialize($_POST['ssl_ipandport']); - } - if (isset($_POST['ssl_ipandport']) && is_array($_POST['ssl_ipandport'])) { - - $ssl_ipandport_check_stmt = Database::prepare(" - SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :ipandport - "); - foreach ($_POST['ssl_ipandport'] as $ssl_ipandport) { - if (trim($ssl_ipandport) == "") - continue; - // fix if ip/port got de-checked and it was the last one - if (trim($ssl_ipandport) < 1) - continue; - $ssl_ipandport = intval($ssl_ipandport); - $ssl_ipandport_check = Database::pexecute_first($ssl_ipandport_check_stmt, array( - 'ipandport' => $ssl_ipandport - )); - if (! isset($ssl_ipandport_check['id']) || $ssl_ipandport_check['id'] == '0' || $ssl_ipandport_check['id'] != $ssl_ipandport) { - standard_error('ipportdoesntexist'); - } else { - $ssl_ipandports[] = $ssl_ipandport; - } - } - - } else { - $ssl_redirect = 0; - $letsencrypt = 0; - $http2 = 0; - // we need this for the serialize - // if ssl is disabled or no ssl-ip/port exists - $ssl_ipandports[] = - 1; - - // HSTS - $hsts_maxage = 0; - $hsts_sub = 0; - $hsts_preload = 0; - - // OCSP stapling - $ocsp_stapling = 0; - } - } else { - $ssl_redirect = 0; - $letsencrypt = 0; - $http2 = 0; - // we need this for the serialize - // if ssl is disabled or no ssl-ip/port exists - $ssl_ipandports[] = - 1; - - // HSTS - $hsts_maxage = 0; - $hsts_sub = 0; - $hsts_preload = 0; - - // OCSP stapling - $ocsp_stapling = 0; - } - - // We can't enable let's encrypt for wildcard domains when using acme-v1 - if ($serveraliasoption == '0' && $letsencrypt == '1' && Settings::Get('system.leapiversion') == '1') { - standard_error('nowildcardwithletsencrypt'); - } - // if using acme-v2 we cannot issue wildcard-certificates - // because they currently only support the dns-01 challenge - if ($serveraliasoption == '0' && $letsencrypt == '1' && Settings::Get('system.leapiversion') == '2') { - standard_error('nowildcardwithletsencryptv2'); - } - - // Temporarily deactivate ssl_redirect until Let's Encrypt certificate was generated - if ($ssl_redirect > 0 && $letsencrypt == 1 && $result['letsencrypt'] != $letsencrypt) { - $ssl_redirect = 2; - } - - if (! preg_match('/^https?\:\/\//', $documentroot)) { - $documentroot = makeCorrectDir($documentroot); - } - - if ($phpenabled != '1') { - $phpenabled = '0'; - } - - if ($openbasedir != '1') { - $openbasedir = '0'; - } - - if ($isbinddomain != '1') { - $isbinddomain = '0'; - } - - if ($isemaildomain != '1') { - $isemaildomain = '0'; - } - - if ($email_only == '1') { - $isemaildomain = '1'; - } else { - $email_only = '0'; - } - - if ($subcanemaildomain != '1' && $subcanemaildomain != '2' && $subcanemaildomain != '3') { - $subcanemaildomain = '0'; - } - - if ($dkim != '1') { - $dkim = '0'; - } - - if ($caneditdomain != '1') { - $caneditdomain = '0'; - } - - $aliasdomain_check = array( - 'id' => 0 - ); - - if ($aliasdomain != 0) { - // Overwrite given ipandports with these of the "main" domain - $ipandports = array(); - $ssl_ipandports = array(); - $origipresult_stmt = Database::prepare(" - SELECT `id_ipandports` FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :aliasdomain - "); - Database::pexecute($origipresult_stmt, array( - 'aliasdomain' => $aliasdomain - )); - $ipdata_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :ipid"); - while ($origip = $origipresult_stmt->fetch(PDO::FETCH_ASSOC)) { - $_origip_tmp = Database::pexecute_first($ipdata_stmt, array( - 'ipid' => $origip['id_ipandports'] - )); - if ($_origip_tmp['ssl'] == 0) { - $ipandports[] = $origip['id_ipandports']; - } else { - $ssl_ipandports[] = $origip['id_ipandports']; - } - } - - if (count($ssl_ipandports) == 0) { - // we need this for the serialize - // if ssl is disabled or no ssl-ip/port exists - $ssl_ipandports[] = - 1; - } - - $aliasdomain_check_stmt = Database::prepare(" - SELECT `d`.`id` FROM `" . TABLE_PANEL_DOMAINS . "` `d`, `" . TABLE_PANEL_CUSTOMERS . "` `c` - WHERE `d`.`customerid` = :customerid - AND `d`.`aliasdomain` IS NULL AND `d`.`id` <> `c`.`standardsubdomain` - AND `c`.`customerid` = :customerid - AND `d`.`id` = :aliasdomain - "); - $aliasdomain_check = Database::pexecute_first($aliasdomain_check_stmt, array( - 'customerid' => $customerid, - 'aliasdomain' => $aliasdomain - )); - } - - if (count($ipandports) == 0) { - standard_error('noipportgiven'); - } - - if ($aliasdomain_check['id'] != $aliasdomain) { - standard_error('domainisaliasorothercustomer'); - } - - if ($issubof <= '0') { - $issubof = '0'; - } - - if ($serveraliasoption != '1' && $serveraliasoption != '2') { - $serveraliasoption = '0'; - } - - $params = array( - 'id' => $id, - 'page' => $page, - 'action' => $action, - 'customerid' => $customerid, - 'adminid' => $adminid, - 'documentroot' => $documentroot, - 'alias' => $aliasdomain, - 'isbinddomain' => $isbinddomain, - 'isemaildomain' => $isemaildomain, - 'email_only' => $email_only, - 'subcanemaildomain' => $subcanemaildomain, - 'caneditdomain' => $caneditdomain, - 'zonefile' => $zonefile, - 'dkim' => $dkim, - 'selectserveralias' => $serveraliasoption, - 'ssl_redirect' => $ssl_redirect, - 'phpenabled' => $phpenabled, - 'openbasedir' => $openbasedir, - 'phpsettingid' => $phpsettingid, - 'phpsettingsforsubdomains' => $phpfs, - 'mod_fcgid_starter' => $mod_fcgid_starter, - 'mod_fcgid_maxrequests' => $mod_fcgid_maxrequests, - 'specialsettings' => $specialsettings, - 'specialsettingsforsubdomains' => $ssfs, - 'notryfiles' => $notryfiles, - 'registration_date' => $registration_date, - 'termination_date' => $termination_date, - 'issubof' => $issubof, - 'speciallogfile' => $speciallogfile, - 'speciallogverified' => $speciallogverified, - 'ipandport' => serialize($ipandports), - 'ssl_ipandport' => serialize($ssl_ipandports), - 'letsencrypt' => $letsencrypt, - 'http2' => $http2, - 'hsts_maxage' => $hsts_maxage, - 'hsts_sub' => $hsts_sub, - 'hsts_preload' => $hsts_preload, - 'ocsp_stapling' => $ocsp_stapling - ); - - $security_questions = array( - 'reallydisablesecuritysetting' => ($openbasedir == '0' && $userinfo['change_serversettings'] == '1'), - 'reallydocrootoutofcustomerroot' => (substr($documentroot, 0, strlen($customer['documentroot'])) != $customer['documentroot'] && ! preg_match('/^https?\:\/\//', $documentroot)) - ); - foreach ($security_questions as $question_name => $question_launch) { - if ($question_launch !== false) { - $params[$question_name] = $question_name; - if (! isset($_POST[$question_name]) || $_POST[$question_name] != $question_name) { - ask_yesno('admin_domain_' . $question_name, $filename, $params); - } - } - } - - $wwwserveralias = ($serveraliasoption == '1') ? '1' : '0'; - $iswildcarddomain = ($serveraliasoption == '0') ? '1' : '0'; - - if ( - $documentroot != $result['documentroot'] || - $ssl_redirect != $result['ssl_redirect'] || - $wwwserveralias != $result['wwwserveralias'] || - $iswildcarddomain != $result['iswildcarddomain'] || - $phpenabled != $result['phpenabled'] || - $openbasedir != $result['openbasedir'] || - $phpsettingid != $result['phpsettingid'] || - $mod_fcgid_starter != $result['mod_fcgid_starter'] || - $mod_fcgid_maxrequests != $result['mod_fcgid_maxrequests'] || - $specialsettings != $result['specialsettings'] || - $notryfiles != $result['notryfiles'] || - $aliasdomain != $result['aliasdomain'] || - $issubof != $result['ismainbutsubto'] || - $email_only != $result['email_only'] || - ($speciallogfile != $result['speciallogfile'] && $speciallogverified == '1') || - $letsencrypt != $result['letsencrypt'] || - $http2 != $result['http2'] || - $hsts_maxage != $result['hsts'] || - $hsts_sub != $result['hsts_sub'] || - $hsts_preload != $result['hsts_preload'] || - $ocsp_stapling != $result['ocsp_stapling'] - ) { - inserttask('1'); - } - - if ($speciallogfile != $result['speciallogfile'] && $speciallogverified != '1') { - $speciallogfile = $result['speciallogfile']; - } - - if ($isbinddomain != $result['isbinddomain'] || $zonefile != $result['zonefile'] || $dkim != $result['dkim']) { - inserttask('4'); - } - - if ($isemaildomain == '0' && $result['isemaildomain'] == '1') { - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_MAIL_USERS . "` WHERE `domainid` = :id - "); - Database::pexecute($del_stmt, array( - 'id' => $id - )); - - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_MAIL_VIRTUAL . "` WHERE `domainid` = :id - "); - Database::pexecute($del_stmt, array( - 'id' => $id - )); - $log->logAction(ADM_ACTION, LOG_NOTICE, "deleted domain #" . $id . " from mail-tables"); - } - - // check whether LE has been disabled, so we remove the certificate - if ($letsencrypt == '0' && $result['letsencrypt'] == '1') { - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` WHERE `domainid` = :id - "); - Database::pexecute($del_stmt, array( - 'id' => $id - )); - } - - $updatechildren = ''; - - if ($subcanemaildomain == '0' && $result['subcanemaildomain'] != '0') { - $updatechildren = ", `isemaildomain` = '0' "; - } elseif ($subcanemaildomain == '3' && $result['subcanemaildomain'] != '3') { - $updatechildren = ", `isemaildomain` = '1' "; - } - - if ($customerid != $result['customerid'] && Settings::Get('panel.allow_domain_change_customer') == '1') { - $upd_data = array( - 'customerid' => $customerid, - 'domainid' => $result['id'] - ); - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_MAIL_USERS . "` SET `customerid` = :customerid WHERE `domainid` = :domainid - "); - Database::pexecute($upd_stmt, $upd_data); - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_MAIL_VIRTUAL . "` SET `customerid` = :customerid WHERE `domainid` = :domainid - "); - Database::pexecute($upd_stmt, $upd_data); - $upd_data = array( - 'subdomains' => $subdomains, - 'emails' => $emails, - 'forwarders' => $email_forwarders, - 'accounts' => $email_accounts - ); - $upd_data['customerid'] = $customerid; - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET - `subdomains_used` = `subdomains_used` + :subdomains, - `emails_used` = `emails_used` + :emails, - `email_forwarders_used` = `email_forwarders_used` + :forwarders, - `email_accounts_used` = `email_accounts_used` + :accounts - WHERE `customerid` = :customerid - "); - Database::pexecute($upd_stmt, $upd_data); - - $upd_data['customerid'] = $result['customerid']; - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET - `subdomains_used` = `subdomains_used` - :subdomains, - `emails_used` = `emails_used` - :emails, - `email_forwarders_used` = `email_forwarders_used` - :forwarders, - `email_accounts_used` = `email_accounts_used` - :accounts - WHERE `customerid` = :customerid - "); - Database::pexecute($upd_stmt, $upd_data); - } - - if ($adminid != $result['adminid'] && Settings::Get('panel.allow_domain_change_admin') == '1') { - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_ADMINS . "` SET `domains_used` = `domains_used` + 1 WHERE `adminid` = :adminid - "); - Database::pexecute($upd_stmt, array( - 'adminid' => $adminid - )); - - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_ADMINS . "` SET `domains_used` = `domains_used` - 1 WHERE `adminid` = :adminid - "); - Database::pexecute($upd_stmt, array( - 'adminid' => $result['adminid'] - )); - } - - $_update_data = array(); - - $ssfs = isset($_POST['specialsettingsforsubdomains']) ? 1 : 0; - if ($ssfs == 1) { - $_update_data['specialsettings'] = $specialsettings; - $upd_specialsettings = ", `specialsettings` = :specialsettings "; - } else { - $upd_specialsettings = ''; - unset($_update_data['specialsettings']); - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `specialsettings`='' WHERE `parentdomainid` = :id - "); - Database::pexecute($upd_stmt, array( - 'id' => $id - )); - $log->logAction(ADM_ACTION, LOG_INFO, "removed specialsettings on all subdomains of domain #" . $id); - } - - $wwwserveralias = ($serveraliasoption == '1') ? '1' : '0'; - $iswildcarddomain = ($serveraliasoption == '0') ? '1' : '0'; - - $update_data = array(); - $update_data['customerid'] = $customerid; - $update_data['adminid'] = $adminid; - $update_data['documentroot'] = $documentroot; - $update_data['ssl_redirect'] = $ssl_redirect; - $update_data['aliasdomain'] = ($aliasdomain != 0 && $alias_check == 0) ? $aliasdomain : null; - $update_data['isbinddomain'] = $isbinddomain; - $update_data['isemaildomain'] = $isemaildomain; - $update_data['email_only'] = $email_only; - $update_data['subcanemaildomain'] = $subcanemaildomain; - $update_data['dkim'] = $dkim; - $update_data['caneditdomain'] = $caneditdomain; - $update_data['zonefile'] = $zonefile; - $update_data['wwwserveralias'] = $wwwserveralias; - $update_data['iswildcarddomain'] = $iswildcarddomain; - $update_data['phpenabled'] = $phpenabled; - $update_data['openbasedir'] = $openbasedir; - $update_data['speciallogfile'] = $speciallogfile; - $update_data['phpsettingid'] = $phpsettingid; - $update_data['mod_fcgid_starter'] = $mod_fcgid_starter; - $update_data['mod_fcgid_maxrequests'] = $mod_fcgid_maxrequests; - $update_data['specialsettings'] = $specialsettings; - $update_data['notryfiles'] = $notryfiles; - $update_data['registration_date'] = $registration_date; - $update_data['termination_date'] = $termination_date; - $update_data['ismainbutsubto'] = $issubof; - $update_data['letsencrypt'] = $letsencrypt; - $update_data['http2'] = $http2; - $update_data['hsts'] = $hsts_maxage; - $update_data['hsts_sub'] = $hsts_sub; - $update_data['hsts_preload'] = $hsts_preload; - $update_data['ocsp_stapling'] = $ocsp_stapling; - $update_data['id'] = $id; - - $update_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_DOMAINS . "` SET - `customerid` = :customerid, - `adminid` = :adminid, - `documentroot` = :documentroot, - `ssl_redirect` = :ssl_redirect, - `aliasdomain` = :aliasdomain, - `isbinddomain` = :isbinddomain, - `isemaildomain` = :isemaildomain, - `email_only` = :email_only, - `subcanemaildomain` = :subcanemaildomain, - `dkim` = :dkim, - `caneditdomain` = :caneditdomain, - `zonefile` = :zonefile, - `wwwserveralias` = :wwwserveralias, - `iswildcarddomain` = :iswildcarddomain, - `phpenabled` = :phpenabled, - `openbasedir` = :openbasedir, - `speciallogfile` = :speciallogfile, - `phpsettingid` = :phpsettingid, - `mod_fcgid_starter` = :mod_fcgid_starter, - `mod_fcgid_maxrequests` = :mod_fcgid_maxrequests, - `specialsettings` = :specialsettings, - `notryfiles` = :notryfiles, - `registration_date` = :registration_date, - `termination_date` = :termination_date, - `ismainbutsubto` = :ismainbutsubto, - `letsencrypt` = :letsencrypt, - `http2` = :http2, - `hsts` = :hsts, - `hsts_sub` = :hsts_sub, - `hsts_preload` = :hsts_preload, - `ocsp_stapling` = :ocsp_stapling - WHERE `id` = :id - "); - Database::pexecute($update_stmt, $update_data); - - $_update_data['customerid'] = $customerid; - $_update_data['adminid'] = $adminid; - $_update_data['phpenabled'] = $phpenabled; - $_update_data['openbasedir'] = $openbasedir; - $_update_data['mod_fcgid_starter'] = $mod_fcgid_starter; - $_update_data['mod_fcgid_maxrequests'] = $mod_fcgid_maxrequests; - $_update_data['parentdomainid'] = $id; - - // if php config is to be set for all subdomains, check here - $update_phpconfig = ''; - $phpfs = isset($_POST['phpsettingsforsubdomains']) ? 1 : 0; - if ($phpfs == 1) { - $_update_data['phpsettingid'] = $phpsettingid; - $update_phpconfig = ", `phpsettingid` = :phpsettingid"; - } - - // if we have no more ssl-ip's for this domain, - // all its subdomains must have "ssl-redirect = 0" - // and disable let's encrypt - $update_sslredirect = ''; - if (count($ssl_ipandports) == 1 && $ssl_ipandports[0] == - 1) { - $update_sslredirect = ", `ssl_redirect` = '0', `letsencrypt` = '0' "; - } - - $_update_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_DOMAINS . "` SET - `customerid` = :customerid, - `adminid` = :adminid, - `phpenabled` = :phpenabled, - `openbasedir` = :openbasedir, - `mod_fcgid_starter` = :mod_fcgid_starter, - `mod_fcgid_maxrequests` = :mod_fcgid_maxrequests - " . $update_phpconfig . $upd_specialsettings . $updatechildren . $update_sslredirect . " - WHERE `parentdomainid` = :parentdomainid - "); - Database::pexecute($_update_stmt, $_update_data); - - // FIXME check how many we got and if the amount of assigned IP's - // has changed so we can insert a config-rebuild task if only - // the ip's of this domain were changed - // -> for now, always insert a rebuild-task - inserttask('1'); - - // Cleanup domain <-> ip mapping - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :id - "); - Database::pexecute($del_stmt, array( - 'id' => $id - )); - - $ins_stmt = Database::prepare(" - INSERT INTO `" . TABLE_DOMAINTOIP . "` SET `id_domain` = :domainid, `id_ipandports` = :ipportid - "); - - foreach ($ipandports as $ipportid) { - Database::pexecute($ins_stmt, array( - 'domainid' => $id, - 'ipportid' => $ipportid - )); - } - foreach ($ssl_ipandports as $ssl_ipportid) { - if ($ssl_ipportid > 0) { - Database::pexecute($ins_stmt, array( - 'domainid' => $id, - 'ipportid' => $ssl_ipportid - )); - } - } - - // Cleanup domain <-> ip mapping for subdomains - $domainidsresult_stmt = Database::prepare(" - SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `parentdomainid` = :id - "); - Database::pexecute($domainidsresult_stmt, array( - 'id' => $id - )); - - while ($row = $domainidsresult_stmt->fetch(PDO::FETCH_ASSOC)) { - - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :rowid - "); - Database::pexecute($del_stmt, array( - 'rowid' => $row['id'] - )); - - $ins_stmt = Database::prepare(" - INSERT INTO `" . TABLE_DOMAINTOIP . "` SET - `id_domain` = :rowid, - `id_ipandports` = :ipportid - "); - - foreach ($ipandports as $ipportid) { - Database::pexecute($ins_stmt, array( - 'rowid' => $row['id'], - 'ipportid' => $ipportid - )); - } - foreach ($ssl_ipandports as $ssl_ipportid) { - if ($ssl_ipportid > 0) { - Database::pexecute($ins_stmt, array( - 'rowid' => $row['id'], - 'ipportid' => $ssl_ipportid - )); - } - } - } - if ($result['aliasdomain'] != $aliasdomain) { - // trigger when domain id for alias destination has changed: both for old and new destination - triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $log); - triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $log); - } else - if ($result['wwwserveralias'] != $wwwserveralias || $result['letsencrypt'] != $letsencrypt) { - // or when wwwserveralias or letsencrypt was changed - triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $log); - } - - $log->logAction(ADM_ACTION, LOG_INFO, "edited domain #" . $id); + try { + Domains::getLocal($userinfo, $_POST)->update(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } redirectTo($filename, array( 'page' => $page, 's' => $s )); } else { - + if (Settings::Get('panel.allow_domain_change_customer') == '1') { $customers = ''; $result_customers_stmt = Database::prepare(" @@ -2059,7 +445,7 @@ if ($page == 'domains' || $page == 'overview') { $params['adminid'] = $userinfo['adminid']; } Database::pexecute($result_customers_stmt, $params); - + while ($row_customer = $result_customers_stmt->fetch(PDO::FETCH_ASSOC)) { $customers .= makeoption(getCorrectFullUserDetails($row_customer) . ' (' . $row_customer['loginname'] . ')', $row_customer['customerid'], $result['customerid']); } @@ -2073,10 +459,10 @@ if ($page == 'domains' || $page == 'overview') { )); $result['customername'] = getCorrectFullUserDetails($customer) . ' (' . $customer['loginname'] . ')'; } - + if ($userinfo['customers_see_all'] == '1') { if (Settings::Get('panel.allow_domain_change_admin') == '1') { - + $admins = ''; $result_admins_stmt = Database::prepare(" SELECT `adminid`, `loginname`, `name` FROM `" . TABLE_PANEL_ADMINS . "` @@ -2085,7 +471,7 @@ if ($page == 'domains' || $page == 'overview') { Database::pexecute($result_admins_stmt, array( 'adminid' => $result['adminid'] )); - + while ($row_admin = $result_admins_stmt->fetch(PDO::FETCH_ASSOC)) { $admins .= makeoption(getCorrectFullUserDetails($row_admin) . ' (' . $row_admin['loginname'] . ')', $row_admin['adminid'], $result['adminid']); } @@ -2099,10 +485,10 @@ if ($page == 'domains' || $page == 'overview') { $result['adminname'] = getCorrectFullUserDetails($admin) . ' (' . $admin['loginname'] . ')'; } } - + $result['domain'] = $idna_convert->decode($result['domain']); $domains = makeoption($lng['domains']['noaliasdomain'], 0, null, true); - + $result_domains_stmt = Database::prepare(" SELECT `d`.`id`, `d`.`domain` FROM `" . TABLE_PANEL_DOMAINS . "` `d`, `" . TABLE_PANEL_CUSTOMERS . "` `c` WHERE `d`.`aliasdomain` IS NULL AND `d`.`parentdomainid` = '0' AND `d`.`id` <> :id @@ -2113,11 +499,11 @@ if ($page == 'domains' || $page == 'overview') { 'id' => $result['id'], 'customerid' => $result['customerid'] )); - + while ($row_domain = $result_domains_stmt->fetch(PDO::FETCH_ASSOC)) { $domains .= makeoption($idna_convert->decode($row_domain['domain']), $row_domain['id'], $result['aliasdomain']); } - + $subtodomains = makeoption($lng['domains']['nosubtomaindomain'], 0, null, true); $result_domains_stmt = Database::prepare(" SELECT `d`.`id`, `d`.`domain` FROM `" . TABLE_PANEL_DOMAINS . "` `d`, `" . TABLE_PANEL_CUSTOMERS . "` `c` @@ -2132,11 +518,11 @@ if ($page == 'domains' || $page == 'overview') { $params['adminid'] = $userinfo['adminid']; } Database::pexecute($result_domains_stmt, $params); - + while ($row_domain = $result_domains_stmt->fetch(PDO::FETCH_ASSOC)) { $subtodomains .= makeoption($idna_convert->decode($row_domain['domain']), $row_domain['id'], $result['ismainbutsubto']); } - + if ($userinfo['ip'] == "-1") { $result_ipsandports_stmt = Database::query(" SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `ssl`='0' ORDER BY `ip`, `port` ASC @@ -2151,14 +537,14 @@ if ($page == 'domains' || $page == 'overview') { $admin_ip = Database::pexecute_first($admin_ip_stmt, array( 'ipid' => $userinfo['ip'] )); - + $result_ipsandports_stmt = Database::prepare(" SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `ssl`='0' AND `ip` = :ipid ORDER BY `ip`, `port` ASC "); Database::pexecute($result_ipsandports_stmt, array( 'ipid' => $admin_ip['ip'] )); - + $result_ssl_ipsandports_stmt = Database::prepare(" SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `ssl`='1' AND `ip` = :ipid ORDER BY `ip`, `port` ASC "); @@ -2166,7 +552,7 @@ if ($page == 'domains' || $page == 'overview') { 'ipid' => $admin_ip['ip'] )); } - + $ipsandports = array(); while ($row_ipandport = $result_ipsandports_stmt->fetch(PDO::FETCH_ASSOC)) { if (filter_var($row_ipandport['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { @@ -2177,7 +563,7 @@ if ($page == 'domains' || $page == 'overview') { 'value' => $row_ipandport['id'] ); } - + $ssl_ipsandports = array(); while ($row_ssl_ipandport = $result_ssl_ipsandports_stmt->fetch(PDO::FETCH_ASSOC)) { if (filter_var($row_ssl_ipandport['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { @@ -2188,7 +574,7 @@ if ($page == 'domains' || $page == 'overview') { 'value' => $row_ssl_ipandport['id'] ); } - + // create serveralias options $serveraliasoptions = ""; $_value = '2'; @@ -2198,22 +584,22 @@ if ($page == 'domains' || $page == 'overview') { } elseif ($result['wwwserveralias'] == '1') { $_value = '1'; } - + // Fudge the result for ssl_redirect to hide the Let's Encrypt steps $result['temporary_ssl_redirect'] = $result['ssl_redirect']; $result['ssl_redirect'] = ($result['ssl_redirect'] == 0 ? 0 : 1); - + $serveraliasoptions .= makeoption($lng['domains']['serveraliasoption_wildcard'], '0', $_value, true, true); $serveraliasoptions .= makeoption($lng['domains']['serveraliasoption_www'], '1', $_value, true, true); $serveraliasoptions .= makeoption($lng['domains']['serveraliasoption_none'], '2', $_value, true, true); - + $subcanemaildomain = makeoption($lng['admin']['subcanemaildomain']['never'], '0', $result['subcanemaildomain'], true, true); $subcanemaildomain .= makeoption($lng['admin']['subcanemaildomain']['choosableno'], '1', $result['subcanemaildomain'], true, true); $subcanemaildomain .= makeoption($lng['admin']['subcanemaildomain']['choosableyes'], '2', $result['subcanemaildomain'], true, true); $subcanemaildomain .= makeoption($lng['admin']['subcanemaildomain']['always'], '3', $result['subcanemaildomain'], true, true); $speciallogfile = ($result['speciallogfile'] == 1 ? $lng['panel']['yes'] : $lng['panel']['no']); $result['add_date'] = date('Y-m-d', $result['add_date']); - + $phpconfigs = ''; $phpconfigs_result_stmt = Database::query(" SELECT c.*, fc.description as interpreter @@ -2221,31 +607,31 @@ if ($page == 'domains' || $page == 'overview') { LEFT JOIN `" . TABLE_PANEL_FPMDAEMONS . "` fc ON fc.id = c.fpmsettingid "); $c_allowed_configs = getCustomerDetail($result['customerid'], 'allowed_phpconfigs'); - if (!empty($c_allowed_configs)) { + if (! empty($c_allowed_configs)) { $c_allowed_configs = json_decode($c_allowed_configs, true); } else { $c_allowed_configs = array(); } - + while ($phpconfigs_row = $phpconfigs_result_stmt->fetch(PDO::FETCH_ASSOC)) { - $disabled = !empty($c_allowed_configs) && !in_array($phpconfigs_row['id'], $c_allowed_configs); + $disabled = ! empty($c_allowed_configs) && ! in_array($phpconfigs_row['id'], $c_allowed_configs); if ((int) Settings::Get('phpfpm.enabled') == 1) { - $phpconfigs .= makeoption($phpconfigs_row['description'] . " [".$phpconfigs_row['interpreter']."]", $phpconfigs_row['id'], $result['phpsettingid'], true, true, null, $disabled); + $phpconfigs .= makeoption($phpconfigs_row['description'] . " [" . $phpconfigs_row['interpreter'] . "]", $phpconfigs_row['id'], $result['phpsettingid'], true, true, null, $disabled); } else { $phpconfigs .= makeoption($phpconfigs_row['description'], $phpconfigs_row['id'], $result['phpsettingid'], true, true, null, $disabled); } } - + $result = htmlentities_array($result); - + $domain_edit_data = include_once dirname(__FILE__) . '/lib/formfields/admin/domains/formfield.domains_edit.php'; $domain_edit_form = htmlform::genHTMLForm($domain_edit_data); - + $title = $domain_edit_data['domain_edit']['title']; $image = $domain_edit_data['domain_edit']['image']; - + $speciallogwarning = sprintf($lng['admin']['speciallogwarning'], $lng['admin']['delete_statistics']); - + eval("echo \"" . getTemplate("domains/domains_edit") . "\";"); } } @@ -2253,36 +639,35 @@ if ($page == 'domains' || $page == 'overview') { $customerid = intval($_POST['customerid']); $allowed_phpconfigs = getCustomerDetail($customerid, 'allowed_phpconfigs'); - echo !empty($allowed_phpconfigs) ? $allowed_phpconfigs : json_encode(array()); - exit; - + echo ! empty($allowed_phpconfigs) ? $allowed_phpconfigs : json_encode(array()); + exit(); } elseif ($action == 'import') { - + if (isset($_POST['send']) && $_POST['send'] == 'send') { - + $customerid = intval($_POST['customerid']); $separator = validate($_POST['separator'], 'separator'); $offset = (int) validate($_POST['offset'], 'offset', "/[0-9]/i"); - + $file_name = $_FILES['file']['tmp_name']; - + $result = array(); - + try { $bulk = new DomainBulkAction($file_name, $customerid); $result = $bulk->doImport($separator, $offset); } catch (Exception $e) { standard_error('domain_import_error', $e->getMessage()); } - + // @FIXME find a way to display $result['notice'] here somehow, // as it might be important if you've reached your maximum allocation of domains - + // update customer/admin counters updateCounters(false); inserttask('1'); inserttask('4'); - + $result_str = $result['imported'] . ' / ' . $result['all']; standard_success('domain_import_successfully', $result_str, array( 'filename' => $filename, @@ -2299,47 +684,45 @@ if ($page == 'domains' || $page == 'overview') { $params['adminid'] = $userinfo['adminid']; } Database::pexecute($result_customers_stmt, $params); - + while ($row_customer = $result_customers_stmt->fetch(PDO::FETCH_ASSOC)) { $customers .= makeoption(getCorrectFullUserDetails($row_customer) . ' (' . $row_customer['loginname'] . ')', $row_customer['customerid']); } - + $domain_import_data = include_once dirname(__FILE__) . '/lib/formfields/admin/domains/formfield.domains_import.php'; $domain_import_form = htmlform::genHTMLForm($domain_import_data); - + $title = $domain_import_data['domain_import']['title']; $image = $domain_import_data['domain_import']['image']; - + eval("echo \"" . getTemplate("domains/domains_import") . "\";"); } } } elseif ($page == 'domaindnseditor' && Settings::Get('system.dnsenabled') == '1') { - - require_once __DIR__.'/dns_editor.php'; - + + require_once __DIR__ . '/dns_editor.php'; } elseif ($page == 'sslcertificates') { - - require_once __DIR__.'/ssl_certificates.php'; - + + require_once __DIR__ . '/ssl_certificates.php'; } function formatDomainEntry(&$row, &$idna_convert) { $row['domain'] = $idna_convert->decode($row['domain']); $row['aliasdomain'] = $idna_convert->decode($row['aliasdomain']); - + $resultips_stmt = Database::prepare(" SELECT `ips`.* FROM `" . TABLE_DOMAINTOIP . "` AS `dti`, `" . TABLE_PANEL_IPSANDPORTS . "` AS `ips` WHERE `dti`.`id_ipandports` = `ips`.`id` AND `dti`.`id_domain` = :domainid "); - + Database::pexecute($resultips_stmt, array( 'domainid' => $row['id'] )); - + $row['ipandport'] = ''; while ($rowip = $resultips_stmt->fetch(PDO::FETCH_ASSOC)) { - + if (filter_var($rowip['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { $row['ipandport'] .= '[' . $rowip['ip'] . ']:' . $rowip['port'] . "\n"; } else { @@ -2348,12 +731,12 @@ function formatDomainEntry(&$row, &$idna_convert) } $row['ipandport'] = substr($row['ipandport'], 0, - 1); $row['termination_date'] = str_replace("0000-00-00", "", $row['termination_date']); - + $row['termination_css'] = ""; if ($row['termination_date'] != "") { $cdate = strtotime($row['termination_date'] . " 23:59:59"); $today = time(); - + if ($cdate < $today) { $row['termination_css'] = 'domain-expired'; } else { diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php new file mode 100644 index 00000000..1f44d4f6 --- /dev/null +++ b/lib/classes/api/commands/class.Domains.php @@ -0,0 +1,1635 @@ +isAdmin()) { + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] list domains"); + $result_stmt = Database::prepare(" + SELECT + `d`.*, `c`.`loginname`, `c`.`deactivated`, `c`.`name`, `c`.`firstname`, `c`.`company`, `c`.`standardsubdomain`, + `ad`.`id` AS `aliasdomainid`, `ad`.`domain` AS `aliasdomain` + FROM `" . TABLE_PANEL_DOMAINS . "` `d` + LEFT JOIN `" . TABLE_PANEL_CUSTOMERS . "` `c` USING(`customerid`) + LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` `ad` ON `d`.`aliasdomain`=`ad`.`id` + WHERE `d`.`parentdomainid`='0' " . ($this->getUserDetail('customers_see_all') ? '' : " AND `d`.`adminid` = :adminid ")); + $params = array(); + if ($this->getUserDetail('customers_see_all') == '0') { + $params['adminid'] = $this->getUserDetail('adminid'); + } + Database::pexecute($result_stmt, $params); + $result = array(); + while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { + $result[] = $row; + } + return $this->response(200, "successfull", array( + 'count' => count($result), + 'list' => $result + )); + } + throw new Exception("Not allowed to execute given command.", 403); + } + + public function get() + { + if ($this->isAdmin()) { + $id = $this->getParam('id'); + $no_std_subdomain = $this->getParam('no_std_subdomain', false); + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] get domain #" . $id); + $result_stmt = Database::prepare(" + SELECT `d`.*, `c`.`customerid` + FROM `" . TABLE_PANEL_DOMAINS . "` `d` + LEFT JOIN `" . TABLE_PANEL_CUSTOMERS . "` `c` USING(`customerid`) + WHERE `d`.`parentdomainid` = '0' + AND `d`.`id` = :id" . ($no_std_subdomain ? ' AND `d.`id` <> `c`.`standardsubdomain`' : '') . ($this->getUserDetail('customers_see_all') ? '' : " AND `d`.`adminid` = :adminid")); + $params = array( + 'id' => $id + ); + if ($this->getUserDetail('customers_see_all') == '0') { + $params['adminid'] = $this->getUserDetail('adminid'); + } + $result = Database::pexecute_first($result_stmt, $params, true, true); + if ($result) { + return $this->response(200, "successfull", $result); + } + throw new Exception("Domain with id #" . $id . " could not be found"); + } + throw new Exception("Not allowed to execute given command.", 403); + } + + public function add() + { + if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { + if ($this->getUserDetail('domains_used') < $this->getUserDetail('domains') || $this->getUserDetail('domains') == '-1') { + + if ($this->getParam('domain') == Settings::Get('system.hostname')) { + standard_error('admin_domain_emailsystemhostname', '', true); + } + + if (substr($this->getParam('domain'), 0, 4) == 'xn--') { + standard_error('domain_nopunycode', '', true); + } + + $idna_convert = new idna_convert_wrapper(); + $domain = $idna_convert->encode(preg_replace(array( + '/\:(\d)+$/', + '/^https?\:\/\//' + ), '', validate($this->getParam('domain'), 'domain'))); + + // Check whether domain validation is enabled and if, validate the domain + if (Settings::Get('system.validate_domain') && ! validateDomain($domain)) { + standard_error(array( + 'stringiswrong', + 'mydomain' + ), '', true); + } + + $subcanemaildomain = $this->getParam('subcanemaildomain', 0); + $isemaildomain = $this->getParam('isemaildomain', 0); + $email_only = $this->getParam('email_only', 0); + $serveraliasoption = $this->getParam('selectserveralias', 0); + $speciallogfile = $this->getParam('speciallogfile', 0); + + $aliasdomain = intval($this->getParam('alias')); + $issubof = intval($this->getParam('issubof')); + $customerid = intval($this->getParam('customerid')); + $customer_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_CUSTOMERS . "` + WHERE `customerid` = :customerid " . ($this->getUserDetail('customers_see_all') ? '' : " AND `adminid` = :adminid")); + $params = array( + 'customerid' => $customerid + ); + if ($this->getUserDetail('customers_see_all') == '0') { + $params['adminid'] = $this->getUserDetail('adminid'); + } + $customer = Database::pexecute_first($customer_stmt, $params, true, true); + + if (empty($customer) || $customer['customerid'] != $customerid) { + standard_error('customerdoesntexist', '', true); + } + + if ($this->getUserDetail('customers_see_all') == '1') { + + $adminid = intval($this->getParam('adminid')); + $admin_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_ADMINS . "` + WHERE `adminid` = :adminid AND (`domains_used` < `domains` OR `domains` = '-1')"); + $admin = Database::pexecute_first($admin_stmt, array( + 'adminid' => $adminid + ), true, true); + + if (empty($admin) || $admin['adminid'] != $adminid) { + standard_error('admindoesntexist', '', true); + } + } else { + $adminid = $this->getUserDetail('adminid'); + $admin = $this->getUserData(); + } + + // set default path if admin/reseller has "change_serversettings == false" but we still + // need to respect the documentroot_use_default_value - setting + $path_suffix = ''; + if (Settings::Get('system.documentroot_use_default_value') == 1) { + $path_suffix = '/' . $domain; + } + $documentroot = makeCorrectDir($customer['documentroot'] . $path_suffix); + + $registration_date = trim($this->getParam('registration_date', '')); + $registration_date = validate($registration_date, 'registration_date', '/^(19|20)\d\d[-](0[1-9]|1[012])[-](0[1-9]|[12][0-9]|3[01])$/', '', array( + '0000-00-00', + '0', + '' + ), true); + if ($registration_date == '0000-00-00') { + $registration_date = null; + } + + $termination_date = trim($this->getParam('termination_date', '')); + $termination_date = validate($termination_date, 'termination_date', '/^(19|20)\d\d[-](0[1-9]|1[012])[-](0[1-9]|[12][0-9]|3[01])$/', '', array( + '0000-00-00', + '0', + '' + ), true); + if ($termination_date == '0000-00-00') { + $termination_date = null; + } + + if ($this->getUserDetail('change_serversettings') == '1') { + + $caneditdomain = $this->getParam('caneditdomain', 0); + + $isbinddomain = '0'; + $zonefile = ''; + if (Settings::Get('system.bind_enable') == '1') { + $isbinddomain = $this->getParam('isbinddomain', 0); + $zonefile = validate($this->getParam('zonefile', ''), 'zonefile', '', '', array(), true); + } + + $dkim = intval($this->getParam('dkim', 0)); + + $specialsettings = validate(str_replace("\r\n", "\n", $this->getParam('specialsettings', '')), 'specialsettings', '/^[^\0]*$/', '', array(), true); + $notryfiles = $this->getParam('notryfiles', 0); + validate($this->getParam('documentroot', ''), 'documentroot', '', '', array(), true); + + // If path is empty and 'Use domain name as default value for DocumentRoot path' is enabled in settings, + // set default path to subdomain or domain name + if ($this->getParam('documentroot', '') != '') { + if (substr($this->getParam('documentroot'), 0, 1) != '/' && ! preg_match('/^https?\:\/\//', $this->getParam('documentroot'))) { + $documentroot .= '/' . $this->getParam('documentroot'); + } else { + $documentroot = $this->getParam('documentroot'); + } + } elseif ($this->getParam('documentroot', '') == '' && Settings::Get('system.documentroot_use_default_value') == 1) { + $documentroot = makeCorrectDir($customer['documentroot'] . '/' . $domain); + } + } else { + $isbinddomain = '0'; + if (Settings::Get('system.bind_enable') == '1') { + $isbinddomain = '1'; + } + $caneditdomain = '1'; + $zonefile = ''; + $dkim = '0'; + $specialsettings = ''; + $notryfiles = '0'; + } + + if ($this->getUserDetail('caneditphpsettings') == '1' || $this->getUserDetail('change_serversettings') == '1') { + + $phpenabled = $this->getParam('phpenabled', 0); + $openbasedir = $this->getParam('openbasedir', 0); + + if ((int) Settings::Get('system.mod_fcgid') == 1 || (int) Settings::Get('phpfpm.enabled') == 1) { + $phpsettingid = $this->getParam('phpsettingid', 1); + $phpsettingid_check_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_PHPCONFIGS . "` + WHERE `id` = :phpsettingid"); + $phpsettingid_check = Database::pexecute_first($phpsettingid_check_stmt, array( + 'phpsettingid' => $phpsettingid + ), true, true); + + if (! isset($phpsettingid_check['id']) || $phpsettingid_check['id'] == '0' || $phpsettingid_check['id'] != $phpsettingid) { + standard_error('phpsettingidwrong', '', true); + } + + if ((int) Settings::Get('system.mod_fcgid') == 1) { + $mod_fcgid_starter = validate($this->getParam('mod_fcgid_starter', - 1), 'mod_fcgid_starter', '/^[0-9]*$/', '', array( + '-1', + '' + ), true); + $mod_fcgid_maxrequests = validate($this->getParam('mod_fcgid_maxrequests', - 1), 'mod_fcgid_maxrequests', '/^[0-9]*$/', '', array( + '-1', + '' + ), true); + } else { + $mod_fcgid_starter = '-1'; + $mod_fcgid_maxrequests = '-1'; + } + } else { + + if ((int) Settings::Get('phpfpm.enabled') == 1) { + $phpsettingid = Settings::Get('phpfpm.defaultini'); + } else { + $phpsettingid = Settings::Get('system.mod_fcgid_defaultini'); + } + $mod_fcgid_starter = '-1'; + $mod_fcgid_maxrequests = '-1'; + } + } else { + + $phpenabled = '1'; + $openbasedir = '1'; + + if ((int) Settings::Get('phpfpm.enabled') == 1) { + $phpsettingid = Settings::Get('phpfpm.defaultini'); + } else { + $phpsettingid = Settings::Get('system.mod_fcgid_defaultini'); + } + $mod_fcgid_starter = '-1'; + $mod_fcgid_maxrequests = '-1'; + } + + if ($this->getUserDetail('ip') != "-1") { + $admin_ip_stmt = Database::prepare(" + SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` + WHERE `id` = :id ORDER BY `ip`, `port` ASC"); + $admin_ip = Database::pexecute_first($admin_ip_stmt, array( + 'id' => $this->getUserDetail('ip') + ), true, true); + $additional_ip_condition = " AND `ip` = :adminip "; + $aip_param = array( + 'adminip' => $admin_ip['ip'] + ); + } else { + $additional_ip_condition = ''; + $aip_param = array(); + } + + $ipandports = array(); + if (! empty($this->getParam('ipandport')) && ! is_array($this->getParam('ipandport'))) { + $this->updateParam('ipandport', unserialize($this->getParam('ipandport'))); + } + + if (! empty($this->getParam('ipandport')) && is_array($this->getParam('ipandport'))) { + foreach ($this->getParam('ipandport') as $ipandport) { + $ipandport = intval($ipandport); + $ipandport_check_stmt = Database::prepare(" + SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` + WHERE `id` = :id " . $additional_ip_condition); + $ip_params = null; + $ip_params = array_merge(array( + 'id' => $ipandport + ), $aip_param); + $ipandport_check = Database::pexecute_first($ipandport_check_stmt, $ip_params, true, true); + + if (! isset($ipandport_check['id']) || $ipandport_check['id'] == '0' || $ipandport_check['id'] != $ipandport) { + standard_error('ipportdoesntexist', '', true); + } else { + $ipandports[] = $ipandport; + } + } + } + + if (Settings::Get('system.use_ssl') == "1" && ! empty($this->getParam('ssl_ipandport'))) { + $ssl_redirect = $this->getParam('ssl_redirect', 0); + $letsencrypt = $this->getParam('letsencrypt', 0); + + $ssl_ipandports = array(); + if (! empty($this->getParam('ssl_ipandport')) && ! is_array($this->getParam('ssl_ipandport'))) { + $this->updateParam('ssl_ipandport', unserialize($this->getParam('ssl_ipandport'))); + } + + // Verify SSL-Ports + if (! empty($this->getParam('ssl_ipandport')) && is_array($this->getParam('ssl_ipandport'))) { + foreach ($this->getParam('ssl_ipandport') as $ssl_ipandport) { + if (trim($ssl_ipandport) == "") { + continue; + } + // fix if no ssl-ip/port is checked + if (trim($ssl_ipandport) < 1) { + continue; + } + $ssl_ipandport = intval($ssl_ipandport); + $ssl_ipandport_check_stmt = Database::prepare(" + SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` + WHERE `id` = :id " . $additional_ip_condition); + $ip_params = null; + $ip_params = array_merge(array( + 'id' => $ssl_ipandport + ), $aip_param); + $ssl_ipandport_check = Database::pexecute_first($ssl_ipandport_check_stmt, $ip_params, true, true); + + if (! isset($ssl_ipandport_check['id']) || $ssl_ipandport_check['id'] == '0' || $ssl_ipandport_check['id'] != $ssl_ipandport) { + standard_error('ipportdoesntexist', '', true); + } else { + $ssl_ipandports[] = $ssl_ipandport; + } + } + + $http2 = $this->getParam('http2', 0); + // HSTS + $hsts_maxage = $this->getParam('hsts_maxage', 0); + $hsts_sub = $this->getParam('hsts_sub', 0); + $hsts_preload = $this->getParam('hsts_preload', 0); + // OCSP stapling + $ocsp_stapling = $this->getParam('ocsp_stapling', 0); + } else { + $ssl_redirect = 0; + $letsencrypt = 0; + $http2 = 0; + // we need this for the serialize + // if ssl is disabled or no ssl-ip/port exists + $ssl_ipandports[] = - 1; + + // HSTS + $hsts_maxage = 0; + $hsts_sub = 0; + $hsts_preload = 0; + + // OCSP stapling + $ocsp_stapling = 0; + } + } else { + $ssl_redirect = 0; + $letsencrypt = 0; + $http2 = 0; + // we need this for the serialize + // if ssl is disabled or no ssl-ip/port exists + $ssl_ipandports[] = - 1; + + // HSTS + $hsts_maxage = 0; + $hsts_sub = 0; + $hsts_preload = 0; + + // OCSP stapling + $ocsp_stapling = 0; + } + + // We can't enable let's encrypt for wildcard - domains if using acme-v1 + if ($serveraliasoption == '0' && $letsencrypt == '1' && Settings::Get('system.leapiversion') == '1') { + standard_error('nowildcardwithletsencrypt', '', true); + } + // if using acme-v2 we cannot issue wildcard-certificates + // because they currently only support the dns-01 challenge + if ($serveraliasoption == '0' && $letsencrypt == '1' && Settings::Get('system.leapiversion') == '2') { + standard_error('nowildcardwithletsencryptv2', '', true); + } + + // Temporarily deactivate ssl_redirect until Let's Encrypt certificate was generated + if ($ssl_redirect > 0 && $letsencrypt == 1) { + $ssl_redirect = 2; + } + + if (! preg_match('/^https?\:\/\//', $documentroot)) { + if (strstr($documentroot, ":") !== false) { + standard_error('pathmaynotcontaincolon', '', true); + } else { + $documentroot = makeCorrectDir($documentroot); + } + } + + $domain_check_stmt = Database::prepare(" + SELECT `id`, `domain` FROM `" . TABLE_PANEL_DOMAINS . "` + WHERE `domain` = :domain"); + $domain_check = Database::pexecute_first($domain_check_stmt, array( + 'domain' => strtolower($domain) + ), true, true); + $aliasdomain_check = array( + 'id' => 0 + ); + + if ($aliasdomain != 0) { + // Overwrite given ipandports with these of the "main" domain + $ipandports = array(); + $ssl_ipandports = array(); + $origipresult_stmt = Database::prepare(" + SELECT `id_ipandports` FROM `" . TABLE_DOMAINTOIP . "` + WHERE `id_domain` = :id"); + Database::pexecute($origipresult_stmt, array( + 'id' => $aliasdomain + ), true, true); + $ipdata_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :ipid"); + while ($origip = $origipresult_stmt->fetch(PDO::FETCH_ASSOC)) { + $_origip_tmp = Database::pexecute_first($ipdata_stmt, array( + 'ipid' => $origip['id_ipandports'] + ), true, true); + if ($_origip_tmp['ssl'] == 0) { + $ipandports[] = $origip['id_ipandports']; + } else { + $ssl_ipandports[] = $origip['id_ipandports']; + } + } + + if (count($ssl_ipandports) == 0) { + // we need this for the serialize + // if ssl is disabled or no ssl-ip/port exists + $ssl_ipandports[] = - 1; + } + + $aliasdomain_check_stmt = Database::prepare(" + SELECT `d`.`id` FROM `" . TABLE_PANEL_DOMAINS . "` `d`, `" . TABLE_PANEL_CUSTOMERS . "` `c` + WHERE `d`.`customerid` = :customerid + AND `d`.`aliasdomain` IS NULL AND `d`.`id` <> `c`.`standardsubdomain` + AND `c`.`customerid` = :customerid + AND `d`.`id` = :aliasdomainid"); + $alias_params = array( + 'customerid' => $customerid, + 'aliasdomainid' => $aliasdomain + ); + $aliasdomain_check = Database::pexecute_first($aliasdomain_check_stmt, $alias_params, true, true); + } + + if (count($ipandports) == 0) { + standard_error('noipportgiven', '', true); + } + + if ($phpenabled != '1') { + $phpenabled = '0'; + } + + if ($openbasedir != '1') { + $openbasedir = '0'; + } + + if ($speciallogfile != '1') { + $speciallogfile = '0'; + } + + if ($isbinddomain != '1') { + $isbinddomain = '0'; + } + + if ($isemaildomain != '1') { + $isemaildomain = '0'; + } + + if ($email_only == '1') { + $isemaildomain = '1'; + } else { + $email_only = '0'; + } + + if ($subcanemaildomain != '1' && $subcanemaildomain != '2' && $subcanemaildomain != '3') { + $subcanemaildomain = '0'; + } + + if ($dkim != '1') { + $dkim = '0'; + } + + if ($serveraliasoption != '1' && $serveraliasoption != '2') { + $serveraliasoption = '0'; + } + + if ($caneditdomain != '1') { + $caneditdomain = '0'; + } + + if ($issubof <= '0') { + $issubof = '0'; + } + + if ($domain == '') { + standard_error(array( + 'stringisempty', + 'mydomain' + ), '', true); + } elseif ($documentroot == '') { + standard_error(array( + 'stringisempty', + 'mydocumentroot' + ), '', true); + } elseif ($customerid == 0) { + standard_error('adduserfirst', '', true); + } elseif (strtolower($domain_check['domain']) == strtolower($domain)) { + standard_error('domainalreadyexists', $idna_convert->decode($domain), true); + } elseif ($aliasdomain_check['id'] != $aliasdomain) { + standard_error('domainisaliasorothercustomer', '', true); + } else { + + /** + * + * @todo how to handle security questions now? + * + * $params = array( + * 'page' => $page, + * 'action' => $action, + * 'domain' => $domain, + * 'customerid' => $customerid, + * 'adminid' => $adminid, + * 'documentroot' => $documentroot, + * 'alias' => $aliasdomain, + * 'isbinddomain' => $isbinddomain, + * 'isemaildomain' => $isemaildomain, + * 'email_only' => $email_only, + * 'subcanemaildomain' => $subcanemaildomain, + * 'caneditdomain' => $caneditdomain, + * 'zonefile' => $zonefile, + * 'dkim' => $dkim, + * 'speciallogfile' => $speciallogfile, + * 'selectserveralias' => $serveraliasoption, + * 'ipandport' => serialize($ipandports), + * 'ssl_redirect' => $ssl_redirect, + * 'ssl_ipandport' => serialize($ssl_ipandports), + * 'phpenabled' => $phpenabled, + * 'openbasedir' => $openbasedir, + * 'phpsettingid' => $phpsettingid, + * 'mod_fcgid_starter' => $mod_fcgid_starter, + * 'mod_fcgid_maxrequests' => $mod_fcgid_maxrequests, + * 'specialsettings' => $specialsettings, + * 'notryfiles' => $notryfiles, + * 'registration_date' => $registration_date, + * 'termination_date' => $termination_date, + * 'issubof' => $issubof, + * 'letsencrypt' => $letsencrypt, + * 'http2' => $http2, + * 'hsts_maxage' => $hsts_maxage, + * 'hsts_sub' => $hsts_sub, + * 'hsts_preload' => $hsts_preload, + * 'ocsp_stapling' => $ocsp_stapling + * ); + * + * $security_questions = array( + * 'reallydisablesecuritysetting' => ($openbasedir == '0' && $userinfo['change_serversettings'] == '1'), + * 'reallydocrootoutofcustomerroot' => (substr($documentroot, 0, strlen($customer['documentroot'])) != $customer['documentroot'] && ! preg_match('/^https?\:\/\//', $documentroot)) + * ); + * $question_nr = 1; + * foreach ($security_questions as $question_name => $question_launch) { + * if ($question_launch !== false) { + * $params[$question_name] = $question_name; + * + * if (! isset($_POST[$question_name]) || $_POST[$question_name] != $question_name) { + * ask_yesno('admin_domain_' . $question_name, $filename, $params, $question_nr); + * } + * } + * $question_nr ++; + * } + */ + + $wwwserveralias = ($serveraliasoption == '1') ? '1' : '0'; + $iswildcarddomain = ($serveraliasoption == '0') ? '1' : '0'; + + $ins_data = array( + 'domain' => $domain, + 'customerid' => $customerid, + 'adminid' => $adminid, + 'documentroot' => $documentroot, + 'aliasdomain' => ($aliasdomain != 0 ? $aliasdomain : null), + 'zonefile' => $zonefile, + 'dkim' => $dkim, + 'wwwserveralias' => $wwwserveralias, + 'iswildcarddomain' => $iswildcarddomain, + 'isbinddomain' => $isbinddomain, + 'isemaildomain' => $isemaildomain, + 'email_only' => $email_only, + 'subcanemaildomain' => $subcanemaildomain, + 'caneditdomain' => $caneditdomain, + 'phpenabled' => $phpenabled, + 'openbasedir' => $openbasedir, + 'speciallogfile' => $speciallogfile, + 'specialsettings' => $specialsettings, + 'notryfiles' => $notryfiles, + 'ssl_redirect' => $ssl_redirect, + 'add_date' => time(), + 'registration_date' => $registration_date, + 'termination_date' => $termination_date, + 'phpsettingid' => $phpsettingid, + 'mod_fcgid_starter' => $mod_fcgid_starter, + 'mod_fcgid_maxrequests' => $mod_fcgid_maxrequests, + 'ismainbutsubto' => $issubof, + 'letsencrypt' => $letsencrypt, + 'http2' => $http2, + 'hsts' => $hsts_maxage, + 'hsts_sub' => $hsts_sub, + 'hsts_preload' => $hsts_preload, + 'ocsp_stapling' => $ocsp_stapling + ); + + $ins_stmt = Database::prepare(" + INSERT INTO `" . TABLE_PANEL_DOMAINS . "` SET + `domain` = :domain, + `customerid` = :customerid, + `adminid` = :adminid, + `documentroot` = :documentroot, + `aliasdomain` = :aliasdomain, + `zonefile` = :zonefile, + `dkim` = :dkim, + `dkim_id` = '0', + `dkim_privkey` = '', + `dkim_pubkey` = '', + `wwwserveralias` = :wwwserveralias, + `iswildcarddomain` = :iswildcarddomain, + `isbinddomain` = :isbinddomain, + `isemaildomain` = :isemaildomain, + `email_only` = :email_only, + `subcanemaildomain` = :subcanemaildomain, + `caneditdomain` = :caneditdomain, + `phpenabled` = :phpenabled, + `openbasedir` = :openbasedir, + `speciallogfile` = :speciallogfile, + `specialsettings` = :specialsettings, + `notryfiles` = :notryfiles, + `ssl_redirect` = :ssl_redirect, + `add_date` = :add_date, + `registration_date` = :registration_date, + `termination_date` = :termination_date, + `phpsettingid` = :phpsettingid, + `mod_fcgid_starter` = :mod_fcgid_starter, + `mod_fcgid_maxrequests` = :mod_fcgid_maxrequests, + `ismainbutsubto` = :ismainbutsubto, + `letsencrypt` = :letsencrypt, + `http2` = :http2, + `hsts` = :hsts, + `hsts_sub` = :hsts_sub, + `hsts_preload` = :hsts_preload, + `ocsp_stapling` = :ocsp_stapling + "); + Database::pexecute($ins_stmt, $ins_data, true, true); + $domainid = Database::lastInsertId(); + + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_ADMINS . "` SET `domains_used` = `domains_used` + 1 + WHERE `adminid` = :adminid"); + Database::pexecute($upd_stmt, array( + 'adminid' => $adminid + ), true, true); + + $ins_stmt = Database::prepare(" + INSERT INTO `" . TABLE_DOMAINTOIP . "` SET + `id_domain` = :domainid, + `id_ipandports` = :ipandportsid + "); + + foreach ($ipandports as $ipportid) { + $ins_data = array( + 'domainid' => $domainid, + 'ipandportsid' => $ipportid + ); + Database::pexecute($ins_stmt, $ins_data, true, true); + } + + foreach ($ssl_ipandports as $ssl_ipportid) { + if ($ssl_ipportid > 0) { + $ins_data = array( + 'domainid' => $domainid, + 'ipandportsid' => $ssl_ipportid + ); + Database::pexecute($ins_stmt, $ins_data, true, true); + } + } + + triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $this->logger()); + + inserttask('1'); + // Using nameserver, insert a task which rebuilds the server config + inserttask('4'); + + $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] added domain '" . $domain . "'"); + return $this->response(200, "successfull", $ins_data); + } + } + throw new Exception("No more resources available", 406); + } + throw new Exception("Not allowed to execute given command.", 403); + } + + public function update() + { + if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { + $id = $this->getParam('id'); + + $json_result = Domains::getLocal($this->getUserData(), array( + 'id' => $id, + 'no_std_subdomain' => true + ))->get(); + $result = json_decode($json_result, true)['data']; + + $customer_stmt = Database::prepare(" + SELECT * FROM " . TABLE_PANEL_CUSTOMERS . " WHERE `customerid` = :customerid + "); + $customer = Database::pexecute_first($customer_stmt, array( + 'customerid' => $result['customerid'] + )); + + $customerid = $this->getParam('customerid', $result['customerid']); + + if ($customerid > 0 && $customerid != $result['customerid'] && Settings::Get('panel.allow_domain_change_customer') == '1') { + + $customer_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_CUSTOMERS . "` + WHERE `customerid` = :customerid + AND (`subdomains_used` + :subdomains <= `subdomains` OR `subdomains` = '-1' ) + AND (`emails_used` + :emails <= `emails` OR `emails` = '-1' ) + AND (`email_forwarders_used` + :forwarders <= `email_forwarders` OR `email_forwarders` = '-1' ) + AND (`email_accounts_used` + :accounts <= `email_accounts` OR `email_accounts` = '-1' ) " . ($this->getUserDetail('customers_see_all') ? '' : " AND `adminid` = :adminid")); + + $params = array( + 'customerid' => $customerid, + 'subdomains' => $subdomains, + 'emails' => $emails, + 'forwarders' => $email_forwarders, + 'accounts' => $email_accounts + ); + if ($this->getUserDetail('customers_see_all') == '0') { + $params['adminid'] = $this->getUserDetail('adminid'); + } + + $customer = Database::pexecute_first($customer_stmt, $params, true, true); + if (empty($customer) || $customer['customerid'] != $customerid) { + standard_error('customerdoesntexist', '', true); + } + } else { + $customerid = $result['customerid']; + } + + $customer_stmt = Database::prepare(" + SELECT * FROM " . TABLE_PANEL_ADMINS . " WHERE `adminid` = :adminid + "); + $admin = Database::pexecute_first($customer_stmt, array( + 'adminid' => $result['adminid'] + ), true, true); + + if ($this->getUserDetail('customers_see_all') == '1') { + + $adminid = $this->getParam('adminid', $result['adminid']); + + if ($adminid > 0 && $adminid != $result['adminid'] && Settings::Get('panel.allow_domain_change_admin') == '1') { + + $admin_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_ADMINS . "` + WHERE `adminid` = :adminid AND ( `domains_used` < `domains` OR `domains` = '-1' ) + "); + $admin = Database::pexecute_first($admin_stmt, array( + 'adminid' => $adminid + ), true, true); + + if (empty($admin) || $admin['adminid'] != $adminid) { + standard_error('admindoesntexist', '', true); + } + } else { + $adminid = $result['adminid']; + } + } else { + $adminid = $result['adminid']; + } + + $aliasdomain = $this->getParam('alias', $result['aliasdomain']); + $issubof = $this->getParam('issubof', $result['ismainbutsubto']); + $subcanemaildomain = $this->getParam('subcanemaildomain', $result['subcanemaildomain']); + $caneditdomain = $this->getParam('caneditdomain', $result['caneditdomain']); + $registration_date = $this->getParam('registration_date', $result['registration_date']); + $registration_date = validate($registration_date, 'registration_date', '/^(19|20)\d\d[-](0[1-9]|1[012])[-](0[1-9]|[12][0-9]|3[01])$/', '', array( + '0000-00-00', + '0', + '' + ), true); + if ($registration_date == '0000-00-00') { + $registration_date = null; + } + $termination_date = $this->getParam('termination_date', $result['termination_date']); + $termination_date = validate($termination_date, 'termination_date', '/^(19|20)\d\d[-](0[1-9]|1[012])[-](0[1-9]|[12][0-9]|3[01])$/', '', array( + '0000-00-00', + '0', + '' + ), true); + if ($termination_date == '0000-00-00') { + $termination_date = null; + } + + $isemaildomain = $this->getParam('isemaildomain', $result['isemaildomain']); + $email_only = $this->getParam('email_only', $result['email_only']); + + $serveraliasoption = '2'; + if ($result['iswildcarddomain'] == '1') { + $serveraliasoption = '0'; + } elseif ($result['wwwserveralias'] == '1') { + $serveraliasoption = '1'; + } + if (! empty($this->getParam('selectserveralias'))) { + $serveraliasoption = intval($this->getParam('selectserveralias')); + } + + $speciallogfile = $this->getParam('speciallogfile', $result['speciallogfile']); + + if ($this->getUserDetail('change_serversettings') == '1') { + $isbinddomain = $result['isbinddomain']; + $zonefile = $result['zonefile']; + if (Settings::Get('system.bind_enable') == '1') { + $isbinddomain = $this->getParam('isbinddomain', $result['isbinddomain']); + $zonefile = validate($this->getParam('zonefile', $result['zonefile']), 'zonefile', '', '', array(), true); + } + + if (Settings::Get('dkim.use_dkim') == '1') { + $dkim = $this->getParam('dkim', $result['dkim']); + } else { + $dkim = $result['dkim']; + } + + $specialsettings = validate(str_replace("\r\n", "\n", $this->getParam('specialsettings', $result['specialsettings'])), 'specialsettings', '/^[^\0]*$/', '', array(), true); + $ssfs = $this->getParam('specialsettingsforsubdomains', 0); + $notryfiles = $this->getParam('notryfiles', $result['notryfiles']); + $documentroot = validate($this->getParam('documentroot', $result['documentroot']), 'documentroot', '', '', array(), true); + + if ($documentroot == '') { + // If path is empty and 'Use domain name as default value for DocumentRoot path' is enabled in settings, + // set default path to subdomain or domain name + if (Settings::Get('system.documentroot_use_default_value') == 1) { + $documentroot = makeCorrectDir($customer['documentroot'] . '/' . $result['domain']); + } else { + $documentroot = $customer['documentroot']; + } + } + + if (! preg_match('/^https?\:\/\//', $documentroot) && strstr($documentroot, ":") !== false) { + standard_error('pathmaynotcontaincolon', '', true); + } + } else { + $isbinddomain = $result['isbinddomain']; + $zonefile = $result['zonefile']; + $dkim = $result['dkim']; + $specialsettings = $result['specialsettings']; + $ssfs = (empty($specialsettings) ? 0 : 1); + $notryfiles = $result['notryfiles']; + $documentroot = $result['documentroot']; + } + + // @TODO unsure whether this will still work + $speciallogverified = $this->getParam('speciallogverified', 0); + + if ($this->getUserDetail('caneditphpsettings') == '1' || $this->getUserDetail('change_serversettings') == '1') { + + $phpenabled = $this->getParam('phpenabled', $result['phpenabled']); + $openbasedir = $this->getParam('openbasedir', $result['openbasedir']); + $phpfs = $this->getParam('phpsettingsforsubdomains', 0); + + if ((int) Settings::Get('system.mod_fcgid') == 1 || (int) Settings::Get('phpfpm.enabled') == 1) { + $phpsettingid = $this->getParam('phpsettingid', $result['phpsettingid']); + $phpsettingid_check_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_PHPCONFIGS . "` WHERE `id` = :phpid + "); + $phpsettingid_check = Database::pexecute_first($phpsettingid_check_stmt, array( + 'phpid' => $phpsettingid + ), true, true); + + if (! isset($phpsettingid_check['id']) || $phpsettingid_check['id'] == '0' || $phpsettingid_check['id'] != $phpsettingid) { + standard_error('phpsettingidwrong', '', true); + } + + if ((int) Settings::Get('system.mod_fcgid') == 1) { + $mod_fcgid_starter = validate($this->getParam('mod_fcgid_starter', $result['mod_fcgid_starter']), 'mod_fcgid_starter', '/^[0-9]*$/', '', array( + '-1', + '' + ), true); + $mod_fcgid_maxrequests = validate($this->getParam('mod_fcgid_maxrequests', $result['mod_fcgid_maxrequests']), 'mod_fcgid_maxrequests', '/^[0-9]*$/', '', array( + '-1', + '' + ), true); + } else { + $mod_fcgid_starter = $result['mod_fcgid_starter']; + $mod_fcgid_maxrequests = $result['mod_fcgid_maxrequests']; + } + } else { + $phpsettingid = $result['phpsettingid']; + $phpfs = 1; + $mod_fcgid_starter = $result['mod_fcgid_starter']; + $mod_fcgid_maxrequests = $result['mod_fcgid_maxrequests']; + } + } else { + $phpenabled = $result['phpenabled']; + $openbasedir = $result['openbasedir']; + $phpsettingid = $result['phpsettingid']; + $phpfs = 1; + $mod_fcgid_starter = $result['mod_fcgid_starter']; + $mod_fcgid_maxrequests = $result['mod_fcgid_maxrequests']; + } + + $ipandports = array(); + if (! empty($this->getParam('ipandport')) && ! is_array($this->getParam('ipandport'))) { + $this->updateParam('ipandport', unserialize($this->getParam('ipandport'))); + } + + if (! empty($this->getParam('ipandport')) && is_array($this->getParam('ipandport'))) { + $ipandport_check_stmt = Database::prepare(" + SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :ipandport + "); + foreach ($this->getParam('ipandport') as $ipandport) { + if (trim($ipandport) == "") { + continue; + } + $ipandport = intval($ipandport); + $ipandport_check = Database::pexecute_first($ipandport_check_stmt, array( + 'ipandport' => $ipandport + ), true, true); + if (! isset($ipandport_check['id']) || $ipandport_check['id'] == '0' || $ipandport_check['id'] != $ipandport) { + standard_error('ipportdoesntexist', '', true); + } else { + $ipandports[] = $ipandport; + } + } + } + + if (Settings::Get('system.use_ssl') == '1' && ! empty($this->getParam('ssl_ipandport'))) { + $ssl = 1; // if ssl is set and != 0, it can only be 1 + $ssl_redirect = $this->getParam('ssl_redirect', $result['ssl_redirect']); + $letsencrypt = $this->getParam('letsencrypt', $result['letsencrypt']); + + $ssl_ipandports = array(); + if (! empty($this->getParam('ssl_ipandport')) && ! is_array($this->getParam('ssl_ipandport'))) { + $this->updateParam('ssl_ipandport', unserialize($this->getParam('ssl_ipandport'))); + } + if (! empty($this->getParam('ssl_ipandport')) && is_array($this->getParam('ssl_ipandport'))) { + $ssl_ipandport_check_stmt = Database::prepare(" + SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :ipandport + "); + foreach ($this->getParam('ssl_ipandport') as $ssl_ipandport) { + if (trim($ssl_ipandport) == "") { + continue; + } + // fix if ip/port got de-checked and it was the last one + if (trim($ssl_ipandport) < 1) { + continue; + } + $ssl_ipandport = intval($ssl_ipandport); + $ssl_ipandport_check = Database::pexecute_first($ssl_ipandport_check_stmt, array( + 'ipandport' => $ssl_ipandport + ), true, true); + if (! isset($ssl_ipandport_check['id']) || $ssl_ipandport_check['id'] == '0' || $ssl_ipandport_check['id'] != $ssl_ipandport) { + standard_error('ipportdoesntexist', '', true); + } else { + $ssl_ipandports[] = $ssl_ipandport; + } + } + + $http2 = $this->getParam('http2', $result['http2']); + // HSTS + $hsts_maxage = $this->getParam('hsts_maxage', $result['hsts_maxage']); + $hsts_sub = $this->getParam('hsts_sub', $result['hsts_sub']); + $hsts_preload = $this->getParam('hsts_preload', $result['hsts_preload']); + // OCSP stapling + $ocsp_stapling = $this->getParam('ocsp_stapling', $result['ocsp_stapling']); + } else { + $ssl_redirect = 0; + $letsencrypt = 0; + $http2 = 0; + // we need this for the serialize + // if ssl is disabled or no ssl-ip/port exists + $ssl_ipandports[] = - 1; + + // HSTS + $hsts_maxage = 0; + $hsts_sub = 0; + $hsts_preload = 0; + + // OCSP stapling + $ocsp_stapling = 0; + } + } else { + $ssl_redirect = 0; + $letsencrypt = 0; + $http2 = 0; + // we need this for the serialize + // if ssl is disabled or no ssl-ip/port exists + $ssl_ipandports[] = - 1; + + // HSTS + $hsts_maxage = 0; + $hsts_sub = 0; + $hsts_preload = 0; + + // OCSP stapling + $ocsp_stapling = 0; + } + + // We can't enable let's encrypt for wildcard domains when using acme-v1 + if ($serveraliasoption == '0' && $letsencrypt == '1' && Settings::Get('system.leapiversion') == '1') { + standard_error('nowildcardwithletsencrypt', '', true); + } + // if using acme-v2 we cannot issue wildcard-certificates + // because they currently only support the dns-01 challenge + if ($serveraliasoption == '0' && $letsencrypt == '1' && Settings::Get('system.leapiversion') == '2') { + standard_error('nowildcardwithletsencryptv2', '', true); + } + + // Temporarily deactivate ssl_redirect until Let's Encrypt certificate was generated + if ($ssl_redirect > 0 && $letsencrypt == 1 && $result['letsencrypt'] != $letsencrypt) { + $ssl_redirect = 2; + } + + if (! preg_match('/^https?\:\/\//', $documentroot)) { + $documentroot = makeCorrectDir($documentroot); + } + + if ($phpenabled != '1') { + $phpenabled = '0'; + } + + if ($openbasedir != '1') { + $openbasedir = '0'; + } + + if ($isbinddomain != '1') { + $isbinddomain = '0'; + } + + if ($isemaildomain != '1') { + $isemaildomain = '0'; + } + + if ($email_only == '1') { + $isemaildomain = '1'; + } else { + $email_only = '0'; + } + + if ($subcanemaildomain != '1' && $subcanemaildomain != '2' && $subcanemaildomain != '3') { + $subcanemaildomain = '0'; + } + + if ($dkim != '1') { + $dkim = '0'; + } + + if ($caneditdomain != '1') { + $caneditdomain = '0'; + } + + $aliasdomain_check = array( + 'id' => 0 + ); + + if ($aliasdomain != 0) { + // Overwrite given ipandports with these of the "main" domain + $ipandports = array(); + $ssl_ipandports = array(); + $origipresult_stmt = Database::prepare(" + SELECT `id_ipandports` FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :aliasdomain + "); + Database::pexecute($origipresult_stmt, array( + 'aliasdomain' => $aliasdomain + ), true, true); + $ipdata_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :ipid"); + while ($origip = $origipresult_stmt->fetch(PDO::FETCH_ASSOC)) { + $_origip_tmp = Database::pexecute_first($ipdata_stmt, array( + 'ipid' => $origip['id_ipandports'] + ), true, true); + if ($_origip_tmp['ssl'] == 0) { + $ipandports[] = $origip['id_ipandports']; + } else { + $ssl_ipandports[] = $origip['id_ipandports']; + } + } + + if (count($ssl_ipandports) == 0) { + // we need this for the serialize + // if ssl is disabled or no ssl-ip/port exists + $ssl_ipandports[] = - 1; + } + + $aliasdomain_check_stmt = Database::prepare(" + SELECT `d`.`id` FROM `" . TABLE_PANEL_DOMAINS . "` `d`, `" . TABLE_PANEL_CUSTOMERS . "` `c` + WHERE `d`.`customerid` = :customerid + AND `d`.`aliasdomain` IS NULL AND `d`.`id` <> `c`.`standardsubdomain` + AND `c`.`customerid` = :customerid + AND `d`.`id` = :aliasdomain + "); + $aliasdomain_check = Database::pexecute_first($aliasdomain_check_stmt, array( + 'customerid' => $customerid, + 'aliasdomain' => $aliasdomain + ), true, true); + } + + if (count($ipandports) == 0) { + standard_error('noipportgiven', '', true); + } + + if ($aliasdomain_check['id'] != $aliasdomain) { + standard_error('domainisaliasorothercustomer', '', true); + } + + if ($issubof <= '0') { + $issubof = '0'; + } + + if ($serveraliasoption != '1' && $serveraliasoption != '2') { + $serveraliasoption = '0'; + } + + /** + * + * @todo how to handle security questions now? + * + * $params = array( + * 'id' => $id, + * 'page' => $page, + * 'action' => $action, + * 'customerid' => $customerid, + * 'adminid' => $adminid, + * 'documentroot' => $documentroot, + * 'alias' => $aliasdomain, + * 'isbinddomain' => $isbinddomain, + * 'isemaildomain' => $isemaildomain, + * 'email_only' => $email_only, + * 'subcanemaildomain' => $subcanemaildomain, + * 'caneditdomain' => $caneditdomain, + * 'zonefile' => $zonefile, + * 'dkim' => $dkim, + * 'selectserveralias' => $serveraliasoption, + * 'ssl_redirect' => $ssl_redirect, + * 'phpenabled' => $phpenabled, + * 'openbasedir' => $openbasedir, + * 'phpsettingid' => $phpsettingid, + * 'phpsettingsforsubdomains' => $phpfs, + * 'mod_fcgid_starter' => $mod_fcgid_starter, + * 'mod_fcgid_maxrequests' => $mod_fcgid_maxrequests, + * 'specialsettings' => $specialsettings, + * 'specialsettingsforsubdomains' => $ssfs, + * 'notryfiles' => $notryfiles, + * 'registration_date' => $registration_date, + * 'termination_date' => $termination_date, + * 'issubof' => $issubof, + * 'speciallogfile' => $speciallogfile, + * 'speciallogverified' => $speciallogverified, + * 'ipandport' => serialize($ipandports), + * 'ssl_ipandport' => serialize($ssl_ipandports), + * 'letsencrypt' => $letsencrypt, + * 'http2' => $http2, + * 'hsts_maxage' => $hsts_maxage, + * 'hsts_sub' => $hsts_sub, + * 'hsts_preload' => $hsts_preload, + * 'ocsp_stapling' => $ocsp_stapling + * ); + * + * $security_questions = array( + * 'reallydisablesecuritysetting' => ($openbasedir == '0' && $userinfo['change_serversettings'] == '1'), + * 'reallydocrootoutofcustomerroot' => (substr($documentroot, 0, strlen($customer['documentroot'])) != $customer['documentroot'] && ! preg_match('/^https?\:\/\//', $documentroot)) + * ); + * foreach ($security_questions as $question_name => $question_launch) { + * if ($question_launch !== false) { + * $params[$question_name] = $question_name; + * if (! isset($_POST[$question_name]) || $_POST[$question_name] != $question_name) { + * ask_yesno('admin_domain_' . $question_name, $filename, $params); + * } + * } + * } + */ + + $wwwserveralias = ($serveraliasoption == '1') ? '1' : '0'; + $iswildcarddomain = ($serveraliasoption == '0') ? '1' : '0'; + + if ($documentroot != $result['documentroot'] || $ssl_redirect != $result['ssl_redirect'] || $wwwserveralias != $result['wwwserveralias'] || $iswildcarddomain != $result['iswildcarddomain'] || $phpenabled != $result['phpenabled'] || $openbasedir != $result['openbasedir'] || $phpsettingid != $result['phpsettingid'] || $mod_fcgid_starter != $result['mod_fcgid_starter'] || $mod_fcgid_maxrequests != $result['mod_fcgid_maxrequests'] || $specialsettings != $result['specialsettings'] || $notryfiles != $result['notryfiles'] || $aliasdomain != $result['aliasdomain'] || $issubof != $result['ismainbutsubto'] || $email_only != $result['email_only'] || ($speciallogfile != $result['speciallogfile'] && $speciallogverified == '1') || $letsencrypt != $result['letsencrypt'] || $http2 != $result['http2'] || $hsts_maxage != $result['hsts'] || $hsts_sub != $result['hsts_sub'] || $hsts_preload != $result['hsts_preload'] || $ocsp_stapling != $result['ocsp_stapling']) { + inserttask('1'); + } + + if ($speciallogfile != $result['speciallogfile'] && $speciallogverified != '1') { + $speciallogfile = $result['speciallogfile']; + } + + if ($isbinddomain != $result['isbinddomain'] || $zonefile != $result['zonefile'] || $dkim != $result['dkim']) { + inserttask('4'); + } + + if ($isemaildomain == '0' && $result['isemaildomain'] == '1') { + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_MAIL_USERS . "` WHERE `domainid` = :id + "); + Database::pexecute($del_stmt, array( + 'id' => $id + ), true, true); + + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_MAIL_VIRTUAL . "` WHERE `domainid` = :id + "); + Database::pexecute($del_stmt, array( + 'id' => $id + ), true, true); + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] deleted domain #" . $id . " from mail-tables as is-email-domain was set to 0"); + } + + // check whether LE has been disabled, so we remove the certificate + if ($letsencrypt == '0' && $result['letsencrypt'] == '1') { + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` WHERE `domainid` = :id + "); + Database::pexecute($del_stmt, array( + 'id' => $id + ), true, true); + } + + $updatechildren = ''; + + if ($subcanemaildomain == '0' && $result['subcanemaildomain'] != '0') { + $updatechildren = ", `isemaildomain` = '0' "; + } elseif ($subcanemaildomain == '3' && $result['subcanemaildomain'] != '3') { + $updatechildren = ", `isemaildomain` = '1' "; + } + + if ($customerid != $result['customerid'] && Settings::Get('panel.allow_domain_change_customer') == '1') { + $upd_data = array( + 'customerid' => $customerid, + 'domainid' => $result['id'] + ); + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_MAIL_USERS . "` SET `customerid` = :customerid WHERE `domainid` = :domainid + "); + Database::pexecute($upd_stmt, $upd_data, true, true); + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_MAIL_VIRTUAL . "` SET `customerid` = :customerid WHERE `domainid` = :domainid + "); + Database::pexecute($upd_stmt, $upd_data, true, true); + $upd_data = array( + 'subdomains' => $subdomains, + 'emails' => $emails, + 'forwarders' => $email_forwarders, + 'accounts' => $email_accounts + ); + $upd_data['customerid'] = $customerid; + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET + `subdomains_used` = `subdomains_used` + :subdomains, + `emails_used` = `emails_used` + :emails, + `email_forwarders_used` = `email_forwarders_used` + :forwarders, + `email_accounts_used` = `email_accounts_used` + :accounts + WHERE `customerid` = :customerid + "); + Database::pexecute($upd_stmt, $upd_data, true, true); + + $upd_data['customerid'] = $result['customerid']; + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET + `subdomains_used` = `subdomains_used` - :subdomains, + `emails_used` = `emails_used` - :emails, + `email_forwarders_used` = `email_forwarders_used` - :forwarders, + `email_accounts_used` = `email_accounts_used` - :accounts + WHERE `customerid` = :customerid + "); + Database::pexecute($upd_stmt, $upd_data, true, true); + } + + if ($adminid != $result['adminid'] && Settings::Get('panel.allow_domain_change_admin') == '1') { + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_ADMINS . "` SET `domains_used` = `domains_used` + 1 WHERE `adminid` = :adminid + "); + Database::pexecute($upd_stmt, array( + 'adminid' => $adminid + ), true, true); + + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_ADMINS . "` SET `domains_used` = `domains_used` - 1 WHERE `adminid` = :adminid + "); + Database::pexecute($upd_stmt, array( + 'adminid' => $result['adminid'] + ), true, true); + } + + $_update_data = array(); + + $ssfs = $this->getParam('specialsettingsforsubdomains', 0); + if ($ssfs == 1) { + $_update_data['specialsettings'] = $specialsettings; + $upd_specialsettings = ", `specialsettings` = :specialsettings "; + } else { + $upd_specialsettings = ''; + unset($_update_data['specialsettings']); + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `specialsettings`='' WHERE `parentdomainid` = :id + "); + Database::pexecute($upd_stmt, array( + 'id' => $id + ), true, true); + $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] removed specialsettings on all subdomains of domain #" . $id); + } + + $wwwserveralias = ($serveraliasoption == '1') ? '1' : '0'; + $iswildcarddomain = ($serveraliasoption == '0') ? '1' : '0'; + + $update_data = array(); + $update_data['customerid'] = $customerid; + $update_data['adminid'] = $adminid; + $update_data['documentroot'] = $documentroot; + $update_data['ssl_redirect'] = $ssl_redirect; + $update_data['aliasdomain'] = ($aliasdomain != 0 && $alias_check == 0) ? $aliasdomain : null; + $update_data['isbinddomain'] = $isbinddomain; + $update_data['isemaildomain'] = $isemaildomain; + $update_data['email_only'] = $email_only; + $update_data['subcanemaildomain'] = $subcanemaildomain; + $update_data['dkim'] = $dkim; + $update_data['caneditdomain'] = $caneditdomain; + $update_data['zonefile'] = $zonefile; + $update_data['wwwserveralias'] = $wwwserveralias; + $update_data['iswildcarddomain'] = $iswildcarddomain; + $update_data['phpenabled'] = $phpenabled; + $update_data['openbasedir'] = $openbasedir; + $update_data['speciallogfile'] = $speciallogfile; + $update_data['phpsettingid'] = $phpsettingid; + $update_data['mod_fcgid_starter'] = $mod_fcgid_starter; + $update_data['mod_fcgid_maxrequests'] = $mod_fcgid_maxrequests; + $update_data['specialsettings'] = $specialsettings; + $update_data['notryfiles'] = $notryfiles; + $update_data['registration_date'] = $registration_date; + $update_data['termination_date'] = $termination_date; + $update_data['ismainbutsubto'] = $issubof; + $update_data['letsencrypt'] = $letsencrypt; + $update_data['http2'] = $http2; + $update_data['hsts'] = $hsts_maxage; + $update_data['hsts_sub'] = $hsts_sub; + $update_data['hsts_preload'] = $hsts_preload; + $update_data['ocsp_stapling'] = $ocsp_stapling; + $update_data['id'] = $id; + + $update_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_DOMAINS . "` SET + `customerid` = :customerid, + `adminid` = :adminid, + `documentroot` = :documentroot, + `ssl_redirect` = :ssl_redirect, + `aliasdomain` = :aliasdomain, + `isbinddomain` = :isbinddomain, + `isemaildomain` = :isemaildomain, + `email_only` = :email_only, + `subcanemaildomain` = :subcanemaildomain, + `dkim` = :dkim, + `caneditdomain` = :caneditdomain, + `zonefile` = :zonefile, + `wwwserveralias` = :wwwserveralias, + `iswildcarddomain` = :iswildcarddomain, + `phpenabled` = :phpenabled, + `openbasedir` = :openbasedir, + `speciallogfile` = :speciallogfile, + `phpsettingid` = :phpsettingid, + `mod_fcgid_starter` = :mod_fcgid_starter, + `mod_fcgid_maxrequests` = :mod_fcgid_maxrequests, + `specialsettings` = :specialsettings, + `notryfiles` = :notryfiles, + `registration_date` = :registration_date, + `termination_date` = :termination_date, + `ismainbutsubto` = :ismainbutsubto, + `letsencrypt` = :letsencrypt, + `http2` = :http2, + `hsts` = :hsts, + `hsts_sub` = :hsts_sub, + `hsts_preload` = :hsts_preload, + `ocsp_stapling` = :ocsp_stapling + WHERE `id` = :id + "); + Database::pexecute($update_stmt, $update_data, true, true); + + $_update_data['customerid'] = $customerid; + $_update_data['adminid'] = $adminid; + $_update_data['phpenabled'] = $phpenabled; + $_update_data['openbasedir'] = $openbasedir; + $_update_data['mod_fcgid_starter'] = $mod_fcgid_starter; + $_update_data['mod_fcgid_maxrequests'] = $mod_fcgid_maxrequests; + $_update_data['parentdomainid'] = $id; + + // if php config is to be set for all subdomains, check here + $update_phpconfig = ''; + $phpfs = $this->getParam('phpsettingsforsubdomains', 0); + if ($phpfs == 1) { + $_update_data['phpsettingid'] = $phpsettingid; + $update_phpconfig = ", `phpsettingid` = :phpsettingid"; + } + + // if we have no more ssl-ip's for this domain, + // all its subdomains must have "ssl-redirect = 0" + // and disable let's encrypt + $update_sslredirect = ''; + if (count($ssl_ipandports) == 1 && $ssl_ipandports[0] == - 1) { + $update_sslredirect = ", `ssl_redirect` = '0', `letsencrypt` = '0' "; + } + + $_update_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_DOMAINS . "` SET + `customerid` = :customerid, + `adminid` = :adminid, + `phpenabled` = :phpenabled, + `openbasedir` = :openbasedir, + `mod_fcgid_starter` = :mod_fcgid_starter, + `mod_fcgid_maxrequests` = :mod_fcgid_maxrequests + " . $update_phpconfig . $upd_specialsettings . $updatechildren . $update_sslredirect . " + WHERE `parentdomainid` = :parentdomainid + "); + Database::pexecute($_update_stmt, $_update_data, true, true); + + // FIXME check how many we got and if the amount of assigned IP's + // has changed so we can insert a config-rebuild task if only + // the ip's of this domain were changed + // -> for now, always insert a rebuild-task + inserttask('1'); + + // Cleanup domain <-> ip mapping + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :id + "); + Database::pexecute($del_stmt, array( + 'id' => $id + ), true, true); + + $ins_stmt = Database::prepare(" + INSERT INTO `" . TABLE_DOMAINTOIP . "` SET `id_domain` = :domainid, `id_ipandports` = :ipportid + "); + + foreach ($ipandports as $ipportid) { + Database::pexecute($ins_stmt, array( + 'domainid' => $id, + 'ipportid' => $ipportid + ), true, true); + } + foreach ($ssl_ipandports as $ssl_ipportid) { + if ($ssl_ipportid > 0) { + Database::pexecute($ins_stmt, array( + 'domainid' => $id, + 'ipportid' => $ssl_ipportid + ), true, true); + } + } + + // Cleanup domain <-> ip mapping for subdomains + $domainidsresult_stmt = Database::prepare(" + SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `parentdomainid` = :id + "); + Database::pexecute($domainidsresult_stmt, array( + 'id' => $id + ), true, true); + + while ($row = $domainidsresult_stmt->fetch(PDO::FETCH_ASSOC)) { + + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :rowid + "); + Database::pexecute($del_stmt, array( + 'rowid' => $row['id'] + ), true, true); + + $ins_stmt = Database::prepare(" + INSERT INTO `" . TABLE_DOMAINTOIP . "` SET + `id_domain` = :rowid, + `id_ipandports` = :ipportid + "); + + foreach ($ipandports as $ipportid) { + Database::pexecute($ins_stmt, array( + 'rowid' => $row['id'], + 'ipportid' => $ipportid + ), true, true); + } + foreach ($ssl_ipandports as $ssl_ipportid) { + if ($ssl_ipportid > 0) { + Database::pexecute($ins_stmt, array( + 'rowid' => $row['id'], + 'ipportid' => $ssl_ipportid + ), true, true); + } + } + } + if ($result['aliasdomain'] != $aliasdomain) { + // trigger when domain id for alias destination has changed: both for old and new destination + triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $this->logger()); + triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $this->logger()); + } else if ($result['wwwserveralias'] != $wwwserveralias || $result['letsencrypt'] != $letsencrypt) { + // or when wwwserveralias or letsencrypt was changed + triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $this->logger()); + } + + $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] updated domain '" . $result['domain'] . "'"); + return $this->response(200, "successfull", $update_data); + } + throw new Exception("Not allowed to execute given command.", 403); + } + + public function delete() + { + if ($this->isAdmin()) { + $id = $this->getParam('id'); + + $json_result = Domains::getLocal($this->getUserData(), array( + 'id' => $id, + 'no_std_subdomain' => true + ))->get(); + $result = json_decode($json_result, true)['data']; + + // check for deletion of main-domains which are logically subdomains, #329 + $rsd_sql = ''; + $remove_subbutmain_domains = $this->getParam('delete_userfiles', 0) ? 1 : 0; + if ($remove_subbutmain_domains == 1) { + $rsd_sql .= " OR `ismainbutsubto` = :id"; + } + + $subresult_stmt = Database::prepare(" + SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` + WHERE (`id` = :id OR `parentdomainid` = :id " . $rsd_sql . ")"); + Database::pexecute($subresult_stmt, array( + 'id' => $id + ), true, true); + $idString = array(); + $paramString = array(); + while ($subRow = $subresult_stmt->fetch(PDO::FETCH_ASSOC)) { + $idString[] = "`domainid` = :domain_" . (int) $subRow['id']; + $paramString['domain_' . $subRow['id']] = $subRow['id']; + } + $idString = implode(' OR ', $idString); + + if ($idString != '') { + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_MAIL_USERS . "` WHERE " . $idString); + Database::pexecute($del_stmt, $paramString, true, true); + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_MAIL_VIRTUAL . "` WHERE " . $idString); + Database::pexecute($del_stmt, $paramString, true, true); + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] deleted domain/s from mail-tables"); + } + + // if mainbutsubto-domains are not to be deleted, re-assign the (ismainbutsubto value of the main + // domain which is being deleted) as their new ismainbutsubto value + if ($remove_subbutmain_domains !== 1) { + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_DOMAINS . "` SET + `ismainbutsubto` = :newIsMainButSubtoValue + WHERE `ismainbutsubto` = :deletedMainDomainId + "); + Database::pexecute($upd_stmt, array( + 'newIsMainButSubtoValue' => $result['ismainbutsubto'], + 'deletedMainDomainId' => $id + ), true, true); + } + + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_PANEL_DOMAINS . "` + WHERE `id` = :id OR `parentdomainid` = :id " . $rsd_sql); + Database::pexecute($del_stmt, array( + 'id' => $id + ), true, true); + + $deleted_domains = $del_stmt->rowCount(); + + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET + `subdomains_used` = `subdomains_used` - :domaincount + WHERE `customerid` = :customerid"); + Database::pexecute($upd_stmt, array( + 'domaincount' => ($deleted_domains - 1), + 'customerid' => $result['customerid'] + ), true, true); + + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_ADMINS . "` SET + `domains_used` = `domains_used` - 1 + WHERE `adminid` = :adminid"); + Database::pexecute($upd_stmt, array( + 'adminid' => $this->getUserDetail('adminid') + ), true, true); + + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET + `standardsubdomain` = '0' + WHERE `standardsubdomain` = :id AND `customerid` = :customerid"); + Database::pexecute($upd_stmt, array( + 'id' => $result['id'], + 'customerid' => $result['customerid'] + ), true, true); + + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_DOMAINTOIP . "` + WHERE `id_domain` = :domainid"); + Database::pexecute($del_stmt, array( + 'domainid' => $id + ), true, true); + + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_PANEL_DOMAINREDIRECTS . "` + WHERE `did` = :domainid"); + Database::pexecute($del_stmt, array( + 'domainid' => $id + ), true, true); + + // remove certificate from domain_ssl_settings, fixes #1596 + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` + WHERE `domainid` = :domainid"); + Database::pexecute($del_stmt, array( + 'domainid' => $id + ), true, true); + + // remove possible existing DNS entries + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_DOMAIN_DNS . "` + WHERE `domain_id` = :domainid + "); + Database::pexecute($del_stmt, array( + 'domainid' => $id + ), true, true); + + triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $this->logger()); + + $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] deleted domain/subdomains (#" . $result['id'] . ")"); + updateCounters(); + inserttask('1'); + // Using nameserver, insert a task which rebuilds the server config + inserttask('4'); + return $this->response(200, "successfull", $result); + } + throw new Exception("Not allowed to execute given command.", 403); + } +} From 350e3d733a79e87c6b13768609cb6af48d85ae3c Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Fri, 16 Feb 2018 09:22:34 +0100 Subject: [PATCH 012/746] do not check for options if field is disabled, unset enabled-ownvhost flags for fcgid/fpm if the corresponding other one is activated; fixes #518 Signed-off-by: Michael Kaufmann (d00p) --- .../option/function.validateFormFieldOption.php | 2 +- .../validate/function.checkFcgidPhpFpm.php | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/functions/formfields/option/function.validateFormFieldOption.php b/lib/functions/formfields/option/function.validateFormFieldOption.php index cbda0408..01b4be5f 100644 --- a/lib/functions/formfields/option/function.validateFormFieldOption.php +++ b/lib/functions/formfields/option/function.validateFormFieldOption.php @@ -34,7 +34,7 @@ function validateFormFieldOption($fieldname, $fielddata, $newfieldvalue) $returnvalue = isset($fielddata['option_options'][$newfieldvalue]); } - if($returnvalue === true) + if($returnvalue === true || $fielddata['visible'] == false) { return true; } diff --git a/lib/functions/validate/function.checkFcgidPhpFpm.php b/lib/functions/validate/function.checkFcgidPhpFpm.php index c0431f0d..add4e3df 100644 --- a/lib/functions/validate/function.checkFcgidPhpFpm.php +++ b/lib/functions/validate/function.checkFcgidPhpFpm.php @@ -24,12 +24,14 @@ function checkFcgidPhpFpm($fieldname, $fielddata, $newfieldvalue, $allnewfieldva 'system_mod_fcgid_enabled' => array( 'other_post_field' => 'system_phpfpm_enabled', 'other_enabled' => 'phpfpm.enabled', - 'other_enabled_lng' => 'phpfpmstillenabled' + 'other_enabled_lng' => 'phpfpmstillenabled', + 'deactivate' => array('phpfpm.enabled_ownvhost' => 0) ), 'system_phpfpm_enabled' => array( 'other_post_field' => 'system_mod_fcgid_enabled', 'other_enabled' => 'system.mod_fcgid', - 'other_enabled_lng' => 'fcgidstillenabled' + 'other_enabled_lng' => 'fcgidstillenabled', + 'deactivate' => array('system.mod_fcgid_ownvhost' => 0) ) ); @@ -56,6 +58,13 @@ function checkFcgidPhpFpm($fieldname, $fielddata, $newfieldvalue, $allnewfieldva } } } + if (in_array(FORMFIELDS_PLAUSIBILITY_CHECK_OK, $returnvalue)) { + // be sure to deactivate the other one for the froxlor-vhost + // to avoid having a settings-deadlock + foreach ($check_array[$fieldname]['deactivate'] as $setting => $value) { + Settings::Set($setting, $value, true); + } + } } return $returnvalue; From 2da2912c9c686ba06957133929e21d28f1b702db Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Fri, 16 Feb 2018 10:56:27 +0100 Subject: [PATCH 013/746] set update-check-urls to api-version; started working on Customers-ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- admin_autoupdate.php | 2 +- admin_index.php | 2 +- .../froxlor/{0.9 => 0.10}/update_0.10.inc.php | 0 lib/classes/api/commands/class.Customers.php | 98 +++++++++++++++++++ .../api/commands/class.IpsAndPorts.php | 10 +- 5 files changed, 104 insertions(+), 8 deletions(-) rename install/updates/froxlor/{0.9 => 0.10}/update_0.10.inc.php (100%) create mode 100644 lib/classes/api/commands/class.Customers.php diff --git a/admin_autoupdate.php b/admin_autoupdate.php index 701e8011..ec3242fe 100644 --- a/admin_autoupdate.php +++ b/admin_autoupdate.php @@ -21,7 +21,7 @@ define('AREA', 'admin'); require './lib/init.php'; // define update-uri -define('UPDATE_URI', "https://version.froxlor.org/Froxlor/legacy/" . $version); +define('UPDATE_URI', "https://version.froxlor.org/Froxlor/api/" . $version); define('RELEASE_URI', "https://autoupdate.froxlor.org/froxlor-{version}.zip"); define('CHECKSUM_URI', "https://autoupdate.froxlor.org/froxlor-{version}.zip.sha256"); diff --git a/admin_index.php b/admin_index.php index 4650a24e..174f65a0 100644 --- a/admin_index.php +++ b/admin_index.php @@ -85,7 +85,7 @@ if ($page == 'overview') { if ((isset($_GET['lookfornewversion']) && $_GET['lookfornewversion'] == 'yes') || (isset($lookfornewversion) && $lookfornewversion == 'yes') ) { - $update_check_uri = 'http://version.froxlor.org/Froxlor/legacy/' . $version; + $update_check_uri = 'http://version.froxlor.org/Froxlor/api/' . $version; $latestversion = HttpClient::urlGet($update_check_uri); $latestversion = explode('|', $latestversion); diff --git a/install/updates/froxlor/0.9/update_0.10.inc.php b/install/updates/froxlor/0.10/update_0.10.inc.php similarity index 100% rename from install/updates/froxlor/0.9/update_0.10.inc.php rename to install/updates/froxlor/0.10/update_0.10.inc.php diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php new file mode 100644 index 00000000..2d30d2e7 --- /dev/null +++ b/lib/classes/api/commands/class.Customers.php @@ -0,0 +1,98 @@ +isAdmin()) { + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] list customers"); + $result_stmt = Database::prepare(" + SELECT `c`.*, `a`.`loginname` AS `adminname` + FROM `" . TABLE_PANEL_CUSTOMERS . "` `c`, `" . TABLE_PANEL_ADMINS . "` `a` + WHERE " . ($this->getUserDetail('customers_see_all') ? '' : " `c`.`adminid` = :adminid AND ") . " + `c`.`adminid` = `a`.`adminid` + "); + $params = array(); + if ($this->getUserDetail('customers_see_all') == '0') { + $params = array( + 'adminid' => $this->getUserDetail('adminid') + ); + } + Database::pexecute($result_stmt, $params, true, true); + $result = array(); + while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { + $result[] = $row; + } + return $this->response(200, "successfull", array( + 'count' => count($result), + 'list' => $result + )); + } + throw new Exception("Not allowed to execute given command.", 403); + } + + public function get() + { + if ($this->isAdmin()) { + $id = $this->getParam('id'); + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] get customer #" . $id); + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_CUSTOMERS . "` + WHERE `customerid` = :id" . ($this->getUserDetail('customers_see_all') ? '' : " AND `adminid` = :adminid")); + $params = array( + 'id' => $id + ); + if ($this->getUserDetail('customers_see_all') == '0') { + $params['adminid'] = $this->getUserDetail('adminid'); + } + $result = Database::pexecute_first($result_stmt, $params, true, true); + if ($result) { + return $this->response(200, "successfull", $result); + } + throw new Exception("Customer with id #" . $id . " could not be found"); + } + throw new Exception("Not allowed to execute given command.", 403); + } + + public function add() + { + if ($this->isAdmin()) { + $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] added customer '" . $loginname . "'"); + return $this->response(200, "successfull", $ins_data); + } + throw new Exception("Not allowed to execute given command.", 403); + } + + public function update() + { + if ($this->isAdmin()) { + $id = $this->getParam('id'); + + $json_result = Customers::getLocal($this->getUserData(), array( + 'id' => $id + ))->get(); + $result = json_decode($json_result, true)['data']; + + $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] changed customer '" . $result['loginname'] . "'"); + return $this->response(200, "successfull", $upd_data); + } + throw new Exception("Not allowed to execute given command.", 403); + } + + public function delete() + { + if ($this->isAdmin()) { + $id = $this->getParam('id'); + + $json_result = Customers::getLocal($this->getUserData(), array( + 'id' => $id + ))->get(); + $result = json_decode($json_result, true)['data']; + + $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] deleted customer '" . $result['loginname'] . "'"); + return $this->response(200, "successfull", $result); + } + throw new Exception("Not allowed to execute given command.", 403); + } +} diff --git a/lib/classes/api/commands/class.IpsAndPorts.php b/lib/classes/api/commands/class.IpsAndPorts.php index 309f15b2..94d27115 100644 --- a/lib/classes/api/commands/class.IpsAndPorts.php +++ b/lib/classes/api/commands/class.IpsAndPorts.php @@ -10,7 +10,7 @@ class IpsAndPorts extends ApiCommand $result_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_IPSANDPORTS . "` ORDER BY `ip` ASC, `port` ASC "); - Database::pexecute($result_stmt); + Database::pexecute($result_stmt, null, true, true); $result = array(); while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { $result[] = $row; @@ -174,12 +174,10 @@ class IpsAndPorts extends ApiCommand if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { $id = $this->getParam('id'); - $result_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :id - "); - $result = Database::pexecute_first($result_stmt, array( + $json_result = IpsAndPorts::getLocal($this->getUserData(), array( 'id' => $id - ), true, true); + ))->get(); + $result = json_decode($json_result, true)['data']; $ip = validate_ip2($this->getParam('ip', $result['ip']), false, 'invalidip', false, false, false, true); $port = validate($this->getParam('port', $result['port']), 'port', '/^(([1-9])|([1-9][0-9])|([1-9][0-9][0-9])|([1-9][0-9][0-9][0-9])|([1-5][0-9][0-9][0-9][0-9])|(6[0-4][0-9][0-9][0-9])|(65[0-4][0-9][0-9])|(655[0-2][0-9])|(6553[0-5]))$/Di', array( From 1e45da2410ebe8f97c8032478a874f0e9cd0a0ad Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Fri, 16 Feb 2018 13:27:31 +0100 Subject: [PATCH 014/746] more work on Customer-ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- admin_customers.php | 994 +++--------------- lib/classes/api/abstract.ApiCommand.php | 54 +- lib/classes/api/commands/class.Customers.php | 827 ++++++++++++++- lib/classes/api/commands/class.Domains.php | 6 +- .../validate/function.validatePassword.php | 26 +- 5 files changed, 1018 insertions(+), 889 deletions(-) diff --git a/admin_customers.php b/admin_customers.php index 1ccb9a19..7e0aae18 100644 --- a/admin_customers.php +++ b/admin_customers.php @@ -152,16 +152,14 @@ if ($page == 'customers' } elseif($action == 'su' && $id != 0 ) { - $result_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_CUSTOMERS . "` - WHERE `customerid` = :id" . - ($userinfo['customers_see_all'] ? '' : " AND `adminid` = :adminid") - ); - $params = array('id' => $id); - if ($userinfo['customers_see_all'] == '0') { - $params['adminid'] = $userinfo['adminid']; + try { + $json_result = Customers::getLocal($userinfo, array( + 'id' => $id + ))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - $result = Database::pexecute_first($result_stmt, $params); + $result = json_decode($json_result, true)['data']; $destination_user = $result['loginname']; @@ -212,891 +210,133 @@ if ($page == 'customers' } elseif($action == 'unlock' && $id != 0 ) { - $result_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_CUSTOMERS . "` - WHERE `customerid` = :id" . - ($userinfo['customers_see_all'] ? '' : " AND `adminid` = :adminid") - ); - $result_data = array('id' => $id); - if ($userinfo['customers_see_all'] == '0') { - $result_data['adminid'] = $userinfo['adminid']; + try { + $json_result = Customers::getLocal($userinfo, array( + 'id' => $id + ))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - $result = Database::pexecute_first($result_stmt, $result_data); + $result = json_decode($json_result, true)['data']; - if ($result['loginname'] != '') { - - if (isset($_POST['send']) - && $_POST['send'] == 'send' - ) { - $result_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET - `loginfail_count` = '0' - WHERE `customerid`= :id" - ); - Database::pexecute($result_stmt, array('id' => $id)); - redirectTo($filename, array('page' => $page, 's' => $s)); - - } else { - ask_yesno('customer_reallyunlock', $filename, array('id' => $id, 'page' => $page, 'action' => $action), $result['loginname']); + if (isset($_POST['send']) + && $_POST['send'] == 'send' + ) { + try { + $json_result = Customers::getLocal($userinfo, array( + 'id' => $id + ))->unlock(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } + redirectTo($filename, array('page' => $page, 's' => $s)); + } else { + ask_yesno('customer_reallyunlock', $filename, array('id' => $id, 'page' => $page, 'action' => $action), $result['loginname']); } } elseif ($action == 'delete' && $id != 0 ) { - $result_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_CUSTOMERS . "` - WHERE `customerid` = :id" . - ($userinfo['customers_see_all'] ? '' : " AND `adminid` = :adminid") - ); - $params = array('id' => $id); - if ($userinfo['customers_see_all'] == '0') { - $params['adminid'] = $userinfo['adminid']; + try { + $json_result = Customers::getLocal($userinfo, array( + 'id' => $id + ))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - $result = Database::pexecute_first($result_stmt, $params); + $result = json_decode($json_result, true)['data']; - if ($result['loginname'] != '') { - - if (isset($_POST['send']) - && $_POST['send'] == 'send' - ) { - $databases_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_DATABASES . "` - WHERE `customerid` = :id ORDER BY `dbserver`" - ); - Database::pexecute($databases_stmt, array('id' => $id)); - Database::needRoot(true); - $last_dbserver = 0; - - $dbm = new DbManager($log); - - while ($row_database = $databases_stmt->fetch(PDO::FETCH_ASSOC)) { - - if ($last_dbserver != $row_database['dbserver']) { - Database::needRoot(true, $row_database['dbserver']); - $dbm->getManager()->flushPrivileges(); - $last_dbserver = $row_database['dbserver']; - } - - $dbm->getManager()->deleteDatabase($row_database['databasename']); - } - - $dbm->getManager()->flushPrivileges(); - Database::needRoot(false); - $stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `customerid` = :id"); - Database::pexecute($stmt, array('id' => $id)); - $stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_DATABASES . "` WHERE `customerid` = :id"); - Database::pexecute($stmt, array('id' => $id)); - // first gather all domain-id's to clean up panel_domaintoip and dns-entries accordingly - $did_stmt = Database::prepare("SELECT `id` FROM `".TABLE_PANEL_DOMAINS."` WHERE `customerid` = :id"); - Database::pexecute($did_stmt, array('id' => $id)); - while ($row = $did_stmt->fetch(PDO::FETCH_ASSOC)) { - $stmt = Database::prepare("DELETE FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :did"); - Database::pexecute($stmt, array('did' => $row['id'])); - $stmt = Database::prepare("DELETE FROM `" . TABLE_DOMAIN_DNS . "` WHERE `domain_id` = :did"); - Database::pexecute($stmt, array('did' => $row['id'])); - } - $stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `customerid` = :id"); - Database::pexecute($stmt, array('id' => $id)); - $domains_deleted = $stmt->rowCount(); - $stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_HTPASSWDS . "` WHERE `customerid` = :id"); - Database::pexecute($stmt, array('id' => $id)); - $stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_HTACCESS . "` WHERE `customerid` = :id"); - Database::pexecute($stmt, array('id' => $id)); - $stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_SESSIONS . "` WHERE `userid` = :id AND `adminsession` = '0'"); - Database::pexecute($stmt, array('id' => $id)); - $stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_TRAFFIC . "` WHERE `customerid` = :id"); - Database::pexecute($stmt, array('id' => $id)); - $stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_DISKSPACE . "` WHERE `customerid` = :id"); - Database::pexecute($stmt, array('id' => $id)); - $stmt = Database::prepare("DELETE FROM `" . TABLE_MAIL_USERS . "` WHERE `customerid` = :id"); - Database::pexecute($stmt, array('id' => $id)); - $stmt = Database::prepare("DELETE FROM `" . TABLE_MAIL_VIRTUAL . "` WHERE `customerid` = :id"); - Database::pexecute($stmt, array('id' => $id)); - $result2_stmt = Database::prepare("SELECT `username` FROM `" . TABLE_FTP_USERS . "` WHERE `customerid` = :id"); - Database::pexecute($result2_stmt, array('id' => $id)); - while ($row = $result2_stmt->fetch(PDO::FETCH_ASSOC)) { - $stmt = Database::prepare("DELETE FROM `" . TABLE_FTP_QUOTATALLIES . "` WHERE `name` = :name"); - Database::pexecute($stmt, array('name' => $row['username'])); - } - $stmt = Database::prepare("DELETE FROM `" . TABLE_FTP_GROUPS . "` WHERE `customerid` = :id"); - Database::pexecute($stmt, array('id' => $id)); - $stmt = Database::prepare("DELETE FROM `" . TABLE_FTP_USERS . "` WHERE `customerid` = :id"); - Database::pexecute($stmt, array('id' => $id)); - - // Delete all waiting "create user" -tasks for this user, #276 - // Note: the WHERE selects part of a serialized array, but it should be safe this way - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_PANEL_TASKS . "` - WHERE `type` = '2' AND `data` LIKE :loginname" - ); - Database::pexecute($del_stmt, array('loginname' => "%:{$result['loginname']};%")); - - $admin_update_query = "UPDATE `" . TABLE_PANEL_ADMINS . "` SET `customers_used` = `customers_used` - 1 "; - $admin_update_query.= ", `domains_used` = `domains_used` - 0" . (int)($domains_deleted - $result['subdomains_used']); - - if ($result['mysqls'] != '-1') { - $admin_update_query.= ", `mysqls_used` = `mysqls_used` - 0" . (int)$result['mysqls']; - } - - if ($result['emails'] != '-1') { - $admin_update_query.= ", `emails_used` = `emails_used` - 0" . (int)$result['emails']; - } - - if ($result['email_accounts'] != '-1') { - $admin_update_query.= ", `email_accounts_used` = `email_accounts_used` - 0" . (int)$result['email_accounts']; - } - - if ($result['email_forwarders'] != '-1') { - $admin_update_query.= ", `email_forwarders_used` = `email_forwarders_used` - 0" . (int)$result['email_forwarders']; - } - - if ($result['email_quota'] != '-1') { - $admin_update_query.= ", `email_quota_used` = `email_quota_used` - 0" . (int)$result['email_quota']; - } - - if ($result['subdomains'] != '-1') { - $admin_update_query.= ", `subdomains_used` = `subdomains_used` - 0" . (int)$result['subdomains']; - } - - if ($result['ftps'] != '-1') { - $admin_update_query.= ", `ftps_used` = `ftps_used` - 0" . (int)$result['ftps']; - } - - if ($result['tickets'] != '-1') { - $admin_update_query.= ", `tickets_used` = `tickets_used` - 0" . (int)$result['tickets']; - } - - if (($result['diskspace'] / 1024) != '-1') { - $admin_update_query.= ", `diskspace_used` = `diskspace_used` - 0" . (int)$result['diskspace']; - } - - $admin_update_query.= " WHERE `adminid` = '" . (int)$result['adminid'] . "'"; - Database::query($admin_update_query); - $log->logAction(ADM_ACTION, LOG_INFO, "deleted user '" . $result['loginname'] . "'"); - inserttask('1'); - - // Using nameserver, insert a task which rebuilds the server config - inserttask('4'); - - if (isset($_POST['delete_userfiles']) - && (int)$_POST['delete_userfiles'] == 1 - ) { - inserttask('6', $result['loginname']); - } - - // Using filesystem - quota, insert a task which cleans the filesystem - quota - inserttask('10'); - - /* - * move old tickets to archive - */ - $tickets = ticket::customerHasTickets($id); - if ($tickets !== false && isset($tickets[0])) { - foreach ($tickets as $ticket) { - $now = time(); - $mainticket = ticket::getInstanceOf($userinfo, (int)$ticket); - $mainticket->Set('lastchange', $now, true, true); - $mainticket->Set('lastreplier', '1', true, true); - $mainticket->Set('status', '3', true, true); - $mainticket->Update(); - $mainticket->Archive(); - $log->logAction(ADM_ACTION, LOG_NOTICE, "archived ticket '" . $mainticket->Get('subject') . "'"); - } - } - redirectTo($filename, array('page' => $page, 's' => $s)); - - } else { - ask_yesno_withcheckbox('admin_customer_reallydelete', 'admin_customer_alsoremovefiles', $filename, array('id' => $id, 'page' => $page, 'action' => $action), $result['loginname']); + if (isset($_POST['send']) + && $_POST['send'] == 'send' + ) { + try { + $json_result = Customers::getLocal($userinfo, array( + 'id' => $id, + 'delete_userfiles' => (isset($_POST['delete_userfiles']) ? (int)$_POST['delete_userfiles'] : 0) + ))->delete(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } + redirectTo($filename, array('page' => $page, 's' => $s)); + + } else { + ask_yesno_withcheckbox('admin_customer_reallydelete', 'admin_customer_alsoremovefiles', $filename, array('id' => $id, 'page' => $page, 'action' => $action), $result['loginname']); } } elseif($action == 'add') { - if ($userinfo['customers_used'] < $userinfo['customers'] - || $userinfo['customers'] == '-1' + if (isset($_POST['send']) + && $_POST['send'] == 'send' ) { - if (isset($_POST['send']) - && $_POST['send'] == 'send' - ) { - $name = validate($_POST['name'], 'name'); - $firstname = validate($_POST['firstname'], 'first name'); - $company = validate($_POST['company'], 'company'); - $street = validate($_POST['street'], 'street'); - $zipcode = validate($_POST['zipcode'], 'zipcode', '/^[0-9 \-A-Z]*$/'); - $city = validate($_POST['city'], 'city'); - $phone = validate($_POST['phone'], 'phone', '/^[0-9\- \+\(\)\/]*$/'); - $fax = validate($_POST['fax'], 'fax', '/^[0-9\- \+\(\)\/]*$/'); - $email = $idna_convert->encode(validate($_POST['email'], 'email')); - $customernumber = validate($_POST['customernumber'], 'customer number', '/^[A-Za-z0-9 \-]*$/Di'); - $def_language = validate($_POST['def_language'], 'default language'); - $gender = intval_ressource($_POST['gender']); - - $custom_notes = validate(str_replace("\r\n", "\n", $_POST['custom_notes']), 'custom_notes', '/^[^\0]*$/'); - $custom_notes_show = 0; - if (isset($_POST['custom_notes_show'])) { - $custom_notes_show = intval_ressource($_POST['custom_notes_show']); - } - - $diskspace = intval_ressource($_POST['diskspace']); - if (isset($_POST['diskspace_ul'])) { - $diskspace = - 1; - } - - $traffic = doubleval_ressource($_POST['traffic']); - if (isset($_POST['traffic_ul'])) { - $traffic = - 1; - } - - $subdomains = intval_ressource($_POST['subdomains']); - if (isset($_POST['subdomains_ul'])) { - $subdomains = - 1; - } - - $emails = intval_ressource($_POST['emails']); - if (isset($_POST['emails_ul'])) { - $emails = - 1; - } - - $email_accounts = intval_ressource($_POST['email_accounts']); - if (isset($_POST['email_accounts_ul'])) { - $email_accounts = - 1; - } - - $email_forwarders = intval_ressource($_POST['email_forwarders']); - if (isset($_POST['email_forwarders_ul'])) { - $email_forwarders = - 1; - } - - if (Settings::Get('system.mail_quota_enabled') == '1') { - $email_quota = validate($_POST['email_quota'], 'email_quota', '/^\d+$/', 'vmailquotawrong', array('0', '')); - if (isset($_POST['email_quota_ul'])) { - $email_quota = - 1; - } - } else { - $email_quota = - 1; - } - - $email_imap = 0; - if (isset($_POST['email_imap'])) { - $email_imap = intval_ressource($_POST['email_imap']); - } - - $email_pop3 = 0; - if (isset($_POST['email_pop3'])) { - $email_pop3 = intval_ressource($_POST['email_pop3']); - } - - $ftps = 0; - if (isset($_POST['ftps'])) { - $ftps = intval_ressource($_POST['ftps']); - } - if (isset($_POST['ftps_ul'])) { - $ftps = - 1; - } - - $tickets = (Settings::Get('ticket.enabled') == 1 ? intval_ressource($_POST['tickets']) : 0); - if (isset($_POST['tickets_ul']) - && Settings::Get('ticket.enabled') == '1' - ) { - $tickets = - 1; - } - - $mysqls = intval_ressource($_POST['mysqls']); - if (isset($_POST['mysqls_ul'])) { - $mysqls = - 1; - } - - $createstdsubdomain = 0; - if(isset($_POST['createstdsubdomain'])) { - $createstdsubdomain = intval($_POST['createstdsubdomain']); - } - - $password = validate($_POST['new_customer_password'], 'password'); - // only check if not empty, - // cause empty == generate password automatically - if ($password != '') { - $password = validatePassword($password); - } - - // gender out of range? [0,2] - if ($gender < 0 || $gender > 2) { - $gender = 0; - } - - $sendpassword = 0; - if (isset($_POST['sendpassword'])) { - $sendpassword = intval($_POST['sendpassword']); - } - - $phpenabled = 0; - if (isset($_POST['phpenabled'])) { - $phpenabled = intval($_POST['phpenabled']); - } - - $allowed_phpconfigs = array(); - if (isset($_POST['allowed_phpconfigs']) && is_array($_POST['allowed_phpconfigs'])) { - foreach ($_POST['allowed_phpconfigs'] as $allowed_phpconfig) { - $allowed_phpconfig = intval($allowed_phpconfig); - $allowed_phpconfigs[] = $allowed_phpconfig; - } - } - - $perlenabled = 0; - if (isset($_POST['perlenabled'])) { - $perlenabled = intval($_POST['perlenabled']); - } - - $dnsenabled = 0; - if (isset($_POST['dnsenabled'])) { - $dnsenabled = intval($_POST['dnsenabled']); - } - - $store_defaultindex = 0; - if (isset($_POST['store_defaultindex'])) { - $store_defaultindex = intval($_POST['store_defaultindex']); - } - - $diskspace = $diskspace * 1024; - $traffic = $traffic * 1024 * 1024; - - if (((($userinfo['diskspace_used'] + $diskspace) > $userinfo['diskspace']) && ($userinfo['diskspace'] / 1024) != '-1') - || ((($userinfo['mysqls_used'] + $mysqls) > $userinfo['mysqls']) && $userinfo['mysqls'] != '-1') - || ((($userinfo['emails_used'] + $emails) > $userinfo['emails']) && $userinfo['emails'] != '-1') - || ((($userinfo['email_accounts_used'] + $email_accounts) > $userinfo['email_accounts']) && $userinfo['email_accounts'] != '-1') - || ((($userinfo['email_forwarders_used'] + $email_forwarders) > $userinfo['email_forwarders']) && $userinfo['email_forwarders'] != '-1') - || ((($userinfo['email_quota_used'] + $email_quota) > $userinfo['email_quota']) && $userinfo['email_quota'] != '-1' && Settings::Get('system.mail_quota_enabled') == '1') - || ((($userinfo['ftps_used'] + $ftps) > $userinfo['ftps']) && $userinfo['ftps'] != '-1') - || ((($userinfo['tickets_used'] + $tickets) > $userinfo['tickets']) && $userinfo['tickets'] != '-1') - || ((($userinfo['subdomains_used'] + $subdomains) > $userinfo['subdomains']) && $userinfo['subdomains'] != '-1') - || (($diskspace / 1024) == '-1' && ($userinfo['diskspace'] / 1024) != '-1') - || ($mysqls == '-1' && $userinfo['mysqls'] != '-1') - || ($emails == '-1' && $userinfo['emails'] != '-1') - || ($email_accounts == '-1' && $userinfo['email_accounts'] != '-1') - || ($email_forwarders == '-1' && $userinfo['email_forwarders'] != '-1') - || ($email_quota == '-1' && $userinfo['email_quota'] != '-1' && Settings::Get('system.mail_quota_enabled') == '1') - || ($ftps == '-1' && $userinfo['ftps'] != '-1') - || ($tickets == '-1' && $userinfo['tickets'] != '-1') - || ($subdomains == '-1' && $userinfo['subdomains'] != '-1') - ) { - standard_error('youcantallocatemorethanyouhave'); - } - - // Either $name and $firstname or the $company must be inserted - if ($name == '' && $company == '') { - standard_error(array('stringisempty', 'myname')); - - } elseif($firstname == '' && $company == '') { - standard_error(array('stringisempty', 'myfirstname')); - - } elseif($email == '') { - standard_error(array('stringisempty', 'emailadd')); - - } elseif(!validateEmail($email)) { - standard_error('emailiswrong', $email); - - } else { - - if (isset($_POST['new_loginname']) - && $_POST['new_loginname'] != '' - ) { - $accountnumber = intval(Settings::Get('system.lastaccountnumber')); - $loginname = validate($_POST['new_loginname'], 'loginname', '/^[a-z][a-z0-9\-_]+$/i'); - - // Accounts which match systemaccounts are not allowed, filtering them - if (preg_match('/^' . preg_quote(Settings::Get('customer.accountprefix'), '/') . '([0-9]+)/', $loginname)) { - standard_error('loginnameissystemaccount', Settings::Get('customer.accountprefix')); - } - - // Additional filtering for Bug #962 - if (function_exists('posix_getpwnam') - && !in_array("posix_getpwnam", explode(",", ini_get('disable_functions'))) - && posix_getpwnam($loginname) - ) { - standard_error('loginnameissystemaccount', Settings::Get('customer.accountprefix')); - } - - } else { - $accountnumber = intval(Settings::Get('system.lastaccountnumber')) + 1; - $loginname = Settings::Get('customer.accountprefix') . $accountnumber; - } - - // Check if the account already exists - $loginname_check_stmt = Database::prepare(" - SELECT `loginname` FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `loginname` = :loginname" - ); - $loginname_check = Database::pexecute_first($loginname_check_stmt, array('loginname' => $loginname)); - - $loginname_check_admin_stmt = Database::prepare(" - SELECT `loginname` FROM `" . TABLE_PANEL_ADMINS . "` WHERE `loginname` = :loginname" - ); - $loginname_check_admin = Database::pexecute_first($loginname_check_admin_stmt, array('loginname' => $loginname)); - - if (strtolower($loginname_check['loginname']) == strtolower($loginname) - || strtolower($loginname_check_admin['loginname']) == strtolower($loginname) - ) { - standard_error('loginnameexists', $loginname); - - } elseif (!validateUsername($loginname, Settings::Get('panel.unix_names'), 14 - strlen(Settings::Get('customer.mysqlprefix')))) { - if (strlen($loginname) > 14 - strlen(Settings::Get('customer.mysqlprefix'))) { - standard_error('loginnameiswrong2', 14 - strlen(Settings::Get('customer.mysqlprefix'))); - } else { - standard_error('loginnameiswrong', $loginname); - } - } - - $guid = intval(Settings::Get('system.lastguid')) + 1; - $documentroot = makeCorrectDir(Settings::Get('system.documentroot_prefix') . '/' . $loginname); - - if (file_exists($documentroot)) { - standard_error('documentrootexists', $documentroot); - } - - if ($createstdsubdomain != '1') { - $createstdsubdomain = '0'; - } - - if ($phpenabled != '0') { - $phpenabled = '1'; - } - - if ($perlenabled != '0') { - $perlenabled = '1'; - } - - if ($dnsenabled != '0') { - $dnsenabled = '1'; - } - - if ($password == '') { - $password = generatePassword(); - } - - $_theme = Settings::Get('panel.default_theme'); - - $ins_data = array( - 'adminid' => $userinfo['adminid'], - 'loginname' => $loginname, - 'passwd' => makeCryptPassword($password), - 'name' => $name, - 'firstname' => $firstname, - 'gender' => $gender, - 'company' => $company, - 'street' => $street, - 'zipcode' => $zipcode, - 'city' => $city, - 'phone' => $phone, - 'fax' => $fax, - 'email' => $email, - 'customerno' => $customernumber, - 'lang' => $def_language, - 'docroot' => $documentroot, - 'guid' => $guid, - 'diskspace' => $diskspace, - 'traffic' => $traffic, - 'subdomains' => $subdomains, - 'emails' => $emails, - 'email_accounts' => $email_accounts, - 'email_forwarders' => $email_forwarders, - 'email_quota' => $email_quota, - 'ftps' => $ftps, - 'tickets' => $tickets, - 'mysqls' => $mysqls, - 'phpenabled' => $phpenabled, - 'allowed_phpconfigs' => empty($allowed_phpconfigs) ? "" : json_encode($allowed_phpconfigs), - 'imap' => $email_imap, - 'pop3' => $email_pop3, - 'perlenabled' => $perlenabled, - 'dnsenabled' => $dnsenabled, - 'theme' => $_theme, - 'custom_notes' => $custom_notes, - 'custom_notes_show' => $custom_notes_show - ); - - $ins_stmt = Database::prepare(" - INSERT INTO `" . TABLE_PANEL_CUSTOMERS . "` SET - `adminid` = :adminid, - `loginname` = :loginname, - `password` = :passwd, - `name` = :name, - `firstname` = :firstname, - `gender` = :gender, - `company` = :company, - `street` = :street, - `zipcode` = :zipcode, - `city` = :city, - `phone` = :phone, - `fax` = :fax, - `email` = :email, - `customernumber` = :customerno, - `def_language` = :lang, - `documentroot` = :docroot, - `guid` = :guid, - `diskspace` = :diskspace, - `traffic` = :traffic, - `subdomains` = :subdomains, - `emails` = :emails, - `email_accounts` = :email_accounts, - `email_forwarders` = :email_forwarders, - `email_quota` = :email_quota, - `ftps` = :ftps, - `tickets` = :tickets, - `mysqls` = :mysqls, - `standardsubdomain` = '0', - `phpenabled` = :phpenabled, - `allowed_phpconfigs` = :allowed_phpconfigs, - `imap` = :imap, - `pop3` = :pop3, - `perlenabled` = :perlenabled, - `dnsenabled` = :dnsenabled, - `theme` = :theme, - `custom_notes` = :custom_notes, - `custom_notes_show` = :custom_notes_show" - ); - Database::pexecute($ins_stmt, $ins_data); - - $customerid = Database::lastInsertId(); - - $admin_update_query = "UPDATE `" . TABLE_PANEL_ADMINS . "` SET `customers_used` = `customers_used` + 1"; - - if ($mysqls != '-1') { - $admin_update_query.= ", `mysqls_used` = `mysqls_used` + 0" . (int)$mysqls; - } - - if ($emails != '-1') { - $admin_update_query.= ", `emails_used` = `emails_used` + 0" . (int)$emails; - } - - if ($email_accounts != '-1') { - $admin_update_query.= ", `email_accounts_used` = `email_accounts_used` + 0" . (int)$email_accounts; - } - - if ($email_forwarders != '-1') { - $admin_update_query.= ", `email_forwarders_used` = `email_forwarders_used` + 0" . (int)$email_forwarders; - } - - if ($email_quota != '-1') { - $admin_update_query.= ", `email_quota_used` = `email_quota_used` + 0" . (int)$email_quota; - } - - if ($subdomains != '-1') { - $admin_update_query.= ", `subdomains_used` = `subdomains_used` + 0" . (int)$subdomains; - } - - if ($ftps != '-1') { - $admin_update_query.= ", `ftps_used` = `ftps_used` + 0" . (int)$ftps; - } - - if ($tickets != '-1' - && Settings::Get('ticket.enabled') == 1 - ) { - $admin_update_query.= ", `tickets_used` = `tickets_used` + 0" . (int)$tickets; - } - - if (($diskspace / 1024) != '-1') { - $admin_update_query.= ", `diskspace_used` = `diskspace_used` + 0" . (int)$diskspace; - } - - $admin_update_query.= " WHERE `adminid` = '" . (int)$userinfo['adminid'] . "'"; - Database::query($admin_update_query); - - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_SETTINGS . "` SET - `value` = :guid - WHERE `settinggroup` = 'system' AND `varname` = 'lastguid'" - ); - Database::pexecute($upd_stmt, array('guid' => $guid)); - - if ($accountnumber != intval(Settings::Get('system.lastaccountnumber'))) { - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_SETTINGS . "` SET - `value` = :accno - WHERE `settinggroup` = 'system' AND `varname` = 'lastaccountnumber'" - ); - Database::pexecute($upd_stmt, array('accno' => $accountnumber)); - } - - $log->logAction(ADM_ACTION, LOG_INFO, "added user '" . $loginname . "'"); - inserttask('2', $loginname, $guid, $guid, $store_defaultindex); - - // Using filesystem - quota, insert a task which cleans the filesystem - quota - inserttask('10'); - - // Add htpasswd for the webalizer stats - if (CRYPT_STD_DES == 1) { - $saltfordescrypt = substr(md5(uniqid(microtime(), 1)), 4, 2); - $htpasswdPassword = crypt($password, $saltfordescrypt); - } else { - $htpasswdPassword = crypt($password); - } - - $ins_stmt = Database::prepare(" - INSERT INTO `" . TABLE_PANEL_HTPASSWDS . "` SET - `customerid` = :customerid, - `username` = :username, - `password` = :passwd, - `path` = :path" - ); - $ins_data = array( - 'customerid' => $customerid, - 'username' => $loginname, - 'passwd' => $htpasswdPassword - ); - - if (Settings::Get('system.awstats_enabled') == '1') { - $ins_data['path'] = makeCorrectDir($documentroot . '/awstats/'); - $log->logAction(ADM_ACTION, LOG_NOTICE, "automatically added awstats htpasswd for user '" . $loginname . "'"); - } else { - $ins_data['path'] = makeCorrectDir($documentroot . '/webalizer/'); - $log->logAction(ADM_ACTION, LOG_NOTICE, "automatically added webalizer htpasswd for user '" . $loginname . "'"); - } - Database::pexecute($ins_stmt, $ins_data); - - inserttask('1'); - $cryptPassword = makeCryptPassword($password); - // FTP-User - $ins_stmt = Database::prepare(" - INSERT INTO `" . TABLE_FTP_USERS . "` SET `customerid` = :customerid, `username` = :username, `description` = :desc, - `password` = :passwd, `homedir` = :homedir, `login_enabled` = 'y', `uid` = :guid, `gid` = :guid" - ); - $ins_data = array( - 'customerid' => $customerid, - 'username' => $loginname, - 'passwd' => $cryptPassword, - 'homedir' => $documentroot, - 'guid' => $guid, - 'desc' => "Default" - ); - Database::pexecute($ins_stmt, $ins_data); - // FTP-Group - $ins_stmt = Database::prepare(" - INSERT INTO `" . TABLE_FTP_GROUPS . "` SET `customerid` = :customerid, `groupname` = :groupname, `gid` = :guid, `members` = :members" - ); - $ins_data = array( - 'customerid' => $customerid, - 'groupname' => $loginname, - 'guid' => $guid, - 'members' => $loginname.','.Settings::Get('system.httpuser') - ); - - // also, add froxlor-local user to ftp-group (if exists!) to - // allow access to customer-directories from within the panel, which - // is necessary when pathedit = Dropdown - if ((int)Settings::Get('system.mod_fcgid_ownvhost') == 1 || (int)Settings::Get('phpfpm.enabled_ownvhost') == 1) { - if ((int)Settings::Get('system.mod_fcgid') == 1) { - $local_user = Settings::Get('system.mod_fcgid_httpuser'); - } else { - $local_user = Settings::Get('phpfpm.vhost_httpuser'); - } - // check froxlor-local user membership in ftp-group - // without this check addition may duplicate user in list if httpuser == local_user - if (strpos($ins_data['members'], $local_user) == false) { - $ins_data['members'] .= ','.$local_user; - } - } - - Database::pexecute($ins_stmt, $ins_data); - - // FTP-Quotatallies - $ins_stmt = Database::prepare(" - INSERT INTO `" . TABLE_FTP_QUOTATALLIES . "` SET `name` = :name, `quota_type` = 'user', `bytes_in_used` = '0', - `bytes_out_used` = '0', `bytes_xfer_used` = '0', `files_in_used` = '0', `files_out_used` = '0', `files_xfer_used` = '0'" - ); - Database::pexecute($ins_stmt, array('name' => $loginname)); - $log->logAction(ADM_ACTION, LOG_NOTICE, "automatically added ftp-account for user '" . $loginname . "'"); - - $_stdsubdomain = ''; - - if ($createstdsubdomain == '1') { - - if (Settings::Get('system.stdsubdomain') !== null - && Settings::Get('system.stdsubdomain') != '' - ) { - $_stdsubdomain = $loginname . '.' . Settings::Get('system.stdsubdomain'); - } else { - $_stdsubdomain = $loginname . '.' . Settings::Get('system.hostname'); - } - - $ins_data = array( - 'domain' => $_stdsubdomain, - 'customerid' => $customerid, - 'adminid' => $userinfo['adminid'], - 'docroot' => $documentroot, - 'adddate' => time(), - 'phpenabled' => $phpenabled - ); - $ins_stmt = Database::prepare(" - INSERT INTO `" . TABLE_PANEL_DOMAINS . "` SET - `domain` = :domain, - `customerid` = :customerid, - `adminid` = :adminid, - `parentdomainid` = '0', - `documentroot` = :docroot, - `zonefile` = '', - `isemaildomain` = '0', - `caneditdomain` = '0', - `openbasedir` = '1', - `speciallogfile` = '0', - `specialsettings` = '', - `dkim_id` = '0', - `dkim_privkey` = '', - `dkim_pubkey` = '', - `phpenabled` = :phpenabled, - `add_date` = :adddate" - ); - Database::pexecute($ins_stmt, $ins_data); - $domainid = Database::lastInsertId(); - - // set ip <-> domain connection - $defaultips = explode(',', Settings::Get('system.defaultip')); - $ins_stmt = Database::prepare(" - INSERT INTO `" . TABLE_DOMAINTOIP . "` SET `id_domain` = :domainid, `id_ipandports` = :ipid" - ); - foreach ($defaultips as $defaultip) { - Database::pexecute($ins_stmt, array('domainid' => $domainid, 'ipid' => $defaultip)); - } - - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `standardsubdomain` = :domainid WHERE `customerid` = :customerid" - ); - Database::pexecute($upd_stmt, array('domainid' => $domainid, 'customerid' => $customerid)); - $log->logAction(ADM_ACTION, LOG_NOTICE, "automatically added standardsubdomain for user '" . $loginname . "'"); - inserttask('1'); - } - - if ($sendpassword == '1') { - - $srv_hostname = Settings::Get('system.hostname'); - if (Settings::Get('system.froxlordirectlyviahostname') == '0') { - $srv_hostname .= '/froxlor'; - } - - $srv_ip_stmt = Database::prepare(" - SELECT ip, port FROM `".TABLE_PANEL_IPSANDPORTS."` - WHERE `id` = :defaultip - "); - $default_ips = Settings::Get('system.defaultip'); - $default_ips = explode(',', $default_ips); - $srv_ip = Database::pexecute_first($srv_ip_stmt, array('defaultip' => reset($default_ips))); - - $replace_arr = array( - 'FIRSTNAME' => $firstname, - 'NAME' => $name, - 'COMPANY' => $company, - 'SALUTATION' => getCorrectUserSalutation(array('firstname' => $firstname, 'name' => $name, 'company' => $company)), - 'USERNAME' => $loginname, - 'PASSWORD' => $password, - 'SERVER_HOSTNAME' => $srv_hostname, - 'SERVER_IP' => isset($srv_ip['ip']) ? $srv_ip['ip'] : '', - 'SERVER_PORT' => isset($srv_ip['port']) ? $srv_ip['port'] : '', - 'DOMAINNAME' => $_stdsubdomain - ); - - // Get mail templates from database; the ones from 'admin' are fetched for fallback - $result_stmt = Database::prepare(" - SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` - WHERE `adminid` = :adminid AND `language` = :deflang AND `templategroup` = 'mails' AND `varname` = 'createcustomer_subject'" - ); - $result = Database::pexecute_first($result_stmt, array('adminid' => $userinfo['adminid'], 'deflang' => $def_language)); - $mail_subject = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $lng['mails']['createcustomer']['subject']), $replace_arr)); - - $result_stmt = Database::prepare(" - SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` - WHERE `adminid` = :adminid AND `language` = :deflang AND `templategroup` = 'mails' AND `varname` = 'createcustomer_mailbody'" - ); - $result = Database::pexecute_first($result_stmt, array('adminid' => $userinfo['adminid'], 'deflang' => $def_language)); - $mail_body = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $lng['mails']['createcustomer']['mailbody']), $replace_arr)); - - $_mailerror = false; - try { - $mail->Subject = $mail_subject; - $mail->AltBody = $mail_body; - $mail->MsgHTML(str_replace("\n", "
", $mail_body)); - $mail->AddAddress($email, getCorrectUserSalutation(array('firstname' => $firstname, 'name' => $name, 'company' => $company))); - $mail->Send(); - } catch(phpmailerException $e) { - $mailerr_msg = $e->errorMessage(); - $_mailerror = true; - } catch (Exception $e) { - $mailerr_msg = $e->getMessage(); - $_mailerror = true; - } - - if ($_mailerror) { - $log->logAction(ADM_ACTION, LOG_ERR, "Error sending mail: " . $mailerr_msg); - standard_error('errorsendingmail', $email); - } - - $mail->ClearAddresses(); - $log->logAction(ADM_ACTION, LOG_NOTICE, "automatically sent password to user '" . $loginname . "'"); - } - redirectTo($filename, array('page' => $page, 's' => $s)); - } - - } else { - $language_options = ''; - - foreach ($languages as $language_file => $language_name) { - $language_options.= makeoption($language_name, $language_file, Settings::Get('panel.standardlanguage'), true); - } - - $diskspace_ul = makecheckbox('diskspace_ul', $lng['customer']['unlimited'], '-1', false, '0', true, true); - $traffic_ul = makecheckbox('traffic_ul', $lng['customer']['unlimited'], '-1', false, '0', true, true); - $subdomains_ul = makecheckbox('subdomains_ul', $lng['customer']['unlimited'], '-1', false, '0', true, true); - $emails_ul = makecheckbox('emails_ul', $lng['customer']['unlimited'], '-1', false, '0', true, true); - $email_accounts_ul = makecheckbox('email_accounts_ul', $lng['customer']['unlimited'], '-1', false, '0', true, true); - $email_forwarders_ul = makecheckbox('email_forwarders_ul', $lng['customer']['unlimited'], '-1', false, '0', true, true); - $email_quota_ul = makecheckbox('email_quota_ul', $lng['customer']['unlimited'], '-1', false, '0', true, true); - $ftps_ul = makecheckbox('ftps_ul', $lng['customer']['unlimited'], '-1', false, '0', true, true); - $tickets_ul = makecheckbox('tickets_ul', $lng['customer']['unlimited'], '-1', false, '0', true, true); - $mysqls_ul = makecheckbox('mysqls_ul', $lng['customer']['unlimited'], '-1', false, '0', true, true); - - $gender_options = makeoption($lng['gender']['undef'], 0, true, true, true); - $gender_options .= makeoption($lng['gender']['male'], 1, null, true, true); - $gender_options .= makeoption($lng['gender']['female'], 2, null, true, true); - - $phpconfigs = array(); - $configs = Database::query(" - SELECT c.*, fc.description as interpreter - FROM `" . TABLE_PANEL_PHPCONFIGS . "` c - LEFT JOIN `" . TABLE_PANEL_FPMDAEMONS . "` fc ON fc.id = c.fpmsettingid - "); - while ($row = $configs->fetch(PDO::FETCH_ASSOC)) { - if ((int) Settings::Get('phpfpm.enabled') == 1) { - $phpconfigs[] = array( - 'label' => $row['description'] . " [".$row['interpreter']."]
", - 'value' => $row['id'] - ); - } else { - $phpconfigs[] = array( - 'label' => $row['description']."
", - 'value' => $row['id'] - ); - } - } - - // hosting plans - $hosting_plans = ""; - $plans = Database::query(" - SELECT * - FROM `" . TABLE_PANEL_PLANS . "` - ORDER BY name ASC - "); - if (Database::num_rows() > 0){ - $hosting_plans .= makeoption("---", 0, 0, true, true); - } - while ($row = $plans->fetch(PDO::FETCH_ASSOC)) { - $hosting_plans .= makeoption($row['name'], $row['id'], 0, true, true); - } - - $customer_add_data = include_once dirname(__FILE__).'/lib/formfields/admin/customer/formfield.customer_add.php'; - $customer_add_form = htmlform::genHTMLForm($customer_add_data); - - $title = $customer_add_data['customer_add']['title']; - $image = $customer_add_data['customer_add']['image']; - - eval("echo \"" . getTemplate("customers/customers_add") . "\";"); + try { + Customers::getLocal($userinfo, $_POST)->add(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } + redirectTo($filename, array('page' => $page, 's' => $s)); + } else { + $language_options = ''; + + foreach ($languages as $language_file => $language_name) { + $language_options.= makeoption($language_name, $language_file, Settings::Get('panel.standardlanguage'), true); + } + + $diskspace_ul = makecheckbox('diskspace_ul', $lng['customer']['unlimited'], '-1', false, '0', true, true); + $traffic_ul = makecheckbox('traffic_ul', $lng['customer']['unlimited'], '-1', false, '0', true, true); + $subdomains_ul = makecheckbox('subdomains_ul', $lng['customer']['unlimited'], '-1', false, '0', true, true); + $emails_ul = makecheckbox('emails_ul', $lng['customer']['unlimited'], '-1', false, '0', true, true); + $email_accounts_ul = makecheckbox('email_accounts_ul', $lng['customer']['unlimited'], '-1', false, '0', true, true); + $email_forwarders_ul = makecheckbox('email_forwarders_ul', $lng['customer']['unlimited'], '-1', false, '0', true, true); + $email_quota_ul = makecheckbox('email_quota_ul', $lng['customer']['unlimited'], '-1', false, '0', true, true); + $ftps_ul = makecheckbox('ftps_ul', $lng['customer']['unlimited'], '-1', false, '0', true, true); + $tickets_ul = makecheckbox('tickets_ul', $lng['customer']['unlimited'], '-1', false, '0', true, true); + $mysqls_ul = makecheckbox('mysqls_ul', $lng['customer']['unlimited'], '-1', false, '0', true, true); + + $gender_options = makeoption($lng['gender']['undef'], 0, true, true, true); + $gender_options .= makeoption($lng['gender']['male'], 1, null, true, true); + $gender_options .= makeoption($lng['gender']['female'], 2, null, true, true); + + $phpconfigs = array(); + $configs = Database::query(" + SELECT c.*, fc.description as interpreter + FROM `" . TABLE_PANEL_PHPCONFIGS . "` c + LEFT JOIN `" . TABLE_PANEL_FPMDAEMONS . "` fc ON fc.id = c.fpmsettingid + "); + while ($row = $configs->fetch(PDO::FETCH_ASSOC)) { + if ((int) Settings::Get('phpfpm.enabled') == 1) { + $phpconfigs[] = array( + 'label' => $row['description'] . " [".$row['interpreter']."]
", + 'value' => $row['id'] + ); + } else { + $phpconfigs[] = array( + 'label' => $row['description']."
", + 'value' => $row['id'] + ); + } + } + + // hosting plans + $hosting_plans = ""; + $plans = Database::query(" + SELECT * + FROM `" . TABLE_PANEL_PLANS . "` + ORDER BY name ASC + "); + if (Database::num_rows() > 0){ + $hosting_plans .= makeoption("---", 0, 0, true, true); + } + while ($row = $plans->fetch(PDO::FETCH_ASSOC)) { + $hosting_plans .= makeoption($row['name'], $row['id'], 0, true, true); + } + + $customer_add_data = include_once dirname(__FILE__).'/lib/formfields/admin/customer/formfield.customer_add.php'; + $customer_add_form = htmlform::genHTMLForm($customer_add_data); + + $title = $customer_add_data['customer_add']['title']; + $image = $customer_add_data['customer_add']['image']; + + eval("echo \"" . getTemplate("customers/customers_add") . "\";"); } } elseif($action == 'edit' diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index d11347fc..10e053a8 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -9,6 +9,8 @@ abstract class ApiCommand private $logger = null; + private $mail = null; + private $cmd_params = null; public function __construct($header = null, $params = null, $userinfo = null) @@ -20,12 +22,18 @@ abstract class ApiCommand $this->readUserData($header); } elseif (! empty($userinfo)) { $this->user_data = $userinfo; - $this->is_admin = ($userinfo['adminsession'] == 1 && $userinfo['adminid'] > 0) ? true : false; + $this->is_admin = (isset($userinfo['adminsession']) && $userinfo['adminsession'] == 1 && $userinfo['adminid'] > 0) ? true : false; } else { throw new Exception("Invalid user data", 500); } $this->logger = FroxlorLogger::getInstanceOf($this->user_data); + $this->initLang(); + $this->initMail(); + } + + private function initLang() + { // query the whole table $result_stmt = Database::query("SELECT * FROM `" . TABLE_PANEL_LANGUAGE . "`"); @@ -62,6 +70,37 @@ abstract class ApiCommand include_once makeSecurePath(FROXLOR_INSTALL_DIR . '/lng/lng_references.php'); } + private function initMail() + { + /** + * Initialize the mailingsystem + */ + $this->mail = new PHPMailer(true); + $this->mail->CharSet = "UTF-8"; + + if (Settings::Get('system.mail_use_smtp')) { + $this->mail->isSMTP(); + $this->mail->Host = Settings::Get('system.mail_smtp_host'); + $this->mail->SMTPAuth = Settings::Get('system.mail_smtp_auth') == '1' ? true : false; + $this->mail->Username = Settings::Get('system.mail_smtp_user'); + $this->mail->Password = Settings::Get('system.mail_smtp_passwd'); + if (Settings::Get('system.mail_smtp_usetls')) { + $this->mail->SMTPSecure = 'tls'; + } else { + $this->mail->SMTPAutoTLS = false; + } + $this->mail->Port = Settings::Get('system.mail_smtp_port'); + } + + if (PHPMailer::ValidateAddress(Settings::Get('panel.adminmail')) !== false) { + // set return-to address and custom sender-name, see #76 + $this->mail->SetFrom(Settings::Get('panel.adminmail'), Settings::Get('panel.adminmail_defname')); + if (Settings::Get('panel.adminmail_return') != '') { + $this->mail->AddReplyTo(Settings::Get('panel.adminmail_return'), Settings::Get('panel.adminmail_defname')); + } + } + } + public static function getLocal($userinfo = null, $params = null) { return new static(null, $params, $userinfo); @@ -145,6 +184,16 @@ abstract class ApiCommand return $this->logger; } + /** + * return mailer instance + * + * @return PHPMailer + */ + protected function mailer() + { + return $this->mail; + } + protected function response($status, $status_message, $data = null) { header("HTTP/1.1 " . $status); @@ -189,6 +238,9 @@ abstract class ApiCommand $this->user_data = Database::pexecute_first($sel_stmt, array( 'id' => ($this->is_admin ? $result['adminid'] : $result['customerid']) ), true, true); + if ($this->is_admin) { + $this->user_data['adminsession'] = 1; + } return true; } throw new Exception("Invalid API credentials", 400); diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index 2d30d2e7..8e72e50a 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -57,9 +57,596 @@ class Customers extends ApiCommand public function add() { + global $lng; + if ($this->isAdmin()) { - $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] added customer '" . $loginname . "'"); - return $this->response(200, "successfull", $ins_data); + if ($this->getUserDetail('customers_used') < $this->getUserDetail('customers') || $this->getUserDetail('customers') == '-1') { + + $idna_convert = new idna_convert_wrapper(); + $name = validate($this->getParam('name'), 'name', '', '', array(), true); + $firstname = validate($this->getParam('firstname'), 'first name', '', '', array(), true); + $company = validate($this->getParam('company'), 'company', '', '', array(), true); + $street = validate($this->getParam('street'), 'street', '', '', array(), true); + $zipcode = validate($this->getParam('zipcode'), 'zipcode', '/^[0-9 \-A-Z]*$/', '', array(), true); + $city = validate($this->getParam('city'), 'city', '', '', array(), true); + $phone = validate($this->getParam('phone'), 'phone', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); + $fax = validate($this->getParam('fax'), 'fax', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); + $email = $idna_convert->encode(validate($this->getParam('email'), 'email', '', '', array(), true)); + $customernumber = validate($this->getParam('customernumber'), 'customer number', '/^[A-Za-z0-9 \-]*$/Di', '', array(), true); + $def_language = validate($this->getParam('def_language'), 'default language', '', '', array(), true); + $gender = intval_ressource($this->getParam('gender', 0)); + + $custom_notes = validate(str_replace("\r\n", "\n", $this->getParam('custom_notes', '')), 'custom_notes', '/^[^\0]*$/', '', array(), true); + $custom_notes_show = $this->getParam('custom_notes_show', 0); + + $diskspace = intval_ressource($this->getParam('diskspace', 0)); + if ($this->getParam('diskspace_ul', 0) == 1) { + $diskspace = - 1; + } + + $traffic = doubleval_ressource($this->getParam('traffic', 0)); + if ($this->getParam('traffic_ul', 0) == 1) { + $traffic = - 1; + } + + $subdomains = intval_ressource($this->getParam('subdomains', 0)); + if ($this->getParam('subdomains_ul', 0) == 1) { + $subdomains = - 1; + } + + $emails = intval_ressource($this->getParam('emails', 0)); + if ($this->getParam('emails_ul', 0) == 1) { + $emails = - 1; + } + + $email_accounts = intval_ressource($this->getParam('email_accounts', 0)); + if ($this->getParam('email_accounts_ul', 0) == 1) { + $email_accounts = - 1; + } + + $email_forwarders = intval_ressource($this->getParam('email_forwarders', 0)); + if ($this->getParam('email_forwarders_ul', 0) == 1) { + $email_forwarders = - 1; + } + + if (Settings::Get('system.mail_quota_enabled') == '1') { + $email_quota = validate($this->getParam('email_quota', 0), 'email_quota', '/^\d+$/', 'vmailquotawrong', array( + '0', + '' + ), true); + if ($this->getParam('email_quota_ul', 0) == 1) { + $email_quota = - 1; + } + } else { + $email_quota = - 1; + } + + $email_imap = $this->getParam('email_imap', 0); + $email_pop3 = $this->getParam('email_pop3', 0); + + $ftps = intval_ressource($this->getParam('ftps', 0)); + if ($this->getParam('ftps_ul', 0) == 1) { + $ftps = - 1; + } + + if (Settings::Get('ticket.enabled') == '1') { + $tickets = intval_ressource($this->getParam('tickets', 0)); + if ($this->getParam('tickets_ul', 0) == 1) { + $tickets = - 1; + } + } else { + $tickets = - 1; + } + + $mysqls = intval_ressource($this->getParam('mysqls', 0)); + if ($this->getParam('mysqls_ul', 0) == 1) { + $mysqls = - 1; + } + + $createstdsubdomain = $this->getParam('createstdsubdomain', 0); + + $password = validate($this->getParam('new_customer_password', ''), 'password', '', '', array(), true); + // only check if not empty, + // cause empty == generate password automatically + if ($password != '') { + $password = validatePassword($password, true); + } + + // gender out of range? [0,2] + if ($gender < 0 || $gender > 2) { + $gender = 0; + } + + $sendpassword = $this->getParam('sendpassword', 0); + $phpenabled = $this->getParam('phpenabled', 0); + + $allowed_phpconfigs = array(); + if (! empty($this->getParam('allowed_phpconfigs', array())) && is_array($this->getParam('allowed_phpconfigs'))) { + foreach ($this->getParam('allowed_phpconfigs') as $allowed_phpconfig) { + $allowed_phpconfig = intval($allowed_phpconfig); + $allowed_phpconfigs[] = $allowed_phpconfig; + } + } + + $perlenabled = $this->getParam('perlenabled', 0); + $dnsenabled = $this->getParam('dnsenabled', 0); + $store_defaultindex = $this->getParam('store_defaultindex', 0); + + $diskspace = $diskspace * 1024; + $traffic = $traffic * 1024 * 1024; + + if (((($this->getUserDetail('diskspace_used') + $diskspace) > $this->getUserDetail('diskspace')) && ($this->getUserDetail('diskspace') / 1024) != '-1') || ((($this->getUserDetail('mysqls_used') + $mysqls) > $this->getUserDetail('mysqls')) && $this->getUserDetail('mysqls') != '-1') || ((($this->getUserDetail('emails_used') + $emails) > $this->getUserDetail('emails')) && $this->getUserDetail('emails') != '-1') || ((($this->getUserDetail('email_accounts_used') + $email_accounts) > $this->getUserDetail('email_accounts')) && $this->getUserDetail('email_accounts') != '-1') || ((($this->getUserDetail('email_forwarders_used') + $email_forwarders) > $this->getUserDetail('email_forwarders')) && $this->getUserDetail('email_forwarders') != '-1') || ((($this->getUserDetail('email_quota_used') + $email_quota) > $this->getUserDetail('email_quota')) && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') || ((($this->getUserDetail('ftps_used') + $ftps) > $this->getUserDetail('ftps')) && $this->getUserDetail('ftps') != '-1') || ((($this->getUserDetail('tickets_used') + $tickets) > $this->getUserDetail('tickets')) && $this->getUserDetail('tickets') != '-1') || ((($this->getUserDetail('subdomains_used') + $subdomains) > $this->getUserDetail('subdomains')) && $this->getUserDetail('subdomains') != '-1') || (($diskspace / 1024) == '-1' && ($this->getUserDetail('diskspace') / 1024) != '-1') || ($mysqls == '-1' && $this->getUserDetail('mysqls') != '-1') || ($emails == '-1' && $this->getUserDetail('emails') != '-1') || ($email_accounts == '-1' && $this->getUserDetail('email_accounts') != '-1') || ($email_forwarders == '-1' && $this->getUserDetail('email_forwarders') != '-1') || ($email_quota == '-1' && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') || ($ftps == '-1' && $this->getUserDetail('ftps') != '-1') || ($tickets == '-1' && $this->getUserDetail('tickets') != '-1') || ($subdomains == '-1' && $this->getUserDetail('subdomains') != '-1')) { + standard_error('youcantallocatemorethanyouhave', '', true); + } + + // Either $name and $firstname or the $company must be inserted + if ($name == '' && $company == '') { + standard_error(array( + 'stringisempty', + 'myname' + )); + } elseif ($firstname == '' && $company == '') { + standard_error(array( + 'stringisempty', + 'myfirstname' + ), '', true); + } elseif ($email == '') { + standard_error(array( + 'stringisempty', + 'emailadd' + ), '', true); + } elseif (! validateEmail($email)) { + standard_error('emailiswrong', $email, true); + } else { + + if ($this->getParam('new_loginname', '') != '') { + $accountnumber = intval(Settings::Get('system.lastaccountnumber')); + $loginname = validate($this->getParam('new_loginname'), 'loginname', '/^[a-z][a-z0-9\-_]+$/i', '', array(), true); + + // Accounts which match systemaccounts are not allowed, filtering them + if (preg_match('/^' . preg_quote(Settings::Get('customer.accountprefix'), '/') . '([0-9]+)/', $loginname)) { + standard_error('loginnameissystemaccount', Settings::Get('customer.accountprefix'), true); + } + + // Additional filtering for Bug #962 + if (function_exists('posix_getpwnam') && ! in_array("posix_getpwnam", explode(",", ini_get('disable_functions'))) && posix_getpwnam($loginname)) { + standard_error('loginnameissystemaccount', Settings::Get('customer.accountprefix'), true); + } + } else { + $accountnumber = intval(Settings::Get('system.lastaccountnumber')) + 1; + $loginname = Settings::Get('customer.accountprefix') . $accountnumber; + } + + // Check if the account already exists + $loginname_check_stmt = Database::prepare(" + SELECT `loginname` FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `loginname` = :loginname + "); + $loginname_check = Database::pexecute_first($loginname_check_stmt, array( + 'loginname' => $loginname + ), true, true); + + $loginname_check_admin_stmt = Database::prepare(" + SELECT `loginname` FROM `" . TABLE_PANEL_ADMINS . "` WHERE `loginname` = :loginname + "); + $loginname_check_admin = Database::pexecute_first($loginname_check_admin_stmt, array( + 'loginname' => $loginname + ), true, true); + + if (strtolower($loginname_check['loginname']) == strtolower($loginname) || strtolower($loginname_check_admin['loginname']) == strtolower($loginname)) { + standard_error('loginnameexists', $loginname, true); + } elseif (! validateUsername($loginname, Settings::Get('panel.unix_names'), 14 - strlen(Settings::Get('customer.mysqlprefix')))) { + if (strlen($loginname) > 14 - strlen(Settings::Get('customer.mysqlprefix'))) { + standard_error('loginnameiswrong2', 14 - strlen(Settings::Get('customer.mysqlprefix')), true); + } else { + standard_error('loginnameiswrong', $loginname, true); + } + } + + $guid = intval(Settings::Get('system.lastguid')) + 1; + $documentroot = makeCorrectDir(Settings::Get('system.documentroot_prefix') . '/' . $loginname); + + if (file_exists($documentroot)) { + standard_error('documentrootexists', $documentroot, true); + } + + if ($createstdsubdomain != '1') { + $createstdsubdomain = '0'; + } + + if ($phpenabled != '0') { + $phpenabled = '1'; + } + + if ($perlenabled != '0') { + $perlenabled = '1'; + } + + if ($dnsenabled != '0') { + $dnsenabled = '1'; + } + + if ($password == '') { + $password = generatePassword(); + } + + $_theme = Settings::Get('panel.default_theme'); + + $ins_data = array( + 'adminid' => $this->getUserDetail('adminid'), + 'loginname' => $loginname, + 'passwd' => makeCryptPassword($password), + 'name' => $name, + 'firstname' => $firstname, + 'gender' => $gender, + 'company' => $company, + 'street' => $street, + 'zipcode' => $zipcode, + 'city' => $city, + 'phone' => $phone, + 'fax' => $fax, + 'email' => $email, + 'customerno' => $customernumber, + 'lang' => $def_language, + 'docroot' => $documentroot, + 'guid' => $guid, + 'diskspace' => $diskspace, + 'traffic' => $traffic, + 'subdomains' => $subdomains, + 'emails' => $emails, + 'email_accounts' => $email_accounts, + 'email_forwarders' => $email_forwarders, + 'email_quota' => $email_quota, + 'ftps' => $ftps, + 'tickets' => $tickets, + 'mysqls' => $mysqls, + 'phpenabled' => $phpenabled, + 'allowed_phpconfigs' => empty($allowed_phpconfigs) ? "" : json_encode($allowed_phpconfigs), + 'imap' => $email_imap, + 'pop3' => $email_pop3, + 'perlenabled' => $perlenabled, + 'dnsenabled' => $dnsenabled, + 'theme' => $_theme, + 'custom_notes' => $custom_notes, + 'custom_notes_show' => $custom_notes_show + ); + + $ins_stmt = Database::prepare(" + INSERT INTO `" . TABLE_PANEL_CUSTOMERS . "` SET + `adminid` = :adminid, + `loginname` = :loginname, + `password` = :passwd, + `name` = :name, + `firstname` = :firstname, + `gender` = :gender, + `company` = :company, + `street` = :street, + `zipcode` = :zipcode, + `city` = :city, + `phone` = :phone, + `fax` = :fax, + `email` = :email, + `customernumber` = :customerno, + `def_language` = :lang, + `documentroot` = :docroot, + `guid` = :guid, + `diskspace` = :diskspace, + `traffic` = :traffic, + `subdomains` = :subdomains, + `emails` = :emails, + `email_accounts` = :email_accounts, + `email_forwarders` = :email_forwarders, + `email_quota` = :email_quota, + `ftps` = :ftps, + `tickets` = :tickets, + `mysqls` = :mysqls, + `standardsubdomain` = '0', + `phpenabled` = :phpenabled, + `allowed_phpconfigs` = :allowed_phpconfigs, + `imap` = :imap, + `pop3` = :pop3, + `perlenabled` = :perlenabled, + `dnsenabled` = :dnsenabled, + `theme` = :theme, + `custom_notes` = :custom_notes, + `custom_notes_show` = :custom_notes_show + "); + Database::pexecute($ins_stmt, $ins_data, true, true); + + $customerid = Database::lastInsertId(); + $ins_data['customerid'] = $customerid; + + // update admin resource-usage + $admin_update_query = "UPDATE `" . TABLE_PANEL_ADMINS . "` SET `customers_used` = `customers_used` + 1"; + + if ($mysqls != '-1') { + $admin_update_query .= ", `mysqls_used` = `mysqls_used` + 0" . (int) $mysqls; + } + + if ($emails != '-1') { + $admin_update_query .= ", `emails_used` = `emails_used` + 0" . (int) $emails; + } + + if ($email_accounts != '-1') { + $admin_update_query .= ", `email_accounts_used` = `email_accounts_used` + 0" . (int) $email_accounts; + } + + if ($email_forwarders != '-1') { + $admin_update_query .= ", `email_forwarders_used` = `email_forwarders_used` + 0" . (int) $email_forwarders; + } + + if ($email_quota != '-1') { + $admin_update_query .= ", `email_quota_used` = `email_quota_used` + 0" . (int) $email_quota; + } + + if ($subdomains != '-1') { + $admin_update_query .= ", `subdomains_used` = `subdomains_used` + 0" . (int) $subdomains; + } + + if ($ftps != '-1') { + $admin_update_query .= ", `ftps_used` = `ftps_used` + 0" . (int) $ftps; + } + + if ($tickets != '-1' && Settings::Get('ticket.enabled') == 1) { + $admin_update_query .= ", `tickets_used` = `tickets_used` + 0" . (int) $tickets; + } + + if (($diskspace / 1024) != '-1') { + $admin_update_query .= ", `diskspace_used` = `diskspace_used` + 0" . (int) $diskspace; + } + + $admin_update_query .= " WHERE `adminid` = '" . (int) $this->getUserDetail('adminid') . "'"; + Database::query($admin_update_query); + + // update last guid + Settings::Set('system.lastguid', $guid, true); + + if ($accountnumber != intval(Settings::Get('system.lastaccountnumber'))) { + // update last account number + Settings::Set('system.lastaccountnumber', $accountnumber, true); + } + + $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] added customer '" . $loginname . "'"); + $customer_ins_data = $ins_data; + unset($ins_data); + + // insert task to create homedir etc. + inserttask('2', $loginname, $guid, $guid, $store_defaultindex); + + // Using filesystem - quota, insert a task which cleans the filesystem - quota + inserttask('10'); + + // Add htpasswd for the webalizer stats + if (CRYPT_STD_DES == 1) { + $saltfordescrypt = substr(md5(uniqid(microtime(), 1)), 4, 2); + $htpasswdPassword = crypt($password, $saltfordescrypt); + } else { + $htpasswdPassword = crypt($password); + } + + $ins_stmt = Database::prepare(" + INSERT INTO `" . TABLE_PANEL_HTPASSWDS . "` SET + `customerid` = :customerid, + `username` = :username, + `password` = :passwd, + `path` = :path + "); + $ins_data = array( + 'customerid' => $customerid, + 'username' => $loginname, + 'passwd' => $htpasswdPassword + ); + + if (Settings::Get('system.awstats_enabled') == '1') { + $ins_data['path'] = makeCorrectDir($documentroot . '/awstats/'); + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] automatically added awstats htpasswd for user '" . $loginname . "'"); + } else { + $ins_data['path'] = makeCorrectDir($documentroot . '/webalizer/'); + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] automatically added webalizer htpasswd for user '" . $loginname . "'"); + } + Database::pexecute($ins_stmt, $ins_data, true, true); + + inserttask('1'); + $cryptPassword = makeCryptPassword($password); + // add FTP-User + // @fixme use Ftp-ApiCommand later + $ins_stmt = Database::prepare(" + INSERT INTO `" . TABLE_FTP_USERS . "` SET `customerid` = :customerid, `username` = :username, `description` = :desc, + `password` = :passwd, `homedir` = :homedir, `login_enabled` = 'y', `uid` = :guid, `gid` = :guid + "); + $ins_data = array( + 'customerid' => $customerid, + 'username' => $loginname, + 'passwd' => $cryptPassword, + 'homedir' => $documentroot, + 'guid' => $guid, + 'desc' => "Default" + ); + Database::pexecute($ins_stmt, $ins_data, true, true); + // add FTP-Group + // @fixme use Ftp-ApiCommand later + $ins_stmt = Database::prepare(" + INSERT INTO `" . TABLE_FTP_GROUPS . "` SET `customerid` = :customerid, `groupname` = :groupname, `gid` = :guid, `members` = :members + "); + $ins_data = array( + 'customerid' => $customerid, + 'groupname' => $loginname, + 'guid' => $guid, + 'members' => $loginname . ',' . Settings::Get('system.httpuser') + ); + + // also, add froxlor-local user to ftp-group (if exists!) to + // allow access to customer-directories from within the panel, which + // is necessary when pathedit = Dropdown + if ((int) Settings::Get('system.mod_fcgid_ownvhost') == 1 || (int) Settings::Get('phpfpm.enabled_ownvhost') == 1) { + if ((int) Settings::Get('system.mod_fcgid') == 1) { + $local_user = Settings::Get('system.mod_fcgid_httpuser'); + } else { + $local_user = Settings::Get('phpfpm.vhost_httpuser'); + } + // check froxlor-local user membership in ftp-group + // without this check addition may duplicate user in list if httpuser == local_user + if (strpos($ins_data['members'], $local_user) == false) { + $ins_data['members'] .= ',' . $local_user; + } + } + Database::pexecute($ins_stmt, $ins_data, true, true); + + // FTP-Quotatallies + // @fixme use Ftp-ApiCommand later + $ins_stmt = Database::prepare(" + INSERT INTO `" . TABLE_FTP_QUOTATALLIES . "` SET `name` = :name, `quota_type` = 'user', `bytes_in_used` = '0', + `bytes_out_used` = '0', `bytes_xfer_used` = '0', `files_in_used` = '0', `files_out_used` = '0', `files_xfer_used` = '0' + "); + Database::pexecute($ins_stmt, array( + 'name' => $loginname + ), true, true); + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] automatically added ftp-account for user '" . $loginname . "'"); + + $_stdsubdomain = ''; + if ($createstdsubdomain == '1') { + if (Settings::Get('system.stdsubdomain') !== null && Settings::Get('system.stdsubdomain') != '') { + $_stdsubdomain = $loginname . '.' . Settings::Get('system.stdsubdomain'); + } else { + $_stdsubdomain = $loginname . '.' . Settings::Get('system.hostname'); + } + + $ins_data = array( + 'domain' => $_stdsubdomain, + 'customerid' => $customerid, + 'adminid' => $this->getUserDetail('adminid'), + 'parentdomainid' => '0', + 'docroot' => $documentroot, + 'adddate' => time(), + 'phpenabled' => $phpenabled, + 'zonefile' => '', + 'isemaildomain' => '0', + 'caneditdomain' => '0', + 'openbasedir' => '1', + 'speciallogfile' => '0', + 'dkim_id' => '0', + 'dkim_privkey' => '', + 'dkim_pubkey' => '', + 'ipandport' => explode(',', Settings::Get('system.defaultip')) + ); + $domainid = - 1; + try { + $std_domain = Domains::getLocal($this->getUserData(), $ins_data)->add(); + $domainid = json_decode($std_domain, true)['data']['id']; + } catch (Exception $e) { + $this->logger()->logAction(ADM_ACTION, LOG_ERR, "[API] Unable to add standard-subdomain: " . $e->getMessage()); + } + + if ($domainid > 0) { + // set ip <-> domain connection + $defaultips = explode(',', Settings::Get('system.defaultip')); + $ins_stmt = Database::prepare(" + INSERT INTO `" . TABLE_DOMAINTOIP . "` SET `id_domain` = :domainid, `id_ipandports` = :ipid + "); + foreach ($defaultips as $defaultip) { + Database::pexecute($ins_stmt, array( + 'domainid' => $domainid, + 'ipid' => $defaultip + ), true, true); + } + + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `standardsubdomain` = :domainid WHERE `customerid` = :customerid + "); + Database::pexecute($upd_stmt, array( + 'domainid' => $domainid, + 'customerid' => $customerid + ), true, true); + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] automatically added standardsubdomain for user '" . $loginname . "'"); + inserttask('1'); + } + } + + if ($sendpassword == '1') { + + $srv_hostname = Settings::Get('system.hostname'); + if (Settings::Get('system.froxlordirectlyviahostname') == '0') { + $srv_hostname .= '/froxlor'; + } + + $srv_ip_stmt = Database::prepare(" + SELECT ip, port FROM `" . TABLE_PANEL_IPSANDPORTS . "` + WHERE `id` = :defaultip + "); + $default_ips = Settings::Get('system.defaultip'); + $default_ips = explode(',', $default_ips); + $srv_ip = Database::pexecute_first($srv_ip_stmt, array( + 'defaultip' => reset($default_ips) + ), true, true); + + $replace_arr = array( + 'FIRSTNAME' => $firstname, + 'NAME' => $name, + 'COMPANY' => $company, + 'SALUTATION' => getCorrectUserSalutation(array( + 'firstname' => $firstname, + 'name' => $name, + 'company' => $company + )), + 'USERNAME' => $loginname, + 'PASSWORD' => $password, + 'SERVER_HOSTNAME' => $srv_hostname, + 'SERVER_IP' => isset($srv_ip['ip']) ? $srv_ip['ip'] : '', + 'SERVER_PORT' => isset($srv_ip['port']) ? $srv_ip['port'] : '', + 'DOMAINNAME' => $_stdsubdomain + ); + + // Get mail templates from database; the ones from 'admin' are fetched for fallback + $result_stmt = Database::prepare(" + SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` + WHERE `adminid` = :adminid AND `language` = :deflang AND `templategroup` = 'mails' AND `varname` = 'createcustomer_subject'"); + $result = Database::pexecute_first($result_stmt, array( + 'adminid' => $this->getUserDetail('adminid'), + 'deflang' => $def_language + ), true, true); + $mail_subject = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $lng['mails']['createcustomer']['subject']), $replace_arr)); + + $result_stmt = Database::prepare(" + SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` + WHERE `adminid` = :adminid AND `language` = :deflang AND `templategroup` = 'mails' AND `varname` = 'createcustomer_mailbody'"); + $result = Database::pexecute_first($result_stmt, array( + 'adminid' => $this->getUserDetail('adminid'), + 'deflang' => $def_language + ), true, true); + $mail_body = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $lng['mails']['createcustomer']['mailbody']), $replace_arr)); + + $_mailerror = false; + try { + $this->mailer()->Subject = $mail_subject; + $this->mailer()->AltBody = $mail_body; + $this->mailer()->MsgHTML(str_replace("\n", "
", $mail_body)); + $this->mailer()->AddAddress($email, getCorrectUserSalutation(array( + 'firstname' => $firstname, + 'name' => $name, + 'company' => $company + ))); + $this->mailer()->Send(); + } catch (phpmailerException $e) { + $mailerr_msg = $e->errorMessage(); + $_mailerror = true; + } catch (Exception $e) { + $mailerr_msg = $e->getMessage(); + $_mailerror = true; + } + + if ($_mailerror) { + $this->logger()->logAction(ADM_ACTION, LOG_ERR, "[API] Error sending mail: " . $mailerr_msg); + standard_error('errorsendingmail', $email, true); + } + + $this->mailer()->ClearAddresses(); + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] automatically sent password to user '" . $loginname . "'"); + } + } + + $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] added customer '" . $loginname . "'"); + return $this->response(200, "successfull", $customer_ins_data); + } + throw new Exception("No more resources available", 406); } throw new Exception("Not allowed to execute given command.", 403); } @@ -89,10 +676,244 @@ class Customers extends ApiCommand 'id' => $id ))->get(); $result = json_decode($json_result, true)['data']; - + + // @fixme use Databases-ApiCommand later + $databases_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_DATABASES . "` + WHERE `customerid` = :id ORDER BY `dbserver` + "); + Database::pexecute($databases_stmt, array( + 'id' => $id + )); + Database::needRoot(true); + $last_dbserver = 0; + + $dbm = new DbManager($log); + + while ($row_database = $databases_stmt->fetch(PDO::FETCH_ASSOC)) { + if ($last_dbserver != $row_database['dbserver']) { + Database::needRoot(true, $row_database['dbserver']); + $dbm->getManager()->flushPrivileges(); + $last_dbserver = $row_database['dbserver']; + } + $dbm->getManager()->deleteDatabase($row_database['databasename']); + } + $dbm->getManager()->flushPrivileges(); + Database::needRoot(false); + + // delete customer itself + $stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `customerid` = :id"); + Database::pexecute($stmt, array( + 'id' => $id + ), true, true); + + // delete customer databases + $stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_DATABASES . "` WHERE `customerid` = :id"); + Database::pexecute($stmt, array( + 'id' => $id + ), true, true); + + // first gather all domain-id's to clean up panel_domaintoip and dns-entries accordingly + $did_stmt = Database::prepare("SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `customerid` = :id"); + Database::pexecute($did_stmt, array( + 'id' => $id + ), true, true); + while ($row = $did_stmt->fetch(PDO::FETCH_ASSOC)) { + // remove domain->ip connection + $stmt = Database::prepare("DELETE FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :did"); + Database::pexecute($stmt, array( + 'did' => $row['id'] + ), true, true); + // remove domain->dns entries + $stmt = Database::prepare("DELETE FROM `" . TABLE_DOMAIN_DNS . "` WHERE `domain_id` = :did"); + Database::pexecute($stmt, array( + 'did' => $row['id'] + ), true, true); + } + // remove customer domains + $stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `customerid` = :id"); + Database::pexecute($stmt, array( + 'id' => $id + ), true, true); + $domains_deleted = $stmt->rowCount(); + + // delete htpasswds + $stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_HTPASSWDS . "` WHERE `customerid` = :id"); + Database::pexecute($stmt, array( + 'id' => $id + ), true, true); + + // delete htaccess options + $stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_HTACCESS . "` WHERE `customerid` = :id"); + Database::pexecute($stmt, array( + 'id' => $id + ), true, true); + + // delete potential existing sessions + $stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_SESSIONS . "` WHERE `userid` = :id AND `adminsession` = '0'"); + Database::pexecute($stmt, array( + 'id' => $id + ), true, true); + + // delete traffic information + $stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_TRAFFIC . "` WHERE `customerid` = :id"); + Database::pexecute($stmt, array( + 'id' => $id + ), true, true); + + // remove diskspace analysis + $stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_DISKSPACE . "` WHERE `customerid` = :id"); + Database::pexecute($stmt, array( + 'id' => $id + ), true, true); + + // delete mail-accounts + $stmt = Database::prepare("DELETE FROM `" . TABLE_MAIL_USERS . "` WHERE `customerid` = :id"); + Database::pexecute($stmt, array( + 'id' => $id + ), true, true); + + // delete mail-addresses + $stmt = Database::prepare("DELETE FROM `" . TABLE_MAIL_VIRTUAL . "` WHERE `customerid` = :id"); + Database::pexecute($stmt, array( + 'id' => $id + ), true, true); + + // gather ftp-user names + $result2_stmt = Database::prepare("SELECT `username` FROM `" . TABLE_FTP_USERS . "` WHERE `customerid` = :id"); + Database::pexecute($result2_stmt, array( + 'id' => $id + ), true, true); + while ($row = $result2_stmt->fetch(PDO::FETCH_ASSOC)) { + // delete ftp-quotatallies by username + $stmt = Database::prepare("DELETE FROM `" . TABLE_FTP_QUOTATALLIES . "` WHERE `name` = :name"); + Database::pexecute($stmt, array( + 'name' => $row['username'] + ), true, true); + } + + // remove ftp-group + $stmt = Database::prepare("DELETE FROM `" . TABLE_FTP_GROUPS . "` WHERE `customerid` = :id"); + Database::pexecute($stmt, array( + 'id' => $id + ), true, true); + + // remove ftp-users + $stmt = Database::prepare("DELETE FROM `" . TABLE_FTP_USERS . "` WHERE `customerid` = :id"); + Database::pexecute($stmt, array( + 'id' => $id + ), true, true); + + // Delete all waiting "create user" -tasks for this user, #276 + // Note: the WHERE selects part of a serialized array, but it should be safe this way + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_PANEL_TASKS . "` + WHERE `type` = '2' AND `data` LIKE :loginname + "); + Database::pexecute($del_stmt, array( + 'loginname' => "%:{$result['loginname']};%" + ), true, true); + + // update admin-resource-usage + $admin_update_query = "UPDATE `" . TABLE_PANEL_ADMINS . "` SET `customers_used` = `customers_used` - 1 "; + $admin_update_query .= ", `domains_used` = `domains_used` - 0" . (int) ($domains_deleted - $result['subdomains_used']); + + if ($result['mysqls'] != '-1') { + $admin_update_query .= ", `mysqls_used` = `mysqls_used` - 0" . (int) $result['mysqls']; + } + + if ($result['emails'] != '-1') { + $admin_update_query .= ", `emails_used` = `emails_used` - 0" . (int) $result['emails']; + } + + if ($result['email_accounts'] != '-1') { + $admin_update_query .= ", `email_accounts_used` = `email_accounts_used` - 0" . (int) $result['email_accounts']; + } + + if ($result['email_forwarders'] != '-1') { + $admin_update_query .= ", `email_forwarders_used` = `email_forwarders_used` - 0" . (int) $result['email_forwarders']; + } + + if ($result['email_quota'] != '-1') { + $admin_update_query .= ", `email_quota_used` = `email_quota_used` - 0" . (int) $result['email_quota']; + } + + if ($result['subdomains'] != '-1') { + $admin_update_query .= ", `subdomains_used` = `subdomains_used` - 0" . (int) $result['subdomains']; + } + + if ($result['ftps'] != '-1') { + $admin_update_query .= ", `ftps_used` = `ftps_used` - 0" . (int) $result['ftps']; + } + + if ($result['tickets'] != '-1') { + $admin_update_query .= ", `tickets_used` = `tickets_used` - 0" . (int) $result['tickets']; + } + + if (($result['diskspace'] / 1024) != '-1') { + $admin_update_query .= ", `diskspace_used` = `diskspace_used` - 0" . (int) $result['diskspace']; + } + + $admin_update_query .= " WHERE `adminid` = '" . (int) $result['adminid'] . "'"; + Database::query($admin_update_query); + + // rebuild configs + inserttask('1'); + + // Using nameserver, insert a task which rebuilds the server config + inserttask('4'); + + if ($this->getParam('delete_userfiles', 0) == 1) { + // insert task to remove the customers files from the filesystem + inserttask('6', $result['loginname']); + } + + // Using filesystem - quota, insert a task which cleans the filesystem - quota + inserttask('10'); + + // move old tickets to archive + $tickets = ticket::customerHasTickets($id); + if ($tickets !== false && isset($tickets[0])) { + foreach ($tickets as $ticket) { + $now = time(); + $mainticket = ticket::getInstanceOf($userinfo, (int) $ticket); + $mainticket->Set('lastchange', $now, true, true); + $mainticket->Set('lastreplier', '1', true, true); + $mainticket->Set('status', '3', true, true); + $mainticket->Update(); + $mainticket->Archive(); + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] archived ticket '" . $mainticket->Get('subject') . "'"); + } + } + $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] deleted customer '" . $result['loginname'] . "'"); return $this->response(200, "successfull", $result); } throw new Exception("Not allowed to execute given command.", 403); } + + public function unlock() + { + if ($this->isAdmin()) { + $id = $this->getParam('id'); + + $json_result = Customers::getLocal($this->getUserData(), array( + 'id' => $id + ))->get(); + $result = json_decode($json_result, true)['data']; + + $result_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET + `loginfail_count` = '0' + WHERE `customerid`= :id + "); + Database::pexecute($result_stmt, array( + 'id' => $id + ), true, true); + + $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] unlocked customer '" . $result['loginname'] . "'"); + return $this->response(200, "successfull", $result); + } + throw new Exception("Not allowed to execute given command.", 403); + } } diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index 1f44d4f6..ed5db265 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -492,6 +492,7 @@ class Domains extends ApiCommand $issubof = '0'; } + $idna_convert = new idna_convert_wrapper(); if ($domain == '') { standard_error(array( 'stringisempty', @@ -649,6 +650,9 @@ class Domains extends ApiCommand "); Database::pexecute($ins_stmt, $ins_data, true, true); $domainid = Database::lastInsertId(); + $ins_data['id'] = $domainid; + $domain_ins_data = $ins_data; + unset($ins_data); $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_ADMINS . "` SET `domains_used` = `domains_used` + 1 @@ -688,7 +692,7 @@ class Domains extends ApiCommand inserttask('4'); $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] added domain '" . $domain . "'"); - return $this->response(200, "successfull", $ins_data); + return $this->response(200, "successfull", $domain_ins_data); } } throw new Exception("No more resources available", 406); diff --git a/lib/functions/validate/function.validatePassword.php b/lib/functions/validate/function.validatePassword.php index 48922ba7..03d2b0f9 100644 --- a/lib/functions/validate/function.validatePassword.php +++ b/lib/functions/validate/function.validatePassword.php @@ -26,14 +26,16 @@ * * @return string either the password or an errormessage+exit */ -function validatePassword($password = null) { +function validatePassword($password = null, $json_response = false) { if (Settings::Get('panel.password_min_length') > 0) { $password = validate( $password, Settings::Get('panel.password_min_length'), '/^.{'.(int)Settings::Get('panel.password_min_length').',}$/D', - 'notrequiredpasswordlength' + 'notrequiredpasswordlength', + array(), + $json_response ); } @@ -42,7 +44,9 @@ function validatePassword($password = null) { $password, Settings::Get('panel.password_regex'), Settings::Get('panel.password_regex'), - 'notrequiredpasswordcomplexity' + 'notrequiredpasswordcomplexity', + array(), + $json_response ); } else { if (Settings::Get('panel.password_alpha_lower')) { @@ -50,7 +54,9 @@ function validatePassword($password = null) { $password, '/.*[a-z]+.*/', '/.*[a-z]+.*/', - 'notrequiredpasswordcomplexity' + 'notrequiredpasswordcomplexity', + array(), + $json_response ); } if (Settings::Get('panel.password_alpha_upper')) { @@ -58,7 +64,9 @@ function validatePassword($password = null) { $password, '/.*[A-Z]+.*/', '/.*[A-Z]+.*/', - 'notrequiredpasswordcomplexity' + 'notrequiredpasswordcomplexity', + array(), + $json_response ); } if (Settings::Get('panel.password_numeric')) { @@ -66,7 +74,9 @@ function validatePassword($password = null) { $password, '/.*[0-9]+.*/', '/.*[0-9]+.*/', - 'notrequiredpasswordcomplexity' + 'notrequiredpasswordcomplexity', + array(), + $json_response ); } if (Settings::Get('panel.password_special_char_required')) { @@ -74,7 +84,9 @@ function validatePassword($password = null) { $password, '/.*[' . preg_quote(Settings::Get('panel.password_special_char')) . ']+.*/', '/.*[' . preg_quote(Settings::Get('panel.password_special_char')) . ']+.*/', - 'notrequiredpasswordcomplexity' + 'notrequiredpasswordcomplexity', + array(), + $json_response ); } } From 8978dd3a4bdb6b5fc2b7c5e78946c5b0e1b63cea Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Fri, 16 Feb 2018 13:55:12 +0100 Subject: [PATCH 015/746] std-subdomain <> ip connection is already handled by Domains::add() Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Customers.php | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index 8e72e50a..cad36cfe 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -537,21 +537,9 @@ class Customers extends ApiCommand } if ($domainid > 0) { - // set ip <-> domain connection - $defaultips = explode(',', Settings::Get('system.defaultip')); - $ins_stmt = Database::prepare(" - INSERT INTO `" . TABLE_DOMAINTOIP . "` SET `id_domain` = :domainid, `id_ipandports` = :ipid - "); - foreach ($defaultips as $defaultip) { - Database::pexecute($ins_stmt, array( - 'domainid' => $domainid, - 'ipid' => $defaultip - ), true, true); - } - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `standardsubdomain` = :domainid WHERE `customerid` = :customerid - "); + UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `standardsubdomain` = :domainid WHERE `customerid` = :customerid + "); Database::pexecute($upd_stmt, array( 'domainid' => $domainid, 'customerid' => $customerid From 60defd3cdf668bada02e95bbb4b01580a7c4d8bf Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Fri, 16 Feb 2018 14:34:55 +0100 Subject: [PATCH 016/746] fix unlimited flags when adding customer; add debug flag to log all api-requests for testing purposes now Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/abstract.ApiCommand.php | 5 + lib/classes/api/commands/class.Customers.php | 136 +++++++++---------- 2 files changed, 73 insertions(+), 68 deletions(-) diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index 10e053a8..5ad847b3 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -3,6 +3,8 @@ abstract class ApiCommand { + private $debug = true; + private $is_admin = false; private $user_data = null; @@ -27,6 +29,9 @@ abstract class ApiCommand throw new Exception("Invalid user data", 500); } $this->logger = FroxlorLogger::getInstanceOf($this->user_data); + if ($this->debug) { + $this->logger()->logAction(LOG_ERROR, LOG_DEBUG, json_encode($params, JSON_UNESCAPED_SLASHES)); + } $this->initLang(); $this->initMail(); diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index cad36cfe..84525d0c 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -80,32 +80,32 @@ class Customers extends ApiCommand $custom_notes_show = $this->getParam('custom_notes_show', 0); $diskspace = intval_ressource($this->getParam('diskspace', 0)); - if ($this->getParam('diskspace_ul', 0) == 1) { + if ($this->getParam('diskspace_ul', 0) == -1) { $diskspace = - 1; } $traffic = doubleval_ressource($this->getParam('traffic', 0)); - if ($this->getParam('traffic_ul', 0) == 1) { + if ($this->getParam('traffic_ul', 0) == -1) { $traffic = - 1; } $subdomains = intval_ressource($this->getParam('subdomains', 0)); - if ($this->getParam('subdomains_ul', 0) == 1) { + if ($this->getParam('subdomains_ul', 0) == -1) { $subdomains = - 1; } $emails = intval_ressource($this->getParam('emails', 0)); - if ($this->getParam('emails_ul', 0) == 1) { + if ($this->getParam('emails_ul', 0) == -1) { $emails = - 1; } $email_accounts = intval_ressource($this->getParam('email_accounts', 0)); - if ($this->getParam('email_accounts_ul', 0) == 1) { + if ($this->getParam('email_accounts_ul', 0) == -1) { $email_accounts = - 1; } $email_forwarders = intval_ressource($this->getParam('email_forwarders', 0)); - if ($this->getParam('email_forwarders_ul', 0) == 1) { + if ($this->getParam('email_forwarders_ul', 0) == -1) { $email_forwarders = - 1; } @@ -114,7 +114,7 @@ class Customers extends ApiCommand '0', '' ), true); - if ($this->getParam('email_quota_ul', 0) == 1) { + if ($this->getParam('email_quota_ul', 0) == -1) { $email_quota = - 1; } } else { @@ -125,13 +125,13 @@ class Customers extends ApiCommand $email_pop3 = $this->getParam('email_pop3', 0); $ftps = intval_ressource($this->getParam('ftps', 0)); - if ($this->getParam('ftps_ul', 0) == 1) { + if ($this->getParam('ftps_ul', 0) == -1) { $ftps = - 1; } if (Settings::Get('ticket.enabled') == '1') { $tickets = intval_ressource($this->getParam('tickets', 0)); - if ($this->getParam('tickets_ul', 0) == 1) { + if ($this->getParam('tickets_ul', 0) == -1) { $tickets = - 1; } } else { @@ -139,7 +139,7 @@ class Customers extends ApiCommand } $mysqls = intval_ressource($this->getParam('mysqls', 0)); - if ($this->getParam('mysqls_ul', 0) == 1) { + if ($this->getParam('mysqls_ul', 0) == -1) { $mysqls = - 1; } @@ -219,15 +219,15 @@ class Customers extends ApiCommand // Check if the account already exists $loginname_check_stmt = Database::prepare(" - SELECT `loginname` FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `loginname` = :loginname - "); + SELECT `loginname` FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `loginname` = :loginname + "); $loginname_check = Database::pexecute_first($loginname_check_stmt, array( 'loginname' => $loginname ), true, true); $loginname_check_admin_stmt = Database::prepare(" - SELECT `loginname` FROM `" . TABLE_PANEL_ADMINS . "` WHERE `loginname` = :loginname - "); + SELECT `loginname` FROM `" . TABLE_PANEL_ADMINS . "` WHERE `loginname` = :loginname + "); $loginname_check_admin = Database::pexecute_first($loginname_check_admin_stmt, array( 'loginname' => $loginname ), true, true); @@ -311,45 +311,45 @@ class Customers extends ApiCommand ); $ins_stmt = Database::prepare(" - INSERT INTO `" . TABLE_PANEL_CUSTOMERS . "` SET - `adminid` = :adminid, - `loginname` = :loginname, - `password` = :passwd, - `name` = :name, - `firstname` = :firstname, - `gender` = :gender, - `company` = :company, - `street` = :street, - `zipcode` = :zipcode, - `city` = :city, - `phone` = :phone, - `fax` = :fax, - `email` = :email, - `customernumber` = :customerno, - `def_language` = :lang, - `documentroot` = :docroot, - `guid` = :guid, - `diskspace` = :diskspace, - `traffic` = :traffic, - `subdomains` = :subdomains, - `emails` = :emails, - `email_accounts` = :email_accounts, - `email_forwarders` = :email_forwarders, - `email_quota` = :email_quota, - `ftps` = :ftps, - `tickets` = :tickets, - `mysqls` = :mysqls, - `standardsubdomain` = '0', - `phpenabled` = :phpenabled, - `allowed_phpconfigs` = :allowed_phpconfigs, - `imap` = :imap, - `pop3` = :pop3, - `perlenabled` = :perlenabled, - `dnsenabled` = :dnsenabled, - `theme` = :theme, - `custom_notes` = :custom_notes, - `custom_notes_show` = :custom_notes_show - "); + INSERT INTO `" . TABLE_PANEL_CUSTOMERS . "` SET + `adminid` = :adminid, + `loginname` = :loginname, + `password` = :passwd, + `name` = :name, + `firstname` = :firstname, + `gender` = :gender, + `company` = :company, + `street` = :street, + `zipcode` = :zipcode, + `city` = :city, + `phone` = :phone, + `fax` = :fax, + `email` = :email, + `customernumber` = :customerno, + `def_language` = :lang, + `documentroot` = :docroot, + `guid` = :guid, + `diskspace` = :diskspace, + `traffic` = :traffic, + `subdomains` = :subdomains, + `emails` = :emails, + `email_accounts` = :email_accounts, + `email_forwarders` = :email_forwarders, + `email_quota` = :email_quota, + `ftps` = :ftps, + `tickets` = :tickets, + `mysqls` = :mysqls, + `standardsubdomain` = '0', + `phpenabled` = :phpenabled, + `allowed_phpconfigs` = :allowed_phpconfigs, + `imap` = :imap, + `pop3` = :pop3, + `perlenabled` = :perlenabled, + `dnsenabled` = :dnsenabled, + `theme` = :theme, + `custom_notes` = :custom_notes, + `custom_notes_show` = :custom_notes_show + "); Database::pexecute($ins_stmt, $ins_data, true, true); $customerid = Database::lastInsertId(); @@ -424,12 +424,12 @@ class Customers extends ApiCommand } $ins_stmt = Database::prepare(" - INSERT INTO `" . TABLE_PANEL_HTPASSWDS . "` SET - `customerid` = :customerid, - `username` = :username, - `password` = :passwd, - `path` = :path - "); + INSERT INTO `" . TABLE_PANEL_HTPASSWDS . "` SET + `customerid` = :customerid, + `username` = :username, + `password` = :passwd, + `path` = :path + "); $ins_data = array( 'customerid' => $customerid, 'username' => $loginname, @@ -450,9 +450,9 @@ class Customers extends ApiCommand // add FTP-User // @fixme use Ftp-ApiCommand later $ins_stmt = Database::prepare(" - INSERT INTO `" . TABLE_FTP_USERS . "` SET `customerid` = :customerid, `username` = :username, `description` = :desc, - `password` = :passwd, `homedir` = :homedir, `login_enabled` = 'y', `uid` = :guid, `gid` = :guid - "); + INSERT INTO `" . TABLE_FTP_USERS . "` SET `customerid` = :customerid, `username` = :username, `description` = :desc, + `password` = :passwd, `homedir` = :homedir, `login_enabled` = 'y', `uid` = :guid, `gid` = :guid + "); $ins_data = array( 'customerid' => $customerid, 'username' => $loginname, @@ -465,8 +465,8 @@ class Customers extends ApiCommand // add FTP-Group // @fixme use Ftp-ApiCommand later $ins_stmt = Database::prepare(" - INSERT INTO `" . TABLE_FTP_GROUPS . "` SET `customerid` = :customerid, `groupname` = :groupname, `gid` = :guid, `members` = :members - "); + INSERT INTO `" . TABLE_FTP_GROUPS . "` SET `customerid` = :customerid, `groupname` = :groupname, `gid` = :guid, `members` = :members + "); $ins_data = array( 'customerid' => $customerid, 'groupname' => $loginname, @@ -553,13 +553,13 @@ class Customers extends ApiCommand $srv_hostname = Settings::Get('system.hostname'); if (Settings::Get('system.froxlordirectlyviahostname') == '0') { - $srv_hostname .= '/froxlor'; + $srv_hostname .= '/'.basename(FROXLOR_INSTALL_DIR); } $srv_ip_stmt = Database::prepare(" - SELECT ip, port FROM `" . TABLE_PANEL_IPSANDPORTS . "` - WHERE `id` = :defaultip - "); + SELECT ip, port FROM `" . TABLE_PANEL_IPSANDPORTS . "` + WHERE `id` = :defaultip + "); $default_ips = Settings::Get('system.defaultip'); $default_ips = explode(',', $default_ips); $srv_ip = Database::pexecute_first($srv_ip_stmt, array( From 7c961647704c61c9605253af6555375f3bc2c72a Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Fri, 16 Feb 2018 15:39:59 +0100 Subject: [PATCH 017/746] fix lng availability; add returncode for not-found messages Signed-off-by: Michael Kaufmann (d00p) --- admin_customers.php | 19 +++++++++---------- lib/classes/api/abstract.ApiCommand.php | 8 +++++--- lib/classes/api/commands/class.Customers.php | 2 +- lib/classes/api/commands/class.Domains.php | 2 +- .../api/commands/class.IpsAndPorts.php | 2 +- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/admin_customers.php b/admin_customers.php index 7e0aae18..fc53871b 100644 --- a/admin_customers.php +++ b/admin_customers.php @@ -343,22 +343,21 @@ if ($page == 'customers' && $id != 0 ) { - $result_data = array('id' => $id); - $result_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_CUSTOMERS . "` - WHERE `customerid` = :id" . ($userinfo['customers_see_all'] ? '' : " AND `adminid` = :adminid") - ); - if ($userinfo['customers_see_all'] == '0') { - $result_data['adminid'] = $userinfo['adminid']; + try { + $json_result = Customers::getLocal($userinfo, array( + 'id' => $id + ))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - $result = Database::pexecute_first($result_stmt, $result_data); + $result = json_decode($json_result, true)['data']; /* * information for moving customer */ $available_admins_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_ADMINS . "` - WHERE (`customers` = '-1' OR `customers` > `customers_used`)" + SELECT * FROM `" . TABLE_PANEL_ADMINS . "` + WHERE (`customers` = '-1' OR `customers` > `customers_used`)" ); Database::pexecute($available_admins_stmt); $admin_select = makeoption("-----", 0, true, true, true); diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index 5ad847b3..25fa60f0 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -29,16 +29,18 @@ abstract class ApiCommand throw new Exception("Invalid user data", 500); } $this->logger = FroxlorLogger::getInstanceOf($this->user_data); - if ($this->debug) { - $this->logger()->logAction(LOG_ERROR, LOG_DEBUG, json_encode($params, JSON_UNESCAPED_SLASHES)); - } $this->initLang(); $this->initMail(); + + if ($this->debug) { + $this->logger()->logAction(LOG_ERROR, LOG_DEBUG, "[API] ".get_called_class().": ".json_encode($params, JSON_UNESCAPED_SLASHES)); + } } private function initLang() { + global $lng; // query the whole table $result_stmt = Database::query("SELECT * FROM `" . TABLE_PANEL_LANGUAGE . "`"); diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index 84525d0c..eb1b1085 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -50,7 +50,7 @@ class Customers extends ApiCommand if ($result) { return $this->response(200, "successfull", $result); } - throw new Exception("Customer with id #" . $id . " could not be found"); + throw new Exception("Customer with id #" . $id . " could not be found", 404); } throw new Exception("Not allowed to execute given command.", 403); } diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index ed5db265..2d0eb4dd 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -54,7 +54,7 @@ class Domains extends ApiCommand if ($result) { return $this->response(200, "successfull", $result); } - throw new Exception("Domain with id #" . $id . " could not be found"); + throw new Exception("Domain with id #" . $id . " could not be found", 404); } throw new Exception("Not allowed to execute given command.", 403); } diff --git a/lib/classes/api/commands/class.IpsAndPorts.php b/lib/classes/api/commands/class.IpsAndPorts.php index 94d27115..f0b9d8e1 100644 --- a/lib/classes/api/commands/class.IpsAndPorts.php +++ b/lib/classes/api/commands/class.IpsAndPorts.php @@ -37,7 +37,7 @@ class IpsAndPorts extends ApiCommand if ($result) { return $this->response(200, "successfull", $result); } - throw new Exception("IP/port with id #" . $id . " could not be found"); + throw new Exception("IP/port with id #" . $id . " could not be found", 404); } throw new Exception("Not allowed to execute given command.", 403); } From 5afdbae83afd2b3e79a6ef364c8ec5f74685c92e Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Fri, 16 Feb 2018 15:50:31 +0100 Subject: [PATCH 018/746] minor phpDoc fixes in Logger classes Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/logger/abstract.AbstractLogger.php | 6 +++--- lib/classes/logger/class.FileLogger.php | 4 ++-- lib/classes/logger/class.FroxlorLogger.php | 4 ++-- lib/classes/logger/class.MysqlLogger.php | 2 +- lib/classes/logger/class.SysLogger.php | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/classes/logger/abstract.AbstractLogger.php b/lib/classes/logger/abstract.AbstractLogger.php index c3a1eeca..cfd01f70 100644 --- a/lib/classes/logger/abstract.AbstractLogger.php +++ b/lib/classes/logger/abstract.AbstractLogger.php @@ -37,19 +37,19 @@ abstract class AbstractLogger { /** * Enable/Disable Logging - * @var logenabled + * @var boolean */ private $logenabled = false; /** * Enable/Disable Cronjob-Logging - * @var logcronjob + * @var boolean */ private $logcronjob = false; /** * Loggin-Severity - * @var severity + * @var int */ private $severity = 1; diff --git a/lib/classes/logger/class.FileLogger.php b/lib/classes/logger/class.FileLogger.php index 47a784c7..5f48c5d7 100644 --- a/lib/classes/logger/class.FileLogger.php +++ b/lib/classes/logger/class.FileLogger.php @@ -30,13 +30,13 @@ class FileLogger extends AbstractLogger { /** * Logfile - * @var logfile + * @var string */ private $logfile = null; /** * Syslogger Objects Array - * @var loggers + * @var FileLogger[] */ static private $loggers = array(); diff --git a/lib/classes/logger/class.FroxlorLogger.php b/lib/classes/logger/class.FroxlorLogger.php index 467dd160..78fae694 100644 --- a/lib/classes/logger/class.FroxlorLogger.php +++ b/lib/classes/logger/class.FroxlorLogger.php @@ -30,13 +30,13 @@ class FroxlorLogger { /** * LogTypes Array - * @var logtypes + * @var array */ static private $logtypes = null; /** * Logger-Object-Array - * @var loggers + * @var FroxlorLogger[] */ static private $loggers = null; diff --git a/lib/classes/logger/class.MysqlLogger.php b/lib/classes/logger/class.MysqlLogger.php index b21ed392..0136b56f 100644 --- a/lib/classes/logger/class.MysqlLogger.php +++ b/lib/classes/logger/class.MysqlLogger.php @@ -30,7 +30,7 @@ class MysqlLogger extends AbstractLogger { /** * Syslogger Objects Array - * @var loggers + * @var MysqlLogger[] */ static private $loggers = array(); diff --git a/lib/classes/logger/class.SysLogger.php b/lib/classes/logger/class.SysLogger.php index f15396e2..42255484 100644 --- a/lib/classes/logger/class.SysLogger.php +++ b/lib/classes/logger/class.SysLogger.php @@ -30,7 +30,7 @@ class SysLogger extends AbstractLogger { /** * Syslogger Objects Array - * @var loggers + * @var SysLogger[] */ static private $loggers = array(); From fd287e7be483509220d1164df0c4a3780cde04a6 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sat, 17 Feb 2018 14:51:04 +0100 Subject: [PATCH 019/746] add apache restart command after enabling modules in config-templates Signed-off-by: Michael Kaufmann (d00p) --- install/scripts/config-services.php | 4 ++-- lib/configfiles/jessie.xml | 10 ++++++++++ lib/configfiles/precise.xml | 10 ++++++++++ lib/configfiles/stretch.xml | 10 ++++++++++ lib/configfiles/trusty.xml | 15 +++++++++++++++ lib/configfiles/wheezy.xml | 15 +++++++++++++++ 6 files changed, 62 insertions(+), 2 deletions(-) diff --git a/install/scripts/config-services.php b/install/scripts/config-services.php index 1236fbf5..69d1b654 100755 --- a/install/scripts/config-services.php +++ b/install/scripts/config-services.php @@ -316,7 +316,7 @@ class Action case "file": if (array_key_exists('content', $action)) { CmdLineHandler::printwarn("Creating file '" . $action['name'] . "'"); - file_put_contents($action['name'], strtr($action['content'], $replace_arr)); + file_put_contents($action['name'], trim(strtr($action['content'], $replace_arr))); } elseif (array_key_exists('subcommands', $action)) { foreach ($action['subcommands'] as $fileaction) { if (array_key_exists('execute', $fileaction) && $fileaction['execute'] == "pre") { @@ -325,7 +325,7 @@ class Action exec(strtr($fileaction['content'], $replace_arr)); } elseif ($fileaction['type'] == 'file') { CmdLineHandler::printwarn("Creating file '" . $fileaction['name'] . "'"); - file_put_contents($fileaction['name'], strtr($fileaction['content'], $replace_arr)); + file_put_contents($fileaction['name'], trim(strtr($fileaction['content'], $replace_arr))); } } } diff --git a/lib/configfiles/jessie.xml b/lib/configfiles/jessie.xml index 10ee0a2f..062cdb9b 100644 --- a/lib/configfiles/jessie.xml +++ b/lib/configfiles/jessie.xml @@ -48,6 +48,11 @@ //service[@type='http']/general/commands + + {{settings.system.use_ssl}} + + + {{settings.phpfpm.enabled}} @@ -4753,6 +4758,11 @@ aliases: files + + {{settings.system.webserver}} + + + diff --git a/lib/configfiles/precise.xml b/lib/configfiles/precise.xml index 806d86e6..7017ec3c 100644 --- a/lib/configfiles/precise.xml +++ b/lib/configfiles/precise.xml @@ -49,6 +49,11 @@ //service[@type='http']/general/commands + + {{settings.system.use_ssl}} + + + {{settings.phpfpm.enabled}} @@ -1752,6 +1757,11 @@ aliases: files + + {{settings.system.webserver}} + + + diff --git a/lib/configfiles/stretch.xml b/lib/configfiles/stretch.xml index 7d9eab6b..df0a1cd6 100644 --- a/lib/configfiles/stretch.xml +++ b/lib/configfiles/stretch.xml @@ -48,6 +48,11 @@ //service[@type='http']/general/commands + + {{settings.system.use_ssl}} + + + {{settings.phpfpm.enabled}} @@ -4647,6 +4652,11 @@ aliases: files + + {{settings.system.webserver}} + + + diff --git a/lib/configfiles/trusty.xml b/lib/configfiles/trusty.xml index 0dc4c61f..07788901 100644 --- a/lib/configfiles/trusty.xml +++ b/lib/configfiles/trusty.xml @@ -49,6 +49,11 @@ //service[@type='http']/general/commands + + {{settings.system.use_ssl}} + + + {{settings.phpfpm.enabled}} @@ -85,6 +90,11 @@ Alias "/.well-known/acme-challenge" "{{settings.system.letsencryptchallengepath} //service[@type='http']/general/commands + + {{settings.system.use_ssl}} + + + {{settings.phpfpm.enabled}} @@ -1761,6 +1771,11 @@ aliases: files + + {{settings.system.webserver}} + + + diff --git a/lib/configfiles/wheezy.xml b/lib/configfiles/wheezy.xml index 55ba59d4..17ed9ed9 100644 --- a/lib/configfiles/wheezy.xml +++ b/lib/configfiles/wheezy.xml @@ -49,6 +49,11 @@ //service[@type='http']/general/commands + + {{settings.system.use_ssl}} + + + {{settings.phpfpm.enabled}} @@ -85,6 +90,11 @@ Alias "/.well-known/acme-challenge" "{{settings.system.letsencryptchallengepath} //service[@type='http']/general/commands + + {{settings.system.use_ssl}} + + + {{settings.phpfpm.enabled}} @@ -5561,6 +5571,11 @@ aliases: files + + {{settings.system.webserver}} + + + From 9619abdad7d3ae2edefd1cab199081cd14194d3f Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sat, 17 Feb 2018 16:00:50 +0100 Subject: [PATCH 020/746] when importing settings with enabled ssl flag, validate that the target system has ssl enabled ip's to avoid unexpected behaviour, tthx v3ng for testing Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/settings/class.SImExporter.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/classes/settings/class.SImExporter.php b/lib/classes/settings/class.SImExporter.php index 428d8beb..10e8aa32 100644 --- a/lib/classes/settings/class.SImExporter.php +++ b/lib/classes/settings/class.SImExporter.php @@ -98,14 +98,28 @@ class SImExporter // when there were changes in the variable-name or similar unset($_data['panel.version']); unset($_data['panel.db_version']); - + // validate we got ssl enabled ips when ssl is enabled + // otherwise deactivate it + if ($_data['system.use_ssl'] == 1) { + $result_ssl_ipsandports_stmt = Database::prepare(" + SELECT COUNT(*) as count_ssl_ip FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `ssl`='1' + "); + $result = Database::pexecute_first($result_ssl_ipsandports_stmt); + if ($result['count_ssl_ip'] <= 0) { + // no ssl-ip -> deactivate + $_data['system.use_ssl'] = 0; + // deactivate other ssl-related settings + $_data['system.leenabled'] = 0; + $_data['system.le_froxlor_enabled'] = 0; + $_data['system.le_froxlor_redirect'] = 0; + } + } // store new data foreach ($_data as $index => $value) { Settings::Set($index, $value); } // save to DB Settings::Flush(); - // all good return true; } From 532551263d65c8e9f483c60745d544df0297fce7 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sun, 18 Feb 2018 10:19:17 +0100 Subject: [PATCH 021/746] add new api-module to output list of possible modules/functions Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/abstract.ApiCommand.php | 10 - lib/classes/api/commands/class.Customers.php | 31 ++- lib/classes/api/commands/class.Domains.php | 24 ++- lib/classes/api/commands/class.Froxlor.php | 182 ++++++++++++++++++ .../api/commands/class.IpsAndPorts.php | 23 ++- lib/classes/api/interface.ResourceEntity.php | 15 ++ 6 files changed, 272 insertions(+), 13 deletions(-) create mode 100644 lib/classes/api/commands/class.Froxlor.php create mode 100644 lib/classes/api/interface.ResourceEntity.php diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index 25fa60f0..ec473fbb 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -213,16 +213,6 @@ abstract class ApiCommand return $json_response; } - public abstract function list(); - - public abstract function get(); - - public abstract function add(); - - public abstract function update(); - - public abstract function delete(); - private function readUserData($header = null) { $sel_stmt = Database::prepare("SELECT * FROM `api_keys` WHERE `apikey` = :ak AND `secret` = :as"); diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index eb1b1085..2065a1d7 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -1,8 +1,13 @@ isAdmin()) { @@ -32,6 +37,14 @@ class Customers extends ApiCommand throw new Exception("Not allowed to execute given command.", 403); } + /** + * return a customer entry by id + * + * @param int $id customer-id + * + * @throws Exception + * @return array + */ public function get() { if ($this->isAdmin()) { @@ -655,6 +668,14 @@ class Customers extends ApiCommand throw new Exception("Not allowed to execute given command.", 403); } + /** + * delete a customer entry by id + * + * @param int $id customer-id + * + * @throws Exception + * @return array + */ public function delete() { if ($this->isAdmin()) { @@ -880,6 +901,14 @@ class Customers extends ApiCommand throw new Exception("Not allowed to execute given command.", 403); } + /** + * unlock a locked customer by id + * + * @param int $id customer-id + * + * @throws Exception + * @return array + */ public function unlock() { if ($this->isAdmin()) { diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index 2d0eb4dd..d4f88f84 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -1,8 +1,13 @@ isAdmin()) { @@ -32,6 +37,15 @@ class Domains extends ApiCommand throw new Exception("Not allowed to execute given command.", 403); } + /** + * return a domain entry by id + * + * @param int $id domain-id + * @param boolean $no_std_subdomain optional, default false + * + * @throws Exception + * @return array + */ public function get() { if ($this->isAdmin()) { @@ -1503,6 +1517,14 @@ class Domains extends ApiCommand throw new Exception("Not allowed to execute given command.", 403); } + /** + * delete a domain entry by id + * + * @param int $id domain-id + * + * @throws Exception + * @return array + */ public function delete() { if ($this->isAdmin()) { diff --git a/lib/classes/api/commands/class.Froxlor.php b/lib/classes/api/commands/class.Froxlor.php new file mode 100644 index 00000000..01c316e5 --- /dev/null +++ b/lib/classes/api/commands/class.Froxlor.php @@ -0,0 +1,182 @@ +getParam('module'); + + $functions = array(); + if ($module != null) { + // check existence + $this->requireModules($module); + // now get all static functions + $reflection = new \ReflectionClass($module); + $_functions = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC); + foreach ($_functions as $func) { + if ($func->class == $module && $func->isPublic()) { + array_push($functions, array_merge(array( + 'module' => $module, + 'function' => $func->name + ), $this->_getParamListFromDoc($module, $func->name))); + } + } + } else { + // check all the modules + $path = FROXLOR_INSTALL_DIR . '/lib/classes/api/commands/'; + // valid directory? + if (is_dir($path)) { + // create RecursiveIteratorIterator + $its = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path)); + // check every file + foreach ($its as $fullFileName => $it) { + // does it match the Filename pattern? + $matches = array(); + if (preg_match("/^class\.(.+)\.php$/i", $it->getFilename(), $matches)) { + // check for existence + try { + // set the module to be in our namespace + $mod = $matches[1]; + $this->requireModules($mod); + } catch (Exception $e) { + // @todo log? + continue; + } + // now get all static functions + $reflection = new \ReflectionClass($mod); + $_functions = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC); + foreach ($_functions as $func) { + if ($func->class == $mod && $func->isPublic()) { + array_push($functions, array_merge(array( + 'module' => $matches[1], + 'function' => $func->name + ), $this->_getParamListFromDoc($matches[1], $func->name))); + } + } + } + } + } else { + // yikes - no valid directory to check + throw new Exception("Cannot search directory '" . $path . "'. No such directory.", 500); + } + } + + // return the list + return $this->response(200, "successfull", $functions); + } + + /** + * generate an api-response to list all parameters and the return-value of + * a given module.function-combination + * + * @param string $module + * @param string $function + * + * @throws Exception + * @return array|bool + */ + private function _getParamListFromDoc($module = null, $function = null) + { + try { + // set the module to be in our namespace + $cls = new \ReflectionMethod($module, $function); + $comment = $cls->getDocComment(); + if ($comment == false) { + return array( + 'head' => 'There is no comment-block for "' . $module . '.' . $function . '"' + ); + } + $clines = explode("\n", $comment); + $result = array(); + $result['params'] = array(); + foreach ($clines as $c) { + $c = trim($c); + // check param-section + if (strpos($c, '@param')) { + preg_match('/^\*\s\@param\s(.+)\s(\$\w+)(\s.*)?/', $c, $r); + // cut $ off the parameter-name as it is not wanted in the api-request + $result['params'][] = array( + 'parameter' => substr($r[2], 1), + 'type' => $r[1], + 'desc' => (isset($r[3]) ? trim($r['3']) : '') + ); + } // check return-section + elseif (strpos($c, '@return')) { + preg_match('/^\*\s\@return\s(\w+)(\s.*)?/', $c, $r); + if (! isset($r[0]) || empty($r[0])) { + $r[1] = 'null'; + $r[2] = 'This function has no return value given'; + } + $result['return'] = array( + 'type' => $r[1], + 'desc' => (isset($r[2]) ? trim($r[2]) : '') + ); + } else if (! empty($c) && strpos($c, '@throws') === false) { + if (substr($c, 0, 3) == "/**") + continue; + if (substr($c, 0, 2) == "*/") + continue; + if (substr($c, 0, 1) == "*") + $c = trim(substr($c, 1)); + if (empty($c)) + continue; + if (! isset($result['head']) || empty($result['head'])) { + $result['head'] = $c . " "; + } else { + $result['head'] .= $c . " "; + } + } + } + return $result; + } catch (\ReflectionException $e) { + return array(); + } + } + + /** + * this functions is used to check the availability + * of a given list of modules. + * If either one of + * them are not found, throw an Exception + * + * @param string|array $modules + * + * @throws Exception + */ + private function requireModules($modules = null) + { + if ($modules != null) { + // no array -> create one + if (! is_array($modules)) { + $modules = array( + $modules + ); + } + // check all the modules + foreach ($modules as $module) { + try { + // can we use the class? + if (class_exists($module)) { + continue; + } else { + throw new Exception('The required class "' . $module . '" could not be found but the module-file exists', 404); + } + } catch (Exception $e) { + // The autoloader will throw an Exception + // that the required class could not be found + // but we want a nicer error-message for this here + throw new Exception('The required module "' . $module . '" could not be found', 404); + } + } + } + } +} diff --git a/lib/classes/api/commands/class.IpsAndPorts.php b/lib/classes/api/commands/class.IpsAndPorts.php index f0b9d8e1..9baa0a64 100644 --- a/lib/classes/api/commands/class.IpsAndPorts.php +++ b/lib/classes/api/commands/class.IpsAndPorts.php @@ -1,8 +1,13 @@ isAdmin() && $this->getUserDetail('change_serversettings')) { @@ -23,6 +28,14 @@ class IpsAndPorts extends ApiCommand throw new Exception("Not allowed to execute given command.", 403); } + /** + * return an ip/port entry by id + * + * @param int $id ip-port-id + * + * @throws Exception + * @return array + */ public function get() { if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { @@ -314,6 +327,14 @@ class IpsAndPorts extends ApiCommand throw new Exception("Not allowed to execute given command.", 403); } + /** + * delete an ip/port entry by id + * + * @param int $id ip-port-id + * + * @throws Exception + * @return array + */ public function delete() { if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { diff --git a/lib/classes/api/interface.ResourceEntity.php b/lib/classes/api/interface.ResourceEntity.php new file mode 100644 index 00000000..6361eb46 --- /dev/null +++ b/lib/classes/api/interface.ResourceEntity.php @@ -0,0 +1,15 @@ + Date: Mon, 19 Feb 2018 08:59:24 +0100 Subject: [PATCH 022/746] enhance ApiCommand::getParam() to specify required and optional parameter Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/abstract.ApiCommand.php | 67 +++- lib/classes/api/commands/class.Customers.php | 95 +++--- lib/classes/api/commands/class.Domains.php | 289 +++++++++--------- .../api/commands/class.IpsAndPorts.php | 55 ++-- 4 files changed, 283 insertions(+), 223 deletions(-) diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index ec473fbb..d45808be 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -32,9 +32,9 @@ abstract class ApiCommand $this->initLang(); $this->initMail(); - + if ($this->debug) { - $this->logger()->logAction(LOG_ERROR, LOG_DEBUG, "[API] ".get_called_class().": ".json_encode($params, JSON_UNESCAPED_SLASHES)); + $this->logger()->logAction(LOG_ERROR, LOG_DEBUG, "[API] " . get_called_class() . ": " . json_encode($params, JSON_UNESCAPED_SLASHES)); } } @@ -146,21 +146,72 @@ abstract class ApiCommand } /** - * receive field from parameter-list + * get specific parameter from the parameterlist; + * check for existence and != empty if needed. + * Maybe more in the future * * @param string $param + * parameter to get out of the request-parameter list + * @param bool $optional + * default: false * @param mixed $default - * set if param is not found + * value which is returned if optional=true and param is not set * * @throws Exception * @return mixed */ - protected function getParam($param = null, $default = null) + protected function getParam($param = null, $optional = false, $default = '') { - if (isset($this->cmd_params[$param])) { - return $this->cmd_params[$param]; + // does it exist? + if (! isset($this->cmd_params[$param])) { + if ($optional === false) { + // get module + function for better error-messages + $inmod = $this->getModFunctionString(); + throw new Exception('Requested parameter "' . $param . '" could not be found for "' . $inmod . '"', 404); + } + return $default; + } + // is it empty? - test really on string, as value 0 is being seen as empty by php + if ($this->cmd_params[$param] === "") { + if ($optional === false) { + // get module + function for better error-messages + $inmod = $this->getModFunctionString(); + throw new Exception('Requested parameter "' . $param . '" is empty where it should not be for "' . $inmod . '"', 406); + } + return ''; + } + // everything else is fine + return $this->cmd_params[$param]; + } + + /** + * returns "module::function()" for better error-messages (missing parameter etc.) + * makes debugging a whole lot more comfortable + * + * @return string + */ + private function getModFunctionString() + { + $_c = get_called_class(); + + $level = 2; + if (version_compare(PHP_VERSION, "5.4.0", "<")) { + $t = debug_backtrace(); + } else { + $t = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + } + while (true) { + $c = $t[$level]['class']; + $f = $t[$level]['function']; + if ($c != get_called_class()) { + $level ++; + if ($level > 5) { + break; + } + continue; + } + return $c . ':' . $f; } - return $default; } /** diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index 2065a1d7..820703eb 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -76,89 +76,90 @@ class Customers extends ApiCommand implements ResourceEntity if ($this->getUserDetail('customers_used') < $this->getUserDetail('customers') || $this->getUserDetail('customers') == '-1') { $idna_convert = new idna_convert_wrapper(); - $name = validate($this->getParam('name'), 'name', '', '', array(), true); - $firstname = validate($this->getParam('firstname'), 'first name', '', '', array(), true); - $company = validate($this->getParam('company'), 'company', '', '', array(), true); - $street = validate($this->getParam('street'), 'street', '', '', array(), true); - $zipcode = validate($this->getParam('zipcode'), 'zipcode', '/^[0-9 \-A-Z]*$/', '', array(), true); - $city = validate($this->getParam('city'), 'city', '', '', array(), true); - $phone = validate($this->getParam('phone'), 'phone', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); - $fax = validate($this->getParam('fax'), 'fax', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); + $name = validate($this->getParam('name', true, ''), 'name', '', '', array(), true); + $firstname = validate($this->getParam('firstname', true, ''), 'first name', '', '', array(), true); + $company_required = (empty($name) && empty($first)); + $company = validate($this->getParam('company', $company_required, ''), 'company', '', '', array(), true); + $street = validate($this->getParam('street', true, ''), 'street', '', '', array(), true); + $zipcode = validate($this->getParam('zipcode', true, ''), 'zipcode', '/^[0-9 \-A-Z]*$/', '', array(), true); + $city = validate($this->getParam('city', true, ''), 'city', '', '', array(), true); + $phone = validate($this->getParam('phone', true, ''), 'phone', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); + $fax = validate($this->getParam('fax', true, ''), 'fax', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); $email = $idna_convert->encode(validate($this->getParam('email'), 'email', '', '', array(), true)); - $customernumber = validate($this->getParam('customernumber'), 'customer number', '/^[A-Za-z0-9 \-]*$/Di', '', array(), true); - $def_language = validate($this->getParam('def_language'), 'default language', '', '', array(), true); - $gender = intval_ressource($this->getParam('gender', 0)); + $customernumber = validate($this->getParam('customernumber', true, ''), 'customer number', '/^[A-Za-z0-9 \-]*$/Di', '', array(), true); + $def_language = validate($this->getParam('def_language', true, ''), 'default language', '', '', array(), true); + $gender = intval_ressource($this->getParam('gender', true, 0)); - $custom_notes = validate(str_replace("\r\n", "\n", $this->getParam('custom_notes', '')), 'custom_notes', '/^[^\0]*$/', '', array(), true); - $custom_notes_show = $this->getParam('custom_notes_show', 0); + $custom_notes = validate(str_replace("\r\n", "\n", $this->getParam('custom_notes', true, '')), 'custom_notes', '/^[^\0]*$/', '', array(), true); + $custom_notes_show = $this->getParam('custom_notes_show', true, 0); - $diskspace = intval_ressource($this->getParam('diskspace', 0)); - if ($this->getParam('diskspace_ul', 0) == -1) { + $diskspace = intval_ressource($this->getParam('diskspace', true, 0)); + if ($this->getParam('diskspace_ul', true, 0) == -1) { $diskspace = - 1; } - $traffic = doubleval_ressource($this->getParam('traffic', 0)); - if ($this->getParam('traffic_ul', 0) == -1) { + $traffic = doubleval_ressource($this->getParam('traffic', true, 0)); + if ($this->getParam('traffic_ul', true, 0) == -1) { $traffic = - 1; } - $subdomains = intval_ressource($this->getParam('subdomains', 0)); - if ($this->getParam('subdomains_ul', 0) == -1) { + $subdomains = intval_ressource($this->getParam('subdomains', true, 0)); + if ($this->getParam('subdomains_ul', true, 0) == -1) { $subdomains = - 1; } - $emails = intval_ressource($this->getParam('emails', 0)); - if ($this->getParam('emails_ul', 0) == -1) { + $emails = intval_ressource($this->getParam('emails', true, 0)); + if ($this->getParam('emails_ul', true, 0) == -1) { $emails = - 1; } - $email_accounts = intval_ressource($this->getParam('email_accounts', 0)); - if ($this->getParam('email_accounts_ul', 0) == -1) { + $email_accounts = intval_ressource($this->getParam('email_accounts', true, 0)); + if ($this->getParam('email_accounts_ul', true, 0) == -1) { $email_accounts = - 1; } - $email_forwarders = intval_ressource($this->getParam('email_forwarders', 0)); - if ($this->getParam('email_forwarders_ul', 0) == -1) { + $email_forwarders = intval_ressource($this->getParam('email_forwarders', true, 0)); + if ($this->getParam('email_forwarders_ul', true, 0) == -1) { $email_forwarders = - 1; } if (Settings::Get('system.mail_quota_enabled') == '1') { - $email_quota = validate($this->getParam('email_quota', 0), 'email_quota', '/^\d+$/', 'vmailquotawrong', array( + $email_quota = validate($this->getParam('email_quota', true, 0), 'email_quota', '/^\d+$/', 'vmailquotawrong', array( '0', '' ), true); - if ($this->getParam('email_quota_ul', 0) == -1) { + if ($this->getParam('email_quota_ul', true, 0) == -1) { $email_quota = - 1; } } else { $email_quota = - 1; } - $email_imap = $this->getParam('email_imap', 0); - $email_pop3 = $this->getParam('email_pop3', 0); + $email_imap = $this->getParam('email_imap', true, 0); + $email_pop3 = $this->getParam('email_pop3', true, 0); - $ftps = intval_ressource($this->getParam('ftps', 0)); - if ($this->getParam('ftps_ul', 0) == -1) { + $ftps = intval_ressource($this->getParam('ftps', true, 0)); + if ($this->getParam('ftps_ul', true, 0) == -1) { $ftps = - 1; } if (Settings::Get('ticket.enabled') == '1') { - $tickets = intval_ressource($this->getParam('tickets', 0)); - if ($this->getParam('tickets_ul', 0) == -1) { + $tickets = intval_ressource($this->getParam('tickets', true, 0)); + if ($this->getParam('tickets_ul', true, 0) == -1) { $tickets = - 1; } } else { $tickets = - 1; } - $mysqls = intval_ressource($this->getParam('mysqls', 0)); - if ($this->getParam('mysqls_ul', 0) == -1) { + $mysqls = intval_ressource($this->getParam('mysqls', true, 0)); + if ($this->getParam('mysqls_ul', true, 0) == -1) { $mysqls = - 1; } - $createstdsubdomain = $this->getParam('createstdsubdomain', 0); + $createstdsubdomain = $this->getParam('createstdsubdomain', true, 0); - $password = validate($this->getParam('new_customer_password', ''), 'password', '', '', array(), true); + $password = validate($this->getParam('new_customer_password', true, ''), 'password', '', '', array(), true); // only check if not empty, // cause empty == generate password automatically if ($password != '') { @@ -170,20 +171,20 @@ class Customers extends ApiCommand implements ResourceEntity $gender = 0; } - $sendpassword = $this->getParam('sendpassword', 0); - $phpenabled = $this->getParam('phpenabled', 0); + $sendpassword = $this->getParam('sendpassword', true, 0); + $phpenabled = $this->getParam('phpenabled', true, 0); $allowed_phpconfigs = array(); - if (! empty($this->getParam('allowed_phpconfigs', array())) && is_array($this->getParam('allowed_phpconfigs'))) { - foreach ($this->getParam('allowed_phpconfigs') as $allowed_phpconfig) { + if (! empty($this->getParam('allowed_phpconfigs', true, array())) && is_array($this->getParam('allowed_phpconfigs', true, array()))) { + foreach ($this->getParam('allowed_phpconfigs', true, array()) as $allowed_phpconfig) { $allowed_phpconfig = intval($allowed_phpconfig); $allowed_phpconfigs[] = $allowed_phpconfig; } } - $perlenabled = $this->getParam('perlenabled', 0); - $dnsenabled = $this->getParam('dnsenabled', 0); - $store_defaultindex = $this->getParam('store_defaultindex', 0); + $perlenabled = $this->getParam('perlenabled', true, 0); + $dnsenabled = $this->getParam('dnsenabled', true, 0); + $store_defaultindex = $this->getParam('store_defaultindex', true, 0); $diskspace = $diskspace * 1024; $traffic = $traffic * 1024 * 1024; @@ -212,7 +213,7 @@ class Customers extends ApiCommand implements ResourceEntity standard_error('emailiswrong', $email, true); } else { - if ($this->getParam('new_loginname', '') != '') { + if ($this->getParam('new_loginname', true, '') != '') { $accountnumber = intval(Settings::Get('system.lastaccountnumber')); $loginname = validate($this->getParam('new_loginname'), 'loginname', '/^[a-z][a-z0-9\-_]+$/i', '', array(), true); @@ -672,6 +673,7 @@ class Customers extends ApiCommand implements ResourceEntity * delete a customer entry by id * * @param int $id customer-id + * @param bool $delete_userfiles optional, default false * * @throws Exception * @return array @@ -680,6 +682,7 @@ class Customers extends ApiCommand implements ResourceEntity { if ($this->isAdmin()) { $id = $this->getParam('id'); + $delete_userfiles = $this->getParam('delete_userfiles', true, 0); $json_result = Customers::getLocal($this->getUserData(), array( 'id' => $id @@ -872,7 +875,7 @@ class Customers extends ApiCommand implements ResourceEntity // Using nameserver, insert a task which rebuilds the server config inserttask('4'); - if ($this->getParam('delete_userfiles', 0) == 1) { + if ($delete_userfiles == 1) { // insert task to remove the customers files from the filesystem inserttask('6', $result['loginname']); } diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index d4f88f84..481a8ba2 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -40,9 +40,11 @@ class Domains extends ApiCommand implements ResourceEntity /** * return a domain entry by id * - * @param int $id domain-id - * @param boolean $no_std_subdomain optional, default false - * + * @param int $id + * domain-id + * @param boolean $no_std_subdomain + * optional, default false + * * @throws Exception * @return array */ @@ -50,7 +52,7 @@ class Domains extends ApiCommand implements ResourceEntity { if ($this->isAdmin()) { $id = $this->getParam('id'); - $no_std_subdomain = $this->getParam('no_std_subdomain', false); + $no_std_subdomain = $this->getParam('no_std_subdomain', true, false); $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] get domain #" . $id); $result_stmt = Database::prepare(" SELECT `d`.*, `c`.`customerid` @@ -78,11 +80,50 @@ class Domains extends ApiCommand implements ResourceEntity if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { if ($this->getUserDetail('domains_used') < $this->getUserDetail('domains') || $this->getUserDetail('domains') == '-1') { - if ($this->getParam('domain') == Settings::Get('system.hostname')) { + // parameters + $p_domain = $this->getParam('domain'); + $customerid = intval($this->getParam('customerid')); + $p_ipandports = $this->getParam('ipandport'); + + // optional parameters + $adminid = intval($this->getParam('adminid', true, $this->getUserDetail('adminid'))); + $subcanemaildomain = $this->getParam('subcanemaildomain', true, 0); + $isemaildomain = $this->getParam('isemaildomain', true, 0); + $email_only = $this->getParam('email_only', true, 0); + $serveraliasoption = $this->getParam('selectserveralias', true, 0); + $speciallogfile = $this->getParam('speciallogfile', true, 0); + $aliasdomain = intval($this->getParam('alias', true, 0)); + $issubof = intval($this->getParam('issubof', true, 0)); + $registration_date = trim($this->getParam('registration_date', true, '')); + $termination_date = trim($this->getParam('termination_date', true, '')); + $caneditdomain = $this->getParam('caneditdomain', true, 0); + $isbinddomain = $this->getParam('isbinddomain', true, 0); + $zonefile = $this->getParam('zonefile', true, ''); + $dkim = intval($this->getParam('dkim', true, 0)); + $specialsettings = $this->getParam('specialsettings', true, ''); + $notryfiles = $this->getParam('notryfiles', true, 0); + $documentroot = $this->getParam('documentroot', true, ''); + $phpenabled = $this->getParam('phpenabled', true, 0); + $openbasedir = $this->getParam('openbasedir', true, 0); + $phpsettingid = $this->getParam('phpsettingid', true, 1); + $mod_fcgid_starter = $this->getParam('mod_fcgid_starter', true, - 1); + $mod_fcgid_maxrequests = $this->getParam('mod_fcgid_maxrequests', true, - 1); + $ssl_redirect = $this->getParam('ssl_redirect', true, 0); + $letsencrypt = $this->getParam('letsencrypt', true, 0); + $p_ssl_ipandports = $this->getParam('ssl_ipandport', true, array()); + $http2 = $this->getParam('http2', true, 0); + $hsts_maxage = $this->getParam('hsts_maxage', true, 0); + $hsts_sub = $this->getParam('hsts_sub', true, 0); + $hsts_preload = $this->getParam('hsts_preload', true, 0); + $ocsp_stapling = $this->getParam('ocsp_stapling', true, 0); + + // validation + + if ($p_domain == Settings::Get('system.hostname')) { standard_error('admin_domain_emailsystemhostname', '', true); } - if (substr($this->getParam('domain'), 0, 4) == 'xn--') { + if (substr($p_domain, 0, 4) == 'xn--') { standard_error('domain_nopunycode', '', true); } @@ -90,7 +131,7 @@ class Domains extends ApiCommand implements ResourceEntity $domain = $idna_convert->encode(preg_replace(array( '/\:(\d)+$/', '/^https?\:\/\//' - ), '', validate($this->getParam('domain'), 'domain'))); + ), '', validate($p_domain, 'domain'))); // Check whether domain validation is enabled and if, validate the domain if (Settings::Get('system.validate_domain') && ! validateDomain($domain)) { @@ -100,15 +141,6 @@ class Domains extends ApiCommand implements ResourceEntity ), '', true); } - $subcanemaildomain = $this->getParam('subcanemaildomain', 0); - $isemaildomain = $this->getParam('isemaildomain', 0); - $email_only = $this->getParam('email_only', 0); - $serveraliasoption = $this->getParam('selectserveralias', 0); - $speciallogfile = $this->getParam('speciallogfile', 0); - - $aliasdomain = intval($this->getParam('alias')); - $issubof = intval($this->getParam('issubof')); - $customerid = intval($this->getParam('customerid')); $customer_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `customerid` = :customerid " . ($this->getUserDetail('customers_see_all') ? '' : " AND `adminid` = :adminid")); @@ -125,8 +157,6 @@ class Domains extends ApiCommand implements ResourceEntity } if ($this->getUserDetail('customers_see_all') == '1') { - - $adminid = intval($this->getParam('adminid')); $admin_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_ADMINS . "` WHERE `adminid` = :adminid AND (`domains_used` < `domains` OR `domains` = '-1')"); @@ -150,7 +180,6 @@ class Domains extends ApiCommand implements ResourceEntity } $documentroot = makeCorrectDir($customer['documentroot'] . $path_suffix); - $registration_date = trim($this->getParam('registration_date', '')); $registration_date = validate($registration_date, 'registration_date', '/^(19|20)\d\d[-](0[1-9]|1[012])[-](0[1-9]|[12][0-9]|3[01])$/', '', array( '0000-00-00', '0', @@ -160,7 +189,6 @@ class Domains extends ApiCommand implements ResourceEntity $registration_date = null; } - $termination_date = trim($this->getParam('termination_date', '')); $termination_date = validate($termination_date, 'termination_date', '/^(19|20)\d\d[-](0[1-9]|1[012])[-](0[1-9]|[12][0-9]|3[01])$/', '', array( '0000-00-00', '0', @@ -171,31 +199,23 @@ class Domains extends ApiCommand implements ResourceEntity } if ($this->getUserDetail('change_serversettings') == '1') { - - $caneditdomain = $this->getParam('caneditdomain', 0); - - $isbinddomain = '0'; - $zonefile = ''; if (Settings::Get('system.bind_enable') == '1') { - $isbinddomain = $this->getParam('isbinddomain', 0); - $zonefile = validate($this->getParam('zonefile', ''), 'zonefile', '', '', array(), true); + $zonefile = validate($zonefile, 'zonefile', '', '', array(), true); + } else { + $isbinddomain = 0; + $zonefile = ''; } - $dkim = intval($this->getParam('dkim', 0)); - - $specialsettings = validate(str_replace("\r\n", "\n", $this->getParam('specialsettings', '')), 'specialsettings', '/^[^\0]*$/', '', array(), true); - $notryfiles = $this->getParam('notryfiles', 0); - validate($this->getParam('documentroot', ''), 'documentroot', '', '', array(), true); + $specialsettings = validate(str_replace("\r\n", "\n", $specialsettings), 'specialsettings', '/^[^\0]*$/', '', array(), true); + validate($documentroot, 'documentroot', '', '', array(), true); // If path is empty and 'Use domain name as default value for DocumentRoot path' is enabled in settings, // set default path to subdomain or domain name - if ($this->getParam('documentroot', '') != '') { - if (substr($this->getParam('documentroot'), 0, 1) != '/' && ! preg_match('/^https?\:\/\//', $this->getParam('documentroot'))) { - $documentroot .= '/' . $this->getParam('documentroot'); - } else { - $documentroot = $this->getParam('documentroot'); + if ($documentroot != '') { + if (substr($documentroot, 0, 1) != '/' && ! preg_match('/^https?\:\/\//', $documentroot)) { + $documentroot .= '/' . $documentroot; } - } elseif ($this->getParam('documentroot', '') == '' && Settings::Get('system.documentroot_use_default_value') == 1) { + } elseif ($documentroot == '' && Settings::Get('system.documentroot_use_default_value') == 1) { $documentroot = makeCorrectDir($customer['documentroot'] . '/' . $domain); } } else { @@ -212,11 +232,7 @@ class Domains extends ApiCommand implements ResourceEntity if ($this->getUserDetail('caneditphpsettings') == '1' || $this->getUserDetail('change_serversettings') == '1') { - $phpenabled = $this->getParam('phpenabled', 0); - $openbasedir = $this->getParam('openbasedir', 0); - if ((int) Settings::Get('system.mod_fcgid') == 1 || (int) Settings::Get('phpfpm.enabled') == 1) { - $phpsettingid = $this->getParam('phpsettingid', 1); $phpsettingid_check_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_PHPCONFIGS . "` WHERE `id` = :phpsettingid"); @@ -229,11 +245,11 @@ class Domains extends ApiCommand implements ResourceEntity } if ((int) Settings::Get('system.mod_fcgid') == 1) { - $mod_fcgid_starter = validate($this->getParam('mod_fcgid_starter', - 1), 'mod_fcgid_starter', '/^[0-9]*$/', '', array( + $mod_fcgid_starter = validate($mod_fcgid_starter, 'mod_fcgid_starter', '/^[0-9]*$/', '', array( '-1', '' ), true); - $mod_fcgid_maxrequests = validate($this->getParam('mod_fcgid_maxrequests', - 1), 'mod_fcgid_maxrequests', '/^[0-9]*$/', '', array( + $mod_fcgid_maxrequests = validate($mod_fcgid_maxrequests, 'mod_fcgid_maxrequests', '/^[0-9]*$/', '', array( '-1', '' ), true); @@ -282,12 +298,12 @@ class Domains extends ApiCommand implements ResourceEntity } $ipandports = array(); - if (! empty($this->getParam('ipandport')) && ! is_array($this->getParam('ipandport'))) { - $this->updateParam('ipandport', unserialize($this->getParam('ipandport'))); + if (! empty($p_ipandport) && ! is_array($p_ipandports)) { + $p_ipandports = unserialize($p_ipandports); } - if (! empty($this->getParam('ipandport')) && is_array($this->getParam('ipandport'))) { - foreach ($this->getParam('ipandport') as $ipandport) { + if (! empty($p_ipandports) && is_array($p_ipandports)) { + foreach ($p_ipandports as $ipandport) { $ipandport = intval($ipandport); $ipandport_check_stmt = Database::prepare(" SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` @@ -306,18 +322,16 @@ class Domains extends ApiCommand implements ResourceEntity } } - if (Settings::Get('system.use_ssl') == "1" && ! empty($this->getParam('ssl_ipandport'))) { - $ssl_redirect = $this->getParam('ssl_redirect', 0); - $letsencrypt = $this->getParam('letsencrypt', 0); + if (Settings::Get('system.use_ssl') == "1" && ! empty($p_ssl_ipandports)) { $ssl_ipandports = array(); - if (! empty($this->getParam('ssl_ipandport')) && ! is_array($this->getParam('ssl_ipandport'))) { - $this->updateParam('ssl_ipandport', unserialize($this->getParam('ssl_ipandport'))); + if (! empty($p_ssl_ipandports) && ! is_array($p_ssl_ipandports)) { + $p_ssl_ipandports = unserialize($p_ssl_ipandports); } // Verify SSL-Ports - if (! empty($this->getParam('ssl_ipandport')) && is_array($this->getParam('ssl_ipandport'))) { - foreach ($this->getParam('ssl_ipandport') as $ssl_ipandport) { + if (! empty($p_ssl_ipandports) && is_array($p_ssl_ipandports)) { + foreach ($p_ssl_ipandports as $ssl_ipandport) { if (trim($ssl_ipandport) == "") { continue; } @@ -341,14 +355,6 @@ class Domains extends ApiCommand implements ResourceEntity $ssl_ipandports[] = $ssl_ipandport; } } - - $http2 = $this->getParam('http2', 0); - // HSTS - $hsts_maxage = $this->getParam('hsts_maxage', 0); - $hsts_sub = $this->getParam('hsts_sub', 0); - $hsts_preload = $this->getParam('hsts_preload', 0); - // OCSP stapling - $ocsp_stapling = $this->getParam('ocsp_stapling', 0); } else { $ssl_redirect = 0; $letsencrypt = 0; @@ -717,23 +723,57 @@ class Domains extends ApiCommand implements ResourceEntity public function update() { if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { + + // parameters $id = $this->getParam('id'); + // get requested domain $json_result = Domains::getLocal($this->getUserData(), array( 'id' => $id, 'no_std_subdomain' => true ))->get(); $result = json_decode($json_result, true)['data']; - $customer_stmt = Database::prepare(" - SELECT * FROM " . TABLE_PANEL_CUSTOMERS . " WHERE `customerid` = :customerid - "); - $customer = Database::pexecute_first($customer_stmt, array( - 'customerid' => $result['customerid'] - )); + // optional parameters + $p_domain = $this->getParam('domain', true, $result['domain']); + $p_ipandports = $this->getParam('ipandport', true, array()); + $customerid = intval($this->getParam('customerid', true, $result['customerid'])); + $adminid = intval($this->getParam('adminid', true, $result['adminid'])); - $customerid = $this->getParam('customerid', $result['customerid']); + $subcanemaildomain = $this->getParam('subcanemaildomain', true, $result['subcanemaildomain']); + $isemaildomain = $this->getParam('isemaildomain', true, $result['isemaildomain']); + $email_only = $this->getParam('email_only', true, $result['email_only']); + $p_serveraliasoption = $this->getParam('selectserveralias', true, - 1); + $speciallogfile = $this->getParam('speciallogfile', true, $result['speciallogfile']); + $speciallogverified = $this->getParam('speciallogverified', true, 0); + $aliasdomain = intval($this->getParam('alias', true, $result['aliasdomain'])); + $issubof = intval($this->getParam('issubof', true, $result['ismainbutsubto'])); + $registration_date = trim($this->getParam('registration_date', true, $result['registration_date'])); + $termination_date = trim($this->getParam('termination_date', true, $result['termination_date'])); + $caneditdomain = $this->getParam('caneditdomain', true, $result['caneditdomain']); + $isbinddomain = $this->getParam('isbinddomain', true, $result['isbinddomain']); + $zonefile = $this->getParam('zonefile', true, $result['zonefile']); + $dkim = intval($this->getParam('dkim', true, $result['dkim'])); + $specialsettings = $this->getParam('specialsettings', true, $result['specialsettings']); + $ssfs = $this->getParam('specialsettingsforsubdomains', true, 0); + $notryfiles = $this->getParam('notryfiles', true, $result['notryfiles']); + $documentroot = $this->getParam('documentroot', true, $result['documentroot']); + $phpenabled = $this->getParam('phpenabled', true, $result['phpenabled']); + $phpfs = $this->getParam('phpsettingsforsubdomains', true, 0); + $openbasedir = $this->getParam('openbasedir', true, $result['openbasedir']); + $phpsettingid = $this->getParam('phpsettingid', true, $result['phpsettingid']); + $mod_fcgid_starter = $this->getParam('mod_fcgid_starter', true, $result['mod_fcgid_starter']); + $mod_fcgid_maxrequests = $this->getParam('mod_fcgid_maxrequests', true, $result['mod_fcgid_maxrequests']); + $ssl_redirect = $this->getParam('ssl_redirect', true, $result['ssl_redirect']); + $letsencrypt = $this->getParam('letsencrypt', true, $result['letsencrypt']); + $p_ssl_ipandports = $this->getParam('ssl_ipandport', true, array()); + $http2 = $this->getParam('http2', true, $result['http2']); + $hsts_maxage = $this->getParam('hsts_maxage', true, $result['hsts_maxage']); + $hsts_sub = $this->getParam('hsts_sub', true, $result['hsts_sub']); + $hsts_preload = $this->getParam('hsts_preload', true, $result['hsts_preload']); + $ocsp_stapling = $this->getParam('ocsp_stapling', true, $result['ocsp_stapling']); + // handle change of customer (move domain from customer to customer) if ($customerid > 0 && $customerid != $result['customerid'] && Settings::Get('panel.allow_domain_change_customer') == '1') { $customer_stmt = Database::prepare(" @@ -755,7 +795,12 @@ class Domains extends ApiCommand implements ResourceEntity $params['adminid'] = $this->getUserDetail('adminid'); } - $customer = Database::pexecute_first($customer_stmt, $params, true, true); + // get domains customer + $json_result = Customers::getLocal($this->getUserData(), array( + 'id' => $result['customerid'] + ))->get(); + $customer = json_decode($json_result, true)['data']; + if (empty($customer) || $customer['customerid'] != $customerid) { standard_error('customerdoesntexist', '', true); } @@ -763,17 +808,9 @@ class Domains extends ApiCommand implements ResourceEntity $customerid = $result['customerid']; } - $customer_stmt = Database::prepare(" - SELECT * FROM " . TABLE_PANEL_ADMINS . " WHERE `adminid` = :adminid - "); - $admin = Database::pexecute_first($customer_stmt, array( - 'adminid' => $result['adminid'] - ), true, true); - + // handle change of admin (move domain from admin to admin) if ($this->getUserDetail('customers_see_all') == '1') { - $adminid = $this->getParam('adminid', $result['adminid']); - if ($adminid > 0 && $adminid != $result['adminid'] && Settings::Get('panel.allow_domain_change_admin') == '1') { $admin_stmt = Database::prepare(" @@ -794,11 +831,6 @@ class Domains extends ApiCommand implements ResourceEntity $adminid = $result['adminid']; } - $aliasdomain = $this->getParam('alias', $result['aliasdomain']); - $issubof = $this->getParam('issubof', $result['ismainbutsubto']); - $subcanemaildomain = $this->getParam('subcanemaildomain', $result['subcanemaildomain']); - $caneditdomain = $this->getParam('caneditdomain', $result['caneditdomain']); - $registration_date = $this->getParam('registration_date', $result['registration_date']); $registration_date = validate($registration_date, 'registration_date', '/^(19|20)\d\d[-](0[1-9]|1[012])[-](0[1-9]|[12][0-9]|3[01])$/', '', array( '0000-00-00', '0', @@ -807,7 +839,6 @@ class Domains extends ApiCommand implements ResourceEntity if ($registration_date == '0000-00-00') { $registration_date = null; } - $termination_date = $this->getParam('termination_date', $result['termination_date']); $termination_date = validate($termination_date, 'termination_date', '/^(19|20)\d\d[-](0[1-9]|1[012])[-](0[1-9]|[12][0-9]|3[01])$/', '', array( '0000-00-00', '0', @@ -817,39 +848,31 @@ class Domains extends ApiCommand implements ResourceEntity $termination_date = null; } - $isemaildomain = $this->getParam('isemaildomain', $result['isemaildomain']); - $email_only = $this->getParam('email_only', $result['email_only']); - $serveraliasoption = '2'; if ($result['iswildcarddomain'] == '1') { $serveraliasoption = '0'; } elseif ($result['wwwserveralias'] == '1') { $serveraliasoption = '1'; } - if (! empty($this->getParam('selectserveralias'))) { - $serveraliasoption = intval($this->getParam('selectserveralias')); + if ($p_serveraliasoption > - 1) { + $serveraliasoption = $p_serveraliasoption; } - $speciallogfile = $this->getParam('speciallogfile', $result['speciallogfile']); - if ($this->getUserDetail('change_serversettings') == '1') { - $isbinddomain = $result['isbinddomain']; - $zonefile = $result['zonefile']; - if (Settings::Get('system.bind_enable') == '1') { - $isbinddomain = $this->getParam('isbinddomain', $result['isbinddomain']); - $zonefile = validate($this->getParam('zonefile', $result['zonefile']), 'zonefile', '', '', array(), true); + + if (Settings::Get('system.bind_enable') != '1') { + $zonefile = validate($zonefile, 'zonefile', '', '', array(), true); + } else { + $isbinddomain = $result['isbinddomain']; + $zonefile = $result['zonefile']; } - if (Settings::Get('dkim.use_dkim') == '1') { - $dkim = $this->getParam('dkim', $result['dkim']); - } else { + if (Settings::Get('dkim.use_dkim') != '1') { $dkim = $result['dkim']; } - $specialsettings = validate(str_replace("\r\n", "\n", $this->getParam('specialsettings', $result['specialsettings'])), 'specialsettings', '/^[^\0]*$/', '', array(), true); - $ssfs = $this->getParam('specialsettingsforsubdomains', 0); - $notryfiles = $this->getParam('notryfiles', $result['notryfiles']); - $documentroot = validate($this->getParam('documentroot', $result['documentroot']), 'documentroot', '', '', array(), true); + $specialsettings = validate(str_replace("\r\n", "\n", $specialsettings), 'specialsettings', '/^[^\0]*$/', '', array(), true); + $documentroot = validate($documentroot, 'documentroot', '', '', array(), true); if ($documentroot == '') { // If path is empty and 'Use domain name as default value for DocumentRoot path' is enabled in settings, @@ -874,17 +897,9 @@ class Domains extends ApiCommand implements ResourceEntity $documentroot = $result['documentroot']; } - // @TODO unsure whether this will still work - $speciallogverified = $this->getParam('speciallogverified', 0); - if ($this->getUserDetail('caneditphpsettings') == '1' || $this->getUserDetail('change_serversettings') == '1') { - $phpenabled = $this->getParam('phpenabled', $result['phpenabled']); - $openbasedir = $this->getParam('openbasedir', $result['openbasedir']); - $phpfs = $this->getParam('phpsettingsforsubdomains', 0); - if ((int) Settings::Get('system.mod_fcgid') == 1 || (int) Settings::Get('phpfpm.enabled') == 1) { - $phpsettingid = $this->getParam('phpsettingid', $result['phpsettingid']); $phpsettingid_check_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_PHPCONFIGS . "` WHERE `id` = :phpid "); @@ -897,11 +912,11 @@ class Domains extends ApiCommand implements ResourceEntity } if ((int) Settings::Get('system.mod_fcgid') == 1) { - $mod_fcgid_starter = validate($this->getParam('mod_fcgid_starter', $result['mod_fcgid_starter']), 'mod_fcgid_starter', '/^[0-9]*$/', '', array( + $mod_fcgid_starter = validate($mod_fcgid_starter, 'mod_fcgid_starter', '/^[0-9]*$/', '', array( '-1', '' ), true); - $mod_fcgid_maxrequests = validate($this->getParam('mod_fcgid_maxrequests', $result['mod_fcgid_maxrequests']), 'mod_fcgid_maxrequests', '/^[0-9]*$/', '', array( + $mod_fcgid_maxrequests = validate($mod_fcgid_maxrequests, 'mod_fcgid_maxrequests', '/^[0-9]*$/', '', array( '-1', '' ), true); @@ -925,15 +940,15 @@ class Domains extends ApiCommand implements ResourceEntity } $ipandports = array(); - if (! empty($this->getParam('ipandport')) && ! is_array($this->getParam('ipandport'))) { - $this->updateParam('ipandport', unserialize($this->getParam('ipandport'))); + if (! empty($p_ipandports) && ! is_array($p_ipandports)) { + $p_ipandports = unserialize($p_ipandports); } - if (! empty($this->getParam('ipandport')) && is_array($this->getParam('ipandport'))) { + if (! empty($p_ipandports) && is_array($p_ipandports)) { $ipandport_check_stmt = Database::prepare(" SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :ipandport "); - foreach ($this->getParam('ipandport') as $ipandport) { + foreach ($p_ipandports as $ipandport) { if (trim($ipandport) == "") { continue; } @@ -949,20 +964,18 @@ class Domains extends ApiCommand implements ResourceEntity } } - if (Settings::Get('system.use_ssl') == '1' && ! empty($this->getParam('ssl_ipandport'))) { + if (Settings::Get('system.use_ssl') == '1' && ! empty($p_ssl_ipandports)) { $ssl = 1; // if ssl is set and != 0, it can only be 1 - $ssl_redirect = $this->getParam('ssl_redirect', $result['ssl_redirect']); - $letsencrypt = $this->getParam('letsencrypt', $result['letsencrypt']); $ssl_ipandports = array(); - if (! empty($this->getParam('ssl_ipandport')) && ! is_array($this->getParam('ssl_ipandport'))) { - $this->updateParam('ssl_ipandport', unserialize($this->getParam('ssl_ipandport'))); + if (! empty($p_ssl_ipandports) && ! is_array($p_ssl_ipandports)) { + $p_ssl_ipandports = unserialize($p_ssl_ipandports); } - if (! empty($this->getParam('ssl_ipandport')) && is_array($this->getParam('ssl_ipandport'))) { + if (! empty($p_ssl_ipandports) && is_array($p_ssl_ipandports)) { $ssl_ipandport_check_stmt = Database::prepare(" SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :ipandport "); - foreach ($this->getParam('ssl_ipandport') as $ssl_ipandport) { + foreach ($p_ssl_ipandports as $ssl_ipandport) { if (trim($ssl_ipandport) == "") { continue; } @@ -980,14 +993,6 @@ class Domains extends ApiCommand implements ResourceEntity $ssl_ipandports[] = $ssl_ipandport; } } - - $http2 = $this->getParam('http2', $result['http2']); - // HSTS - $hsts_maxage = $this->getParam('hsts_maxage', $result['hsts_maxage']); - $hsts_sub = $this->getParam('hsts_sub', $result['hsts_sub']); - $hsts_preload = $this->getParam('hsts_preload', $result['hsts_preload']); - // OCSP stapling - $ocsp_stapling = $this->getParam('ocsp_stapling', $result['ocsp_stapling']); } else { $ssl_redirect = 0; $letsencrypt = 0; @@ -1304,7 +1309,6 @@ class Domains extends ApiCommand implements ResourceEntity $_update_data = array(); - $ssfs = $this->getParam('specialsettingsforsubdomains', 0); if ($ssfs == 1) { $_update_data['specialsettings'] = $specialsettings; $upd_specialsettings = ", `specialsettings` = :specialsettings "; @@ -1404,7 +1408,6 @@ class Domains extends ApiCommand implements ResourceEntity // if php config is to be set for all subdomains, check here $update_phpconfig = ''; - $phpfs = $this->getParam('phpsettingsforsubdomains', 0); if ($phpfs == 1) { $_update_data['phpsettingid'] = $phpsettingid; $update_phpconfig = ", `phpsettingid` = :phpsettingid"; @@ -1520,8 +1523,10 @@ class Domains extends ApiCommand implements ResourceEntity /** * delete a domain entry by id * - * @param int $id domain-id - * + * @param int $id + * domain-id + * @param bool $delete_mainsubdomains optional, remove also domains that are subdomains of this domain but added as main domains; default false + * * @throws Exception * @return array */ @@ -1529,7 +1534,8 @@ class Domains extends ApiCommand implements ResourceEntity { if ($this->isAdmin()) { $id = $this->getParam('id'); - + $remove_subbutmain_domains = $this->getParam('delete_mainsubdomains', true, 0); + $json_result = Domains::getLocal($this->getUserData(), array( 'id' => $id, 'no_std_subdomain' => true @@ -1538,11 +1544,10 @@ class Domains extends ApiCommand implements ResourceEntity // check for deletion of main-domains which are logically subdomains, #329 $rsd_sql = ''; - $remove_subbutmain_domains = $this->getParam('delete_userfiles', 0) ? 1 : 0; - if ($remove_subbutmain_domains == 1) { + if ($remove_subbutmain_domains) { $rsd_sql .= " OR `ismainbutsubto` = :id"; } - + $subresult_stmt = Database::prepare(" SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE (`id` = :id OR `parentdomainid` = :id " . $rsd_sql . ")"); diff --git a/lib/classes/api/commands/class.IpsAndPorts.php b/lib/classes/api/commands/class.IpsAndPorts.php index 9baa0a64..17b90d3f 100644 --- a/lib/classes/api/commands/class.IpsAndPorts.php +++ b/lib/classes/api/commands/class.IpsAndPorts.php @@ -58,25 +58,26 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity public function add() { if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { + $ip = validate_ip2($this->getParam('ip'), false, 'invalidip', false, false, false, true); - $port = validate($this->getParam('port'), 'port', '/^(([1-9])|([1-9][0-9])|([1-9][0-9][0-9])|([1-9][0-9][0-9][0-9])|([1-5][0-9][0-9][0-9][0-9])|(6[0-4][0-9][0-9][0-9])|(65[0-4][0-9][0-9])|(655[0-2][0-9])|(6553[0-5]))$/Di', array( + $port = validate($this->getParam('port', true, 80), 'port', '/^(([1-9])|([1-9][0-9])|([1-9][0-9][0-9])|([1-9][0-9][0-9][0-9])|([1-5][0-9][0-9][0-9][0-9])|(6[0-4][0-9][0-9][0-9])|(65[0-4][0-9][0-9])|(655[0-2][0-9])|(6553[0-5]))$/Di', array( 'stringisempty', 'myport' ), array(), true); - $listen_statement = ! empty($this->getParam('listen_statement')) ? 1 : 0; - $namevirtualhost_statement = ! empty($this->getParam('namevirtualhost_statement')) ? 1 : 0; - $vhostcontainer = ! empty($this->getParam('vhostcontainer')) ? 1 : 0; - $specialsettings = validate(str_replace("\r\n", "\n", $this->getParam('specialsettings')), 'specialsettings', '/^[^\0]*$/', '', array(), true); - $vhostcontainer_servername_statement = ! empty($this->getParam('vhostcontainer_servername_statement')) ? 1 : 0; - $default_vhostconf_domain = validate(str_replace("\r\n", "\n", $this->getParam('default_vhostconf_domain')), 'default_vhostconf_domain', '/^[^\0]*$/', '', array(), true); - $docroot = validate($this->getParam('docroot'), 'docroot', '', '', array(), true); + $listen_statement = ! empty($this->getParam('listen_statement', true, 0)) ? 1 : 0; + $namevirtualhost_statement = ! empty($this->getParam('namevirtualhost_statement', true, 0)) ? 1 : 0; + $vhostcontainer = ! empty($this->getParam('vhostcontainer', true, 0)) ? 1 : 0; + $specialsettings = validate(str_replace("\r\n", "\n", $this->getParam('specialsettings', true, '')), 'specialsettings', '/^[^\0]*$/', '', array(), true); + $vhostcontainer_servername_statement = ! empty($this->getParam('vhostcontainer_servername_statement', true, 1)) ? 1 : 0; + $default_vhostconf_domain = validate(str_replace("\r\n", "\n", $this->getParam('default_vhostconf_domain', true, '')), 'default_vhostconf_domain', '/^[^\0]*$/', '', array(), true); + $docroot = validate($this->getParam('docroot', true, ''), 'docroot', '', '', array(), true); if ((int) Settings::Get('system.use_ssl') == 1) { - $ssl = ! empty($this->getParam('ssl')) ? intval($this->getParam('ssl')) : 0; - $ssl_cert_file = validate($this->getParam('ssl_cert_file'), 'ssl_cert_file', '', '', array(), true); - $ssl_key_file = validate($this->getParam('ssl_key_file'), 'ssl_key_file', '', '', array(), true); - $ssl_ca_file = validate($this->getParam('ssl_ca_file'), 'ssl_ca_file', '', '', array(), true); - $ssl_cert_chainfile = validate($this->getParam('ssl_cert_chainfile'), 'ssl_cert_chainfile', '', '', array(), true); + $ssl = ! empty($this->getParam('ssl', true, 0)) ? intval($this->getParam('ssl', true, 0)) : 0; + $ssl_cert_file = validate($this->getParam('ssl_cert_file', $ssl, ''), 'ssl_cert_file', '', '', array(), true); + $ssl_key_file = validate($this->getParam('ssl_key_file', $ssl, ''), 'ssl_key_file', '', '', array(), true); + $ssl_ca_file = validate($this->getParam('ssl_ca_file', true, ''), 'ssl_ca_file', '', '', array(), true); + $ssl_cert_chainfile = validate($this->getParam('ssl_cert_chainfile', true, ''), 'ssl_cert_chainfile', '', '', array(), true); } else { $ssl = 0; $ssl_cert_file = ''; @@ -192,25 +193,25 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity ))->get(); $result = json_decode($json_result, true)['data']; - $ip = validate_ip2($this->getParam('ip', $result['ip']), false, 'invalidip', false, false, false, true); - $port = validate($this->getParam('port', $result['port']), 'port', '/^(([1-9])|([1-9][0-9])|([1-9][0-9][0-9])|([1-9][0-9][0-9][0-9])|([1-5][0-9][0-9][0-9][0-9])|(6[0-4][0-9][0-9][0-9])|(65[0-4][0-9][0-9])|(655[0-2][0-9])|(6553[0-5]))$/Di', array( + $ip = validate_ip2($this->getParam('ip', true, $result['ip']), false, 'invalidip', false, false, false, true); + $port = validate($this->getParam('port', true, $result['port']), 'port', '/^(([1-9])|([1-9][0-9])|([1-9][0-9][0-9])|([1-9][0-9][0-9][0-9])|([1-5][0-9][0-9][0-9][0-9])|(6[0-4][0-9][0-9][0-9])|(65[0-4][0-9][0-9])|(655[0-2][0-9])|(6553[0-5]))$/Di', array( 'stringisempty', 'myport' ), array(), true); - $listen_statement = $this->getParam('listen_statement', $result['listen_statement']); - $namevirtualhost_statement = $this->getParam('namevirtualhost_statement', $result['namevirtualhost_statement']); - $vhostcontainer = $this->getParam('vhostcontainer', $result['vhostcontainer']); - $specialsettings = validate(str_replace("\r\n", "\n", $this->getParam('specialsettings', $result['specialsettings'])), 'specialsettings', '/^[^\0]*$/', '', array(), true); - $vhostcontainer_servername_statement = $this->getParam('vhostcontainer_servername_statement', $result['vhostcontainer_servername_statement']); - $default_vhostconf_domain = validate(str_replace("\r\n", "\n", $this->getParam('default_vhostconf_domain', $result['default_vhostconf_domain'])), 'default_vhostconf_domain', '/^[^\0]*$/', '', array(), true); - $docroot = validate($this->getParam('docroot', $result['docroot']), 'docroot', '', '', array(), true); + $listen_statement = $this->getParam('listen_statement', true, $result['listen_statement']); + $namevirtualhost_statement = $this->getParam('namevirtualhost_statement', true, $result['namevirtualhost_statement']); + $vhostcontainer = $this->getParam('vhostcontainer', true, $result['vhostcontainer']); + $specialsettings = validate(str_replace("\r\n", "\n", $this->getParam('specialsettings', true, $result['specialsettings'])), 'specialsettings', '/^[^\0]*$/', '', array(), true); + $vhostcontainer_servername_statement = $this->getParam('vhostcontainer_servername_statement', true, $result['vhostcontainer_servername_statement']); + $default_vhostconf_domain = validate(str_replace("\r\n", "\n", $this->getParam('default_vhostconf_domain',true, $result['default_vhostconf_domain'])), 'default_vhostconf_domain', '/^[^\0]*$/', '', array(), true); + $docroot = validate($this->getParam('docroot', true, $result['docroot']), 'docroot', '', '', array(), true); if ((int) Settings::Get('system.use_ssl') == 1) { - $ssl = $this->getParam('ssl', $result['ssl']); - $ssl_cert_file = validate($this->getParam('ssl_cert_file', $result['ssl_cert_file']), 'ssl_cert_file', '', '', array(), true); - $ssl_key_file = validate($this->getParam('ssl_key_file', $result['ssl_key_file']), 'ssl_key_file', '', '', array(), true); - $ssl_ca_file = validate($this->getParam('ssl_ca_file', $result['ssl_ca_file']), 'ssl_ca_file', '', '', array(), true); - $ssl_cert_chainfile = validate($this->getParam('ssl_cert_chainfile', $result['ssl_cert_chainfile']), 'ssl_cert_chainfile', '', '', array(), true); + $ssl = $this->getParam('ssl', true, $result['ssl']); + $ssl_cert_file = validate($this->getParam('ssl_cert_file', $ssl, $result['ssl_cert_file']), 'ssl_cert_file', '', '', array(), true); + $ssl_key_file = validate($this->getParam('ssl_key_file', $ssl, $result['ssl_key_file']), 'ssl_key_file', '', '', array(), true); + $ssl_ca_file = validate($this->getParam('ssl_ca_file', true, $result['ssl_ca_file']), 'ssl_ca_file', '', '', array(), true); + $ssl_cert_chainfile = validate($this->getParam('ssl_cert_chainfile', true, $result['ssl_cert_chainfile']), 'ssl_cert_chainfile', '', '', array(), true); } else { $ssl = 0; $ssl_cert_file = ''; From 168738b23a6842cf820f70283f00a27c59790297 Mon Sep 17 00:00:00 2001 From: JB1985 Date: Mon, 19 Feb 2018 09:02:34 +0100 Subject: [PATCH 023/746] Update german.lng.php Statt Benutze Lets Encrypt => SSL Zertifikat erstellen (Let\'s Encrypt) --- lng/german.lng.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lng/german.lng.php b/lng/german.lng.php index 1f6ee8f9..322ddb14 100644 --- a/lng/german.lng.php +++ b/lng/german.lng.php @@ -1588,7 +1588,7 @@ $lng['integrity_check']['SubdomainLetsencrypt'] = 'Hauptdomains ohne zugewiesene $lng['admin']['mod_fcgid_umask']['title'] = 'Umask (Standard: 022)'; // Added for let's encrypt -$lng['admin']['letsencrypt']['title'] = 'Benutze Let\'s Encrypt'; +$lng['admin']['letsencrypt']['title'] = 'SSL Zertifikat erstellen (Let\'s Encrypt'); $lng['admin']['letsencrypt']['description'] = 'Holt ein kostenloses Zertifikat von Let\'s Encrypt. Das Zertifikat wird automatisch erstellt und verlängert.
ACHTUNG: Wenn Wildcards aktiviert sind, wird diese Option automatisch deaktiviert. Dieses Feature befindet sich noch im Test.'; $lng['customer']['letsencrypt']['title'] = 'Benutze Let\'s Encrypt'; $lng['customer']['letsencrypt']['description'] = 'Holt ein kostenloses Zertifikat von Let\'s Encrypt. Das Zertifikat wird automatisch erstellt und verlängert.
ACHTUNG: Dieses Feature befindet sich noch im Test.'; From 603e14913ba08bd4bf8ae0e0ed29315d092e0460 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 19 Feb 2018 10:45:41 +0100 Subject: [PATCH 024/746] finished Customer::update(), untested Signed-off-by: Michael Kaufmann (d00p) --- admin_customers.php | 554 +-------------- lib/classes/api/abstract.ApiCommand.php | 10 + lib/classes/api/commands/class.Customers.php | 638 +++++++++++++++--- lib/classes/api/commands/class.Domains.php | 61 +- lib/classes/api/commands/class.Froxlor.php | 79 ++- .../api/commands/class.IpsAndPorts.php | 28 +- .../output/function.standard_success.php | 39 +- 7 files changed, 720 insertions(+), 689 deletions(-) diff --git a/admin_customers.php b/admin_customers.php index fc53871b..3a8c66f1 100644 --- a/admin_customers.php +++ b/admin_customers.php @@ -375,553 +375,15 @@ if ($page == 'customers' if (isset($_POST['send']) && $_POST['send'] == 'send' ) { - - $name = validate($_POST['name'], 'name'); - $firstname = validate($_POST['firstname'], 'first name'); - $company = validate($_POST['company'], 'company'); - $street = validate($_POST['street'], 'street'); - $zipcode = validate($_POST['zipcode'], 'zipcode', '/^[0-9 \-A-Z]*$/'); - $city = validate($_POST['city'], 'city'); - $phone = validate($_POST['phone'], 'phone', '/^[0-9\- \+\(\)\/]*$/'); - $fax = validate($_POST['fax'], 'fax', '/^[0-9\- \+\(\)\/]*$/'); - $email = $idna_convert->encode(validate($_POST['email'], 'email')); - $customernumber = validate($_POST['customernumber'], 'customer number', '/^[A-Za-z0-9 \-]*$/Di'); - $def_language = validate($_POST['def_language'], 'default language'); - $password = validate($_POST['new_customer_password'], 'new password'); - $gender = intval_ressource($_POST['gender']); - - $move_to_admin = isset($_POST['move_to_admin']) ? intval_ressource($_POST['move_to_admin']) : 0; - - $custom_notes = validate(str_replace("\r\n", "\n", $_POST['custom_notes']), 'custom_notes', '/^[^\0]*$/'); - $custom_notes_show = $result['custom_notes_show']; - if (isset($_POST['custom_notes_show'])) { - $custom_notes_show = intval_ressource($_POST['custom_notes_show']); + try { + Customers::getLocal($userinfo, $_POST)->update(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - - $diskspace = intval_ressource($_POST['diskspace']); - if (isset($_POST['diskspace_ul'])) { - $diskspace = - 1; - } - - $traffic = doubleval_ressource($_POST['traffic']); - if (isset($_POST['traffic_ul'])) { - $traffic = - 1; - } - - $subdomains = intval_ressource($_POST['subdomains']); - if (isset($_POST['subdomains_ul'])) { - $subdomains = - 1; - } - - $emails = intval_ressource($_POST['emails']); - if (isset($_POST['emails_ul'])) { - $emails = - 1; - } - - $email_accounts = intval_ressource($_POST['email_accounts']); - if (isset($_POST['email_accounts_ul'])) { - $email_accounts = - 1; - } - - $email_forwarders = intval_ressource($_POST['email_forwarders']); - if (isset($_POST['email_forwarders_ul'])) { - $email_forwarders = - 1; - } - - if (Settings::Get('system.mail_quota_enabled') == '1') { - $email_quota = validate($_POST['email_quota'], 'email_quota', '/^\d+$/', 'vmailquotawrong', array('0', '')); - if (isset($_POST['email_quota_ul'])) { - $email_quota = - 1; - } - } else { - $email_quota = - 1; - } - - $email_imap = 0; - if (isset($_POST['email_imap'])) { - $email_imap = intval_ressource($_POST['email_imap']); - } - - $email_pop3 = 0; - if (isset($_POST['email_pop3'])) { - $email_pop3 = intval_ressource($_POST['email_pop3']); - } - - $ftps = 0; - if (isset($_POST['ftps'])) { - $ftps = intval_ressource($_POST['ftps']); - } - if (isset($_POST['ftps_ul'])) { - $ftps = - 1; - } - - $tickets = (Settings::Get('ticket.enabled') == 1 ? intval_ressource($_POST['tickets']) : 0); - if (isset($_POST['tickets_ul']) - && Settings::Get('ticket.enabled') == '1' - ) { - $tickets = - 1; - } - - // gender out of range? [0,2] - if ($gender < 0 || $gender > 2) { - $gender = 0; - } - - $mysqls = 0; - if (isset($_POST['mysqls'])) { - $mysqls = intval_ressource($_POST['mysqls']); - } - if (isset($_POST['mysqls_ul'])) { - $mysqls = - 1; - } - - $createstdsubdomain = 0; - if (isset($_POST['createstdsubdomain'])) { - $createstdsubdomain = intval($_POST['createstdsubdomain']); - } - - $deactivated = 0; - if (isset($_POST['deactivated'])) { - $deactivated = intval($_POST['deactivated']); - } - - $phpenabled = 0; - if (isset($_POST['phpenabled'])) { - $phpenabled = intval($_POST['phpenabled']); - } - - $allowed_phpconfigs = array(); - if (isset($_POST['allowed_phpconfigs']) && is_array($_POST['allowed_phpconfigs'])) { - foreach ($_POST['allowed_phpconfigs'] as $allowed_phpconfig) { - $allowed_phpconfig = intval($allowed_phpconfig); - $allowed_phpconfigs[] = $allowed_phpconfig; - } - } - - $perlenabled = 0; - if (isset($_POST['perlenabled'])) { - $perlenabled = intval($_POST['perlenabled']); - } - - $dnsenabled = 0; - if (isset($_POST['dnsenabled'])) { - $dnsenabled = intval($_POST['dnsenabled']); - } - - $diskspace = $diskspace * 1024; - $traffic = $traffic * 1024 * 1024; - - if (((($userinfo['diskspace_used'] + $diskspace - $result['diskspace']) > $userinfo['diskspace']) && ($userinfo['diskspace'] / 1024) != '-1') - || ((($userinfo['mysqls_used'] + $mysqls - $result['mysqls']) > $userinfo['mysqls']) && $userinfo['mysqls'] != '-1') - || ((($userinfo['emails_used'] + $emails - $result['emails']) > $userinfo['emails']) && $userinfo['emails'] != '-1') - || ((($userinfo['email_accounts_used'] + $email_accounts - $result['email_accounts']) > $userinfo['email_accounts']) && $userinfo['email_accounts'] != '-1') - || ((($userinfo['email_forwarders_used'] + $email_forwarders - $result['email_forwarders']) > $userinfo['email_forwarders']) && $userinfo['email_forwarders'] != '-1') - || ((($userinfo['email_quota_used'] + $email_quota - $result['email_quota']) > $userinfo['email_quota']) && $userinfo['email_quota'] != '-1' && Settings::Get('system.mail_quota_enabled') == '1') - || ((($userinfo['ftps_used'] + $ftps - $result['ftps']) > $userinfo['ftps']) && $userinfo['ftps'] != '-1') - || ((($userinfo['tickets_used'] + $tickets - $result['tickets']) > $userinfo['tickets']) && $userinfo['tickets'] != '-1') - || ((($userinfo['subdomains_used'] + $subdomains - $result['subdomains']) > $userinfo['subdomains']) && $userinfo['subdomains'] != '-1') - || (($diskspace / 1024) == '-1' && ($userinfo['diskspace'] / 1024) != '-1') - || ($mysqls == '-1' && $userinfo['mysqls'] != '-1') - || ($emails == '-1' && $userinfo['emails'] != '-1') - || ($email_accounts == '-1' && $userinfo['email_accounts'] != '-1') - || ($email_forwarders == '-1' && $userinfo['email_forwarders'] != '-1') - || ($email_quota == '-1' && $userinfo['email_quota'] != '-1' && Settings::Get('system.mail_quota_enabled') == '1') - || ($ftps == '-1' && $userinfo['ftps'] != '-1') - || ($tickets == '-1' && $userinfo['tickets'] != '-1') - || ($subdomains == '-1' && $userinfo['subdomains'] != '-1') - ) { - standard_error('youcantallocatemorethanyouhave'); - } - - // Either $name and $firstname or the $company must be inserted - if ($name == '' && $company == '') { - standard_error(array('stringisempty', 'myname')); - - } elseif($firstname == '' && $company == '') { - standard_error(array('stringisempty', 'myfirstname')); - - } elseif($email == '') { - standard_error(array('stringisempty', 'emailadd')); - - } elseif(!validateEmail($email)) { - standard_error('emailiswrong', $email); - - } else { - - if ($password != '') { - $password = validatePassword($password); - $password = makeCryptPassword($password); - } else { - $password = $result['password']; - } - - if ($createstdsubdomain != '1') { - $createstdsubdomain = '0'; - } - - if ($createstdsubdomain == '1' - && $result['standardsubdomain'] == '0' - ) { - - if (Settings::Get('system.stdsubdomain') !== null - && Settings::Get('system.stdsubdomain') != '' - ) { - $_stdsubdomain = $result['loginname'] . '.' . Settings::Get('system.stdsubdomain'); - } else { - $_stdsubdomain = $result['loginname'] . '.' . Settings::Get('system.hostname'); - } - - $ins_data = array( - 'domain' => $_stdsubdomain, - 'customerid' => $result['customerid'], - 'adminid' => $userinfo['adminid'], - 'docroot' => $result['documentroot'], - 'adddate' => time() - ); - $ins_stmt = Database::prepare(" - INSERT INTO `" . TABLE_PANEL_DOMAINS . "` SET - `domain` = :domain, - `customerid` = :customerid, - `adminid` = :adminid, - `parentdomainid` = '0', - `documentroot` = :docroot, - `zonefile` = '', - `isemaildomain` = '0', - `caneditdomain` = '0', - `openbasedir` = '1', - `speciallogfile` = '0', - `specialsettings` = '', - `add_date` = :adddate" - ); - Database::pexecute($ins_stmt, $ins_data); - $domainid = Database::lastInsertId(); - - // set ip <-> domain connection - $defaultips = explode(',', Settings::Get('system.defaultip')); - $ins_stmt = Database::prepare(" - INSERT INTO `" . TABLE_DOMAINTOIP . "` SET `id_domain` = :domainid, `id_ipandports` = :ipid" - ); - foreach ($defaultips as $defaultip) { - Database::pexecute($ins_stmt, array('domainid' => $domainid, 'ipid' => $defaultip)); - } - - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `standardsubdomain` = :domainid WHERE `customerid` = :customerid" - ); - Database::pexecute($upd_stmt, array('domainid' => $domainid, 'customerid' => $result['customerid'])); - $log->logAction(ADM_ACTION, LOG_NOTICE, "automatically added standardsubdomain for user '" . $result['loginname'] . "'"); - inserttask('1'); - } - - if ($createstdsubdomain == '0' - && $result['standardsubdomain'] != '0' - ) { - - $del_stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `id` = :stdsub"); - Database::pexecute($del_stmt, array('stdsub' => $result['standardsubdomain'])); - $del_stmt = Database::prepare("DELETE FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :stdsub"); - Database::pexecute($del_stmt, array('stdsub' => $result['standardsubdomain'])); - $del_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `standardsubdomain`= '0' WHERE `customerid` = :customerid"); - Database::pexecute($del_stmt, array('customerid' => $result['customerid'])); - $log->logAction(ADM_ACTION, LOG_NOTICE, "automatically deleted standardsubdomain for user '" . $result['loginname'] . "'"); - inserttask('1'); - } - - if ($deactivated != '1') { - $deactivated = '0'; - } - - if ($phpenabled != '0') { - $phpenabled = '1'; - } - - if ($perlenabled != '0') { - $perlenabled = '1'; - } - - if ($dnsenabled != '0') { - $dnsenabled = '1'; - } - - if ($phpenabled != $result['phpenabled'] - || $perlenabled != $result['perlenabled'] - ) { - inserttask('1'); - } - - // activate/deactivate customer services - if ($deactivated != $result['deactivated']) { - - $yesno = (($deactivated) ? 'N' : 'Y'); - $pop3 = (($deactivated) ? '0' : (int)$result['pop3']); - $imap = (($deactivated) ? '0' : (int)$result['imap']); - - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_MAIL_USERS . "` SET `postfix`= :yesno, `pop3` = :pop3, `imap` = :imap WHERE `customerid` = :customerid" - ); - Database::pexecute($upd_stmt, array('yesno' => $yesno, 'pop3' => $pop3, 'imap' => $imap, 'customerid' => $id)); - - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_FTP_USERS . "` SET `login_enabled` = :yesno WHERE `customerid` = :customerid" - ); - Database::pexecute($upd_stmt, array('yesno' => $yesno, 'customerid' => $id)); - - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `deactivated`= :deactivated WHERE `customerid` = :customerid" - ); - Database::pexecute($upd_stmt, array('deactivated' => $deactivated, 'customerid' => $id)); - - // Retrieve customer's databases - $databases_stmt = Database::prepare("SELECT * FROM " . TABLE_PANEL_DATABASES . " WHERE customerid = :customerid ORDER BY `dbserver`"); - Database::pexecute($databases_stmt, array('customerid' => $id)); - - Database::needRoot(true); - $last_dbserver = 0; - - $dbm = new DbManager($log); - - // For each of them - while ($row_database = $databases_stmt->fetch(PDO::FETCH_ASSOC)) { - - if ($last_dbserver != $row_database['dbserver']) { - $dbm->getManager()->flushPrivileges(); - Database::needRoot(true, $row_database['dbserver']); - $last_dbserver = $row_database['dbserver']; - } - - foreach (array_unique(explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) { - $mysql_access_host = trim($mysql_access_host); - - // Prevent access, if deactivated - if ($deactivated) { - // failsafe if user has been deleted manually (requires MySQL 4.1.2+) - $dbm->getManager()->disableUser($row_database['databasename'], $mysql_access_host); - } else { - // Otherwise grant access - $dbm->getManager()->enableUser($row_database['databasename'], $mysql_access_host); - } - } - } - - // At last flush the new privileges - $dbm->getManager()->flushPrivileges(); - Database::needRoot(false); - - $log->logAction(ADM_ACTION, LOG_INFO, "deactivated user '" . $result['loginname'] . "'"); - inserttask('1'); - } - - // Disable or enable POP3 Login for customers Mail Accounts - if ($email_pop3 != $result['pop3']) { - $upd_stmt = Database::prepare("UPDATE `" . TABLE_MAIL_USERS . "` SET `pop3` = :pop3 WHERE `customerid` = :customerid"); - Database::pexecute($upd_stmt, array('pop3' => $email_pop3, 'customerid' => $id)); - } - - // Disable or enable IMAP Login for customers Mail Accounts - if ($email_imap != $result['imap']) { - $upd_stmt = Database::prepare("UPDATE `" . TABLE_MAIL_USERS . "` SET `imap` = :imap WHERE `customerid` = :customerid"); - Database::pexecute($upd_stmt, array('imap' => $email_imap, 'customerid' => $id)); - } - - $upd_data = array( - 'customerid' => $id, - 'passwd' => $password, - 'name' => $name, - 'firstname' => $firstname, - 'gender' => $gender, - 'company' => $company, - 'street' => $street, - 'zipcode' => $zipcode, - 'city' => $city, - 'phone' => $phone, - 'fax' => $fax, - 'email' => $email, - 'customerno' => $customernumber, - 'lang' => $def_language, - 'diskspace' => $diskspace, - 'traffic' => $traffic, - 'subdomains' => $subdomains, - 'emails' => $emails, - 'email_accounts' => $email_accounts, - 'email_forwarders' => $email_forwarders, - 'email_quota' => $email_quota, - 'ftps' => $ftps, - 'tickets' => $tickets, - 'mysqls' => $mysqls, - 'deactivated' => $deactivated, - 'phpenabled' => $phpenabled, - 'allowed_phpconfigs' => empty($allowed_phpconfigs) ? "" : json_encode($allowed_phpconfigs), - 'imap' => $email_imap, - 'pop3' => $email_pop3, - 'perlenabled' => $perlenabled, - 'dnsenabled' => $dnsenabled, - 'custom_notes' => $custom_notes, - 'custom_notes_show' => $custom_notes_show - ); - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET - `name` = :name, - `firstname` = :firstname, - `gender` = :gender, - `company` = :company, - `street` = :street, - `zipcode` = :zipcode, - `city` = :city, - `phone` = :phone, - `fax` = :fax, - `email` = :email, - `customernumber` = :customerno, - `def_language` = :lang, - `password` = :passwd, - `diskspace` = :diskspace, - `traffic` = :traffic, - `subdomains` = :subdomains, - `emails` = :emails, - `email_accounts` = :email_accounts, - `email_forwarders` = :email_forwarders, - `ftps` = :ftps, - `tickets` = :tickets, - `mysqls` = :mysqls, - `deactivated` = :deactivated, - `phpenabled` = :phpenabled, - `allowed_phpconfigs` = :allowed_phpconfigs, - `email_quota` = :email_quota, - `imap` = :imap, - `pop3` = :pop3, - `perlenabled` = :perlenabled, - `dnsenabled` = :dnsenabled, - `custom_notes` = :custom_notes, - `custom_notes_show` = :custom_notes_show - WHERE `customerid` = :customerid" - ); - Database::pexecute($upd_stmt, $upd_data); - - // Using filesystem - quota, insert a task which cleans the filesystem - quota - inserttask('10'); - - $admin_update_query = "UPDATE `" . TABLE_PANEL_ADMINS . "` SET `customers_used` = `customers_used` "; - - if ($mysqls != '-1' || $result['mysqls'] != '-1') { - $admin_update_query.= ", `mysqls_used` = `mysqls_used` "; - - if ($mysqls != '-1') { - $admin_update_query.= " + 0" . (int)$mysqls . " "; - } - if ($result['mysqls'] != '-1') { - $admin_update_query.= " - 0" . (int)$result['mysqls'] . " "; - } - } - - if($emails != '-1' || $result['emails'] != '-1') { - $admin_update_query.= ", `emails_used` = `emails_used` "; - - if ($emails != '-1') { - $admin_update_query.= " + 0" . (int)$emails . " "; - } - if ($result['emails'] != '-1') { - $admin_update_query.= " - 0" . (int)$result['emails'] . " "; - } - } - - if ($email_accounts != '-1' || $result['email_accounts'] != '-1') { - $admin_update_query.= ", `email_accounts_used` = `email_accounts_used` "; - - if ($email_accounts != '-1') { - $admin_update_query.= " + 0" . (int)$email_accounts . " "; - } - if ($result['email_accounts'] != '-1') { - $admin_update_query.= " - 0" . (int)$result['email_accounts'] . " "; - } - } - - if ($email_forwarders != '-1' || $result['email_forwarders'] != '-1') { - $admin_update_query.= ", `email_forwarders_used` = `email_forwarders_used` "; - - if ($email_forwarders != '-1') { - $admin_update_query.= " + 0" . (int)$email_forwarders . " "; - } - if ($result['email_forwarders'] != '-1') { - $admin_update_query.= " - 0" . (int)$result['email_forwarders'] . " "; - } - } - - if ($email_quota != '-1' || $result['email_quota'] != '-1') { - $admin_update_query.= ", `email_quota_used` = `email_quota_used` "; - - if ($email_quota != '-1') { - $admin_update_query.= " + 0" . (int)$email_quota . " "; - } - if ($result['email_quota'] != '-1') { - $admin_update_query.= " - 0" . (int)$result['email_quota'] . " "; - } - } - - if ($subdomains != '-1' || $result['subdomains'] != '-1') { - $admin_update_query.= ", `subdomains_used` = `subdomains_used` "; - - if ($subdomains != '-1') { - $admin_update_query.= " + 0" . (int)$subdomains . " "; - } - if ($result['subdomains'] != '-1') { - $admin_update_query.= " - 0" . (int)$result['subdomains'] . " "; - } - } - - if ($ftps != '-1' || $result['ftps'] != '-1') { - $admin_update_query.= ", `ftps_used` = `ftps_used` "; - - if ($ftps != '-1') { - $admin_update_query.= " + 0" . (int)$ftps . " "; - } - if ($result['ftps'] != '-1') { - $admin_update_query.= " - 0" . (int)$result['ftps'] . " "; - } - } - - if ($tickets != '-1' || $result['tickets'] != '-1') { - $admin_update_query.= ", `tickets_used` = `tickets_used` "; - - if ($tickets != '-1') { - $admin_update_query.= " + 0" . (int)$tickets . " "; - } - if ($result['tickets'] != '-1') { - $admin_update_query.= " - 0" . (int)$result['tickets'] . " "; - } - } - - if (($diskspace / 1024) != '-1' || ($result['diskspace'] / 1024) != '-1') { - $admin_update_query.= ", `diskspace_used` = `diskspace_used` "; - - if (($diskspace / 1024) != '-1') { - $admin_update_query.= " + 0" . (int)$diskspace . " "; - } - if (($result['diskspace'] / 1024) != '-1') { - $admin_update_query.= " - 0" . (int)$result['diskspace'] . " "; - } - } - - $admin_update_query.= " WHERE `adminid` = '" . (int)$result['adminid'] . "'"; - Database::query($admin_update_query); - $log->logAction(ADM_ACTION, LOG_INFO, "edited user '" . $result['loginname'] . "'"); - - /* - * move customer to another admin/reseller; #1166 - */ - if ($move_to_admin > 0 && $move_to_admin != $result['adminid']) { - $move_result = moveCustomerToAdmin($id, $move_to_admin); - if ($move_result != true) { - standard_error('moveofcustomerfailed', $move_result); - } - } - - $redirect_props = Array( - 'page' => $page, - 's' => $s - ); - - redirectTo($filename, $redirect_props); - } - + redirectTo($filename, array( + 'page' => $page, + 's' => $s + )); } else { $language_options = ''; diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index d45808be..5a749e63 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -184,6 +184,16 @@ abstract class ApiCommand return $this->cmd_params[$param]; } + protected function getUlParam($param = null, $ul_field = null, $optional = false, $default = 0) + { + $param_value = intval_ressource($this->getParam($param, $optional, $default)); + $ul_field_value = $this->getParam($ul_field, true, 0); + if ($ul_field_value != 0) { + $param_value = - 1; + } + return $param_value; + } + /** * returns "module::function()" for better error-messages (missing parameter etc.) * makes debugging a whole lot more comfortable diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index 820703eb..9f7ed7c4 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -1,5 +1,19 @@ (2010-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package Panel + * + */ class Customers extends ApiCommand implements ResourceEntity { @@ -40,8 +54,9 @@ class Customers extends ApiCommand implements ResourceEntity /** * return a customer entry by id * - * @param int $id customer-id - * + * @param int $id + * customer-id + * * @throws Exception * @return array */ @@ -75,91 +90,72 @@ class Customers extends ApiCommand implements ResourceEntity if ($this->isAdmin()) { if ($this->getUserDetail('customers_used') < $this->getUserDetail('customers') || $this->getUserDetail('customers') == '-1') { - $idna_convert = new idna_convert_wrapper(); - $name = validate($this->getParam('name', true, ''), 'name', '', '', array(), true); - $firstname = validate($this->getParam('firstname', true, ''), 'first name', '', '', array(), true); + // required parameters + $email = $this->getParam('email'); + + // parameters + $name = $this->getParam('name', true, ''); + $firstname = $this->getParam('firstname', true, ''); $company_required = (empty($name) && empty($first)); - $company = validate($this->getParam('company', $company_required, ''), 'company', '', '', array(), true); - $street = validate($this->getParam('street', true, ''), 'street', '', '', array(), true); - $zipcode = validate($this->getParam('zipcode', true, ''), 'zipcode', '/^[0-9 \-A-Z]*$/', '', array(), true); - $city = validate($this->getParam('city', true, ''), 'city', '', '', array(), true); - $phone = validate($this->getParam('phone', true, ''), 'phone', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); - $fax = validate($this->getParam('fax', true, ''), 'fax', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); - $email = $idna_convert->encode(validate($this->getParam('email'), 'email', '', '', array(), true)); - $customernumber = validate($this->getParam('customernumber', true, ''), 'customer number', '/^[A-Za-z0-9 \-]*$/Di', '', array(), true); - $def_language = validate($this->getParam('def_language', true, ''), 'default language', '', '', array(), true); + $company = $this->getParam('company', $company_required, ''); + $street = $this->getParam('street', true, ''); + $zipcode = $this->getParam('zipcode', true, ''); + $city = $this->getParam('city', true, ''); + $phone = $this->getParam('phone', true, ''); + $fax = $this->getParam('fax', true, ''); + $customernumber = $this->getParam('customernumber', true, ''); + $def_language = $this->getParam('def_language', true, ''); $gender = intval_ressource($this->getParam('gender', true, 0)); - - $custom_notes = validate(str_replace("\r\n", "\n", $this->getParam('custom_notes', true, '')), 'custom_notes', '/^[^\0]*$/', '', array(), true); + $custom_notes = $this->getParam('custom_notes', true, ''); $custom_notes_show = $this->getParam('custom_notes_show', true, 0); - $diskspace = intval_ressource($this->getParam('diskspace', true, 0)); - if ($this->getParam('diskspace_ul', true, 0) == -1) { - $diskspace = - 1; - } + $diskspace = $this->getUlParam('diskspace', 'diskspace_ul', true, 0); + $traffic = $this->getUlParam('traffic', 'traffic_ul', true, 0); + $subdomains = $this->getUlParam('subdomains', 'subdomains_ul', true, 0); + $emails = $this->getUlParam('emails', 'emails_ul', true, 0); + $email_accounts = $this->getUlParam('email_accounts', 'email_accounts_ul', true, 0); + $email_forwarders = $this->getUlParam('email_forwarders', 'email_forwarders_ul', true, 0); + $email_quota = $this->getUlParam('email_quota', 'email_quota_ul', true, 0); + $email_imap = $this->getParam('email_imap', true, 0); + $email_pop3 = $this->getParam('email_pop3', true, 0); + $ftps = $this->getUlParam('ftps', 'ftps_ul', true, 0); + $tickets = $this->getUlParam('tickets', 'tickets_ul', true, 0); + $mysqls = $this->getUlParam('mysqls', 'mysqls_ul', true, 0); + $createstdsubdomain = $this->getParam('createstdsubdomain', true, 0); + $password = $this->getParam('new_customer_password', true, ''); + $sendpassword = $this->getParam('sendpassword', true, 0); + $phpenabled = $this->getParam('phpenabled', true, 0); + $p_allowed_phpconfigs = $this->getParam('allowed_phpconfigs', true, array()); + $perlenabled = $this->getParam('perlenabled', true, 0); + $dnsenabled = $this->getParam('dnsenabled', true, 0); + $store_defaultindex = $this->getParam('store_defaultindex', true, 0); + $loginname = $this->getParam('new_loginname', true, ''); - $traffic = doubleval_ressource($this->getParam('traffic', true, 0)); - if ($this->getParam('traffic_ul', true, 0) == -1) { - $traffic = - 1; - } + // validation + $idna_convert = new idna_convert_wrapper(); + $name = validate($name, 'name', '', '', array(), true); + $firstname = validate($firstname, 'first name', '', '', array(), true); + $company = validate($company, 'company', '', '', array(), true); + $street = validate($street, 'street', '', '', array(), true); + $zipcode = validate($zipcode, 'zipcode', '/^[0-9 \-A-Z]*$/', '', array(), true); + $city = validate($city, 'city', '', '', array(), true); + $phone = validate($phone, 'phone', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); + $fax = validate(fax, 'fax', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); + $idna_convert = new idna_convert_wrapper(); + $email = $idna_convert->encode(validate($email, 'email', '', '', array(), true)); + $customernumber = validate($customernumber, 'customer number', '/^[A-Za-z0-9 \-]*$/Di', '', array(), true); + $def_language = validate($def_language, 'default language', '', '', array(), true); + $custom_notes = validate(str_replace("\r\n", "\n", $custom_notes), 'custom_notes', '/^[^\0]*$/', '', array(), true); - $subdomains = intval_ressource($this->getParam('subdomains', true, 0)); - if ($this->getParam('subdomains_ul', true, 0) == -1) { - $subdomains = - 1; - } - - $emails = intval_ressource($this->getParam('emails', true, 0)); - if ($this->getParam('emails_ul', true, 0) == -1) { - $emails = - 1; - } - - $email_accounts = intval_ressource($this->getParam('email_accounts', true, 0)); - if ($this->getParam('email_accounts_ul', true, 0) == -1) { - $email_accounts = - 1; - } - - $email_forwarders = intval_ressource($this->getParam('email_forwarders', true, 0)); - if ($this->getParam('email_forwarders_ul', true, 0) == -1) { - $email_forwarders = - 1; - } - - if (Settings::Get('system.mail_quota_enabled') == '1') { - $email_quota = validate($this->getParam('email_quota', true, 0), 'email_quota', '/^\d+$/', 'vmailquotawrong', array( - '0', - '' - ), true); - if ($this->getParam('email_quota_ul', true, 0) == -1) { - $email_quota = - 1; - } - } else { + if (Settings::Get('system.mail_quota_enabled') != '1') { $email_quota = - 1; } - $email_imap = $this->getParam('email_imap', true, 0); - $email_pop3 = $this->getParam('email_pop3', true, 0); - - $ftps = intval_ressource($this->getParam('ftps', true, 0)); - if ($this->getParam('ftps_ul', true, 0) == -1) { - $ftps = - 1; - } - - if (Settings::Get('ticket.enabled') == '1') { - $tickets = intval_ressource($this->getParam('tickets', true, 0)); - if ($this->getParam('tickets_ul', true, 0) == -1) { - $tickets = - 1; - } - } else { + if (Settings::Get('ticket.enabled') != '1') { $tickets = - 1; } - $mysqls = intval_ressource($this->getParam('mysqls', true, 0)); - if ($this->getParam('mysqls_ul', true, 0) == -1) { - $mysqls = - 1; - } - - $createstdsubdomain = $this->getParam('createstdsubdomain', true, 0); - - $password = validate($this->getParam('new_customer_password', true, ''), 'password', '', '', array(), true); + $password = validate($password, 'password', '', '', array(), true); // only check if not empty, // cause empty == generate password automatically if ($password != '') { @@ -171,21 +167,14 @@ class Customers extends ApiCommand implements ResourceEntity $gender = 0; } - $sendpassword = $this->getParam('sendpassword', true, 0); - $phpenabled = $this->getParam('phpenabled', true, 0); - $allowed_phpconfigs = array(); - if (! empty($this->getParam('allowed_phpconfigs', true, array())) && is_array($this->getParam('allowed_phpconfigs', true, array()))) { - foreach ($this->getParam('allowed_phpconfigs', true, array()) as $allowed_phpconfig) { + if (! empty($p_allowed_phpconfigs) && is_array($p_allowed_phpconfigs)) { + foreach ($p_allowed_phpconfigs as $allowed_phpconfig) { $allowed_phpconfig = intval($allowed_phpconfig); $allowed_phpconfigs[] = $allowed_phpconfig; } } - $perlenabled = $this->getParam('perlenabled', true, 0); - $dnsenabled = $this->getParam('dnsenabled', true, 0); - $store_defaultindex = $this->getParam('store_defaultindex', true, 0); - $diskspace = $diskspace * 1024; $traffic = $traffic * 1024 * 1024; @@ -213,9 +202,9 @@ class Customers extends ApiCommand implements ResourceEntity standard_error('emailiswrong', $email, true); } else { - if ($this->getParam('new_loginname', true, '') != '') { + if ($loginname != '') { $accountnumber = intval(Settings::Get('system.lastaccountnumber')); - $loginname = validate($this->getParam('new_loginname'), 'loginname', '/^[a-z][a-z0-9\-_]+$/i', '', array(), true); + $loginname = validate($loginname, 'loginname', '/^[a-z][a-z0-9\-_]+$/i', '', array(), true); // Accounts which match systemaccounts are not allowed, filtering them if (preg_match('/^' . preg_quote(Settings::Get('customer.accountprefix'), '/') . '([0-9]+)/', $loginname)) { @@ -567,7 +556,7 @@ class Customers extends ApiCommand implements ResourceEntity $srv_hostname = Settings::Get('system.hostname'); if (Settings::Get('system.froxlordirectlyviahostname') == '0') { - $srv_hostname .= '/'.basename(FROXLOR_INSTALL_DIR); + $srv_hostname .= '/' . basename(FROXLOR_INSTALL_DIR); } $srv_ip_stmt = Database::prepare(" @@ -663,8 +652,466 @@ class Customers extends ApiCommand implements ResourceEntity ))->get(); $result = json_decode($json_result, true)['data']; - $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] changed customer '" . $result['loginname'] . "'"); - return $this->response(200, "successfull", $upd_data); + // parameters + $move_to_admin = intval_ressource($this->getParam('move_to_admin', true, 0)); + + $idna_convert = new idna_convert_wrapper(); + $email = $this->getParam('email', true, $idna_convert->decode($result['email'])); + $name = $this->getParam('name', true, $result['name']); + $firstname = $this->getParam('firstname', true, $result['firstname']); + $company_required = (empty($name) && empty($first)); + $company = $this->getParam('company', $company_required, $result['company']); + $street = $this->getParam('street', true, $result['street']); + $zipcode = $this->getParam('zipcode', true, $result['zipcode']); + $city = $this->getParam('city', true, $result['city']); + $phone = $this->getParam('phone', true, $result['phone']); + $fax = $this->getParam('fax', true, $result['fax']); + $customernumber = $this->getParam('customernumber', true, $result['customernumber']); + $def_language = $this->getParam('def_language', true, $result['def_language']); + $gender = intval_ressource($this->getParam('gender', true, $result['gender'])); + $custom_notes = $this->getParam('custom_notes', true, $result['custom_notes']); + $custom_notes_show = $this->getParam('custom_notes_show', true, $result['custom_notes_show']); + + $dec_places = Settings::Get('panel.decimal_places'); + $diskspace = $this->getUlParam('diskspace', 'diskspace_ul', true, round($result['diskspace'] / 1024, $dec_places)); + $traffic = $this->getUlParam('traffic', 'traffic_ul', true, round($result['traffic'] / (1024 * 1024), $dec_places)); + $subdomains = $this->getUlParam('subdomains', 'subdomains_ul', true, $result['subdomains']); + $emails = $this->getUlParam('emails', 'emails_ul', true, $result['emails']); + $email_accounts = $this->getUlParam('email_accounts', 'email_accounts_ul', true, $result['email_accounts']); + $email_forwarders = $this->getUlParam('email_forwarders', 'email_forwarders_ul', true, $result['email_forwarders']); + $email_quota = $this->getUlParam('email_quota', 'email_quota_ul', true, $result['email_quota']); + $email_imap = $this->getParam('email_imap', true, $result['imap']); + $email_pop3 = $this->getParam('email_pop3', true, $result['pop3']); + $ftps = $this->getUlParam('ftps', 'ftps_ul', true, $result['ftps']); + $tickets = $this->getUlParam('tickets', 'tickets_ul', true, $result['tickets']); + $mysqls = $this->getUlParam('mysqls', 'mysqls_ul', true, $result['mysqls']); + $createstdsubdomain = $this->getParam('createstdsubdomain', true, 0); + $password = $this->getParam('new_customer_password', true, ''); + $sendpassword = $this->getParam('sendpassword', true, 0); + $phpenabled = $this->getParam('phpenabled', true, $result['phpenabled']); + $allowed_phpconfigs = $this->getParam('allowed_phpconfigs', true, json_decode($result['allowed_phpconfigs'], true)); + $perlenabled = $this->getParam('perlenabled', true, $result['perlenabled']); + $dnsenabled = $this->getParam('dnsenabled', true, $result['dnsenabled']); + $deactivated = $this->getParam('deactivated', true, $result['deactivated']); + + // validation + $idna_convert = new idna_convert_wrapper(); + $name = validate($name, 'name', '', '', array(), true); + $firstname = validate($firstname, 'first name', '', '', array(), true); + $company = validate($company, 'company', '', '', array(), true); + $street = validate($street, 'street', '', '', array(), true); + $zipcode = validate($zipcode, 'zipcode', '/^[0-9 \-A-Z]*$/', '', array(), true); + $city = validate($city, 'city', '', '', array(), true); + $phone = validate($phone, 'phone', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); + $fax = validate(fax, 'fax', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); + $email = $idna_convert->encode(validate($email, 'email', '', '', array(), true)); + $customernumber = validate($customernumber, 'customer number', '/^[A-Za-z0-9 \-]*$/Di', '', array(), true); + $def_language = validate($def_language, 'default language', '', '', array(), true); + $custom_notes = validate(str_replace("\r\n", "\n", $custom_notes), 'custom_notes', '/^[^\0]*$/', '', array(), true); + + if (Settings::Get('system.mail_quota_enabled') != '1') { + $email_quota = - 1; + } + + if (Settings::Get('ticket.enabled') != '1') { + $tickets = - 1; + } + + // Either $name and $firstname or the $company must be inserted + if ($name == '' && $company == '') { + standard_error(array( + 'stringisempty', + 'myname' + )); + } elseif ($firstname == '' && $company == '') { + standard_error(array( + 'stringisempty', + 'myfirstname' + )); + } elseif ($email == '') { + standard_error(array( + 'stringisempty', + 'emailadd' + )); + } elseif (! validateEmail($email)) { + standard_error('emailiswrong', $email); + } else { + + if ($password != '') { + $password = validatePassword($password); + $password = makeCryptPassword($password); + } else { + $password = $result['password']; + } + + if ($createstdsubdomain != '1') { + $createstdsubdomain = '0'; + } + + if ($createstdsubdomain == '1' && $result['standardsubdomain'] == '0') { + + if (Settings::Get('system.stdsubdomain') !== null && Settings::Get('system.stdsubdomain') != '') { + $_stdsubdomain = $result['loginname'] . '.' . Settings::Get('system.stdsubdomain'); + } else { + $_stdsubdomain = $result['loginname'] . '.' . Settings::Get('system.hostname'); + } + + $ins_data = array( + 'domain' => $_stdsubdomain, + 'customerid' => $result['customerid'], + 'adminid' => $this->getUserDetail('adminid'), + 'parentdomainid' => '0', + 'docroot' => $result['documentroot'], + 'adddate' => time(), + 'phpenabled' => $phpenabled, + 'zonefile' => '', + 'isemaildomain' => '0', + 'caneditdomain' => '0', + 'openbasedir' => '1', + 'speciallogfile' => '0', + 'dkim_id' => '0', + 'dkim_privkey' => '', + 'dkim_pubkey' => '', + 'ipandport' => explode(',', Settings::Get('system.defaultip')) + ); + $domainid = - 1; + try { + $std_domain = Domains::getLocal($this->getUserData(), $ins_data)->add(); + $domainid = json_decode($std_domain, true)['data']['id']; + } catch (Exception $e) { + $this->logger()->logAction(ADM_ACTION, LOG_ERR, "[API] Unable to add standard-subdomain: " . $e->getMessage()); + } + + if ($domainid > 0) { + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `standardsubdomain` = :domainid WHERE `customerid` = :customerid + "); + Database::pexecute($upd_stmt, array( + 'domainid' => $domainid, + 'customerid' => $result['customerid'] + ), true, true); + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] automatically added standardsubdomain for user '" . $result['loginname'] . "'"); + inserttask('1'); + } + } + + if ($createstdsubdomain == '0' && $result['standardsubdomain'] != '0') { + + try { + $std_domain = Domains::getLocal($this->getUserData(), array( + 'id' => $result['standardsubdomain'], + 'is_stdsubdomain' => 1 + ))->delete(); + } catch (Exception $e) { + $this->logger()->logAction(ADM_ACTION, LOG_ERR, "[API] Unable to delete standard-subdomain: " . $e->getMessage()); + } + $log->logAction(ADM_ACTION, LOG_NOTICE, "automatically deleted standardsubdomain for user '" . $result['loginname'] . "'"); + inserttask('1'); + } + + if ($deactivated != '1') { + $deactivated = '0'; + } + + if ($phpenabled != '0') { + $phpenabled = '1'; + } + + if ($perlenabled != '0') { + $perlenabled = '1'; + } + + if ($dnsenabled != '0') { + $dnsenabled = '1'; + } + + if ($phpenabled != $result['phpenabled'] || $perlenabled != $result['perlenabled']) { + inserttask('1'); + } + + // activate/deactivate customer services + if ($deactivated != $result['deactivated']) { + + $yesno = (($deactivated) ? 'N' : 'Y'); + $pop3 = (($deactivated) ? '0' : (int) $result['pop3']); + $imap = (($deactivated) ? '0' : (int) $result['imap']); + + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_MAIL_USERS . "` SET `postfix`= :yesno, `pop3` = :pop3, `imap` = :imap WHERE `customerid` = :customerid"); + Database::pexecute($upd_stmt, array( + 'yesno' => $yesno, + 'pop3' => $pop3, + 'imap' => $imap, + 'customerid' => $id + )); + + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_FTP_USERS . "` SET `login_enabled` = :yesno WHERE `customerid` = :customerid"); + Database::pexecute($upd_stmt, array( + 'yesno' => $yesno, + 'customerid' => $id + )); + + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `deactivated`= :deactivated WHERE `customerid` = :customerid"); + Database::pexecute($upd_stmt, array( + 'deactivated' => $deactivated, + 'customerid' => $id + )); + + // Retrieve customer's databases + $databases_stmt = Database::prepare("SELECT * FROM " . TABLE_PANEL_DATABASES . " WHERE customerid = :customerid ORDER BY `dbserver`"); + Database::pexecute($databases_stmt, array( + 'customerid' => $id + )); + + Database::needRoot(true); + $last_dbserver = 0; + + $dbm = new DbManager($log); + + // For each of them + while ($row_database = $databases_stmt->fetch(PDO::FETCH_ASSOC)) { + + if ($last_dbserver != $row_database['dbserver']) { + $dbm->getManager()->flushPrivileges(); + Database::needRoot(true, $row_database['dbserver']); + $last_dbserver = $row_database['dbserver']; + } + + foreach (array_unique(explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) { + $mysql_access_host = trim($mysql_access_host); + + // Prevent access, if deactivated + if ($deactivated) { + // failsafe if user has been deleted manually (requires MySQL 4.1.2+) + $dbm->getManager()->disableUser($row_database['databasename'], $mysql_access_host); + } else { + // Otherwise grant access + $dbm->getManager()->enableUser($row_database['databasename'], $mysql_access_host); + } + } + } + + // At last flush the new privileges + $dbm->getManager()->flushPrivileges(); + Database::needRoot(false); + + $log->logAction(ADM_ACTION, LOG_INFO, "deactivated user '" . $result['loginname'] . "'"); + inserttask('1'); + } + + // Disable or enable POP3 Login for customers Mail Accounts + if ($email_pop3 != $result['pop3']) { + $upd_stmt = Database::prepare("UPDATE `" . TABLE_MAIL_USERS . "` SET `pop3` = :pop3 WHERE `customerid` = :customerid"); + Database::pexecute($upd_stmt, array( + 'pop3' => $email_pop3, + 'customerid' => $id + )); + } + + // Disable or enable IMAP Login for customers Mail Accounts + if ($email_imap != $result['imap']) { + $upd_stmt = Database::prepare("UPDATE `" . TABLE_MAIL_USERS . "` SET `imap` = :imap WHERE `customerid` = :customerid"); + Database::pexecute($upd_stmt, array( + 'imap' => $email_imap, + 'customerid' => $id + )); + } + + $upd_data = array( + 'customerid' => $id, + 'passwd' => $password, + 'name' => $name, + 'firstname' => $firstname, + 'gender' => $gender, + 'company' => $company, + 'street' => $street, + 'zipcode' => $zipcode, + 'city' => $city, + 'phone' => $phone, + 'fax' => $fax, + 'email' => $email, + 'customerno' => $customernumber, + 'lang' => $def_language, + 'diskspace' => $diskspace, + 'traffic' => $traffic, + 'subdomains' => $subdomains, + 'emails' => $emails, + 'email_accounts' => $email_accounts, + 'email_forwarders' => $email_forwarders, + 'email_quota' => $email_quota, + 'ftps' => $ftps, + 'tickets' => $tickets, + 'mysqls' => $mysqls, + 'deactivated' => $deactivated, + 'phpenabled' => $phpenabled, + 'allowed_phpconfigs' => empty($allowed_phpconfigs) ? "" : json_encode($allowed_phpconfigs), + 'imap' => $email_imap, + 'pop3' => $email_pop3, + 'perlenabled' => $perlenabled, + 'dnsenabled' => $dnsenabled, + 'custom_notes' => $custom_notes, + 'custom_notes_show' => $custom_notes_show + ); + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET + `name` = :name, + `firstname` = :firstname, + `gender` = :gender, + `company` = :company, + `street` = :street, + `zipcode` = :zipcode, + `city` = :city, + `phone` = :phone, + `fax` = :fax, + `email` = :email, + `customernumber` = :customerno, + `def_language` = :lang, + `password` = :passwd, + `diskspace` = :diskspace, + `traffic` = :traffic, + `subdomains` = :subdomains, + `emails` = :emails, + `email_accounts` = :email_accounts, + `email_forwarders` = :email_forwarders, + `ftps` = :ftps, + `tickets` = :tickets, + `mysqls` = :mysqls, + `deactivated` = :deactivated, + `phpenabled` = :phpenabled, + `allowed_phpconfigs` = :allowed_phpconfigs, + `email_quota` = :email_quota, + `imap` = :imap, + `pop3` = :pop3, + `perlenabled` = :perlenabled, + `dnsenabled` = :dnsenabled, + `custom_notes` = :custom_notes, + `custom_notes_show` = :custom_notes_show + WHERE `customerid` = :customerid + "); + Database::pexecute($upd_stmt, $upd_data); + + // Using filesystem - quota, insert a task which cleans the filesystem - quota + inserttask('10'); + + $admin_update_query = "UPDATE `" . TABLE_PANEL_ADMINS . "` SET `customers_used` = `customers_used` "; + + if ($mysqls != '-1' || $result['mysqls'] != '-1') { + $admin_update_query .= ", `mysqls_used` = `mysqls_used` "; + + if ($mysqls != '-1') { + $admin_update_query .= " + 0" . (int) $mysqls . " "; + } + if ($result['mysqls'] != '-1') { + $admin_update_query .= " - 0" . (int) $result['mysqls'] . " "; + } + } + + if ($emails != '-1' || $result['emails'] != '-1') { + $admin_update_query .= ", `emails_used` = `emails_used` "; + + if ($emails != '-1') { + $admin_update_query .= " + 0" . (int) $emails . " "; + } + if ($result['emails'] != '-1') { + $admin_update_query .= " - 0" . (int) $result['emails'] . " "; + } + } + + if ($email_accounts != '-1' || $result['email_accounts'] != '-1') { + $admin_update_query .= ", `email_accounts_used` = `email_accounts_used` "; + + if ($email_accounts != '-1') { + $admin_update_query .= " + 0" . (int) $email_accounts . " "; + } + if ($result['email_accounts'] != '-1') { + $admin_update_query .= " - 0" . (int) $result['email_accounts'] . " "; + } + } + + if ($email_forwarders != '-1' || $result['email_forwarders'] != '-1') { + $admin_update_query .= ", `email_forwarders_used` = `email_forwarders_used` "; + + if ($email_forwarders != '-1') { + $admin_update_query .= " + 0" . (int) $email_forwarders . " "; + } + if ($result['email_forwarders'] != '-1') { + $admin_update_query .= " - 0" . (int) $result['email_forwarders'] . " "; + } + } + + if ($email_quota != '-1' || $result['email_quota'] != '-1') { + $admin_update_query .= ", `email_quota_used` = `email_quota_used` "; + + if ($email_quota != '-1') { + $admin_update_query .= " + 0" . (int) $email_quota . " "; + } + if ($result['email_quota'] != '-1') { + $admin_update_query .= " - 0" . (int) $result['email_quota'] . " "; + } + } + + if ($subdomains != '-1' || $result['subdomains'] != '-1') { + $admin_update_query .= ", `subdomains_used` = `subdomains_used` "; + + if ($subdomains != '-1') { + $admin_update_query .= " + 0" . (int) $subdomains . " "; + } + if ($result['subdomains'] != '-1') { + $admin_update_query .= " - 0" . (int) $result['subdomains'] . " "; + } + } + + if ($ftps != '-1' || $result['ftps'] != '-1') { + $admin_update_query .= ", `ftps_used` = `ftps_used` "; + + if ($ftps != '-1') { + $admin_update_query .= " + 0" . (int) $ftps . " "; + } + if ($result['ftps'] != '-1') { + $admin_update_query .= " - 0" . (int) $result['ftps'] . " "; + } + } + + if ($tickets != '-1' || $result['tickets'] != '-1') { + $admin_update_query .= ", `tickets_used` = `tickets_used` "; + + if ($tickets != '-1') { + $admin_update_query .= " + 0" . (int) $tickets . " "; + } + if ($result['tickets'] != '-1') { + $admin_update_query .= " - 0" . (int) $result['tickets'] . " "; + } + } + + if (($diskspace / 1024) != '-1' || ($result['diskspace'] / 1024) != '-1') { + $admin_update_query .= ", `diskspace_used` = `diskspace_used` "; + + if (($diskspace / 1024) != '-1') { + $admin_update_query .= " + 0" . (int) $diskspace . " "; + } + if (($result['diskspace'] / 1024) != '-1') { + $admin_update_query .= " - 0" . (int) $result['diskspace'] . " "; + } + } + + $admin_update_query .= " WHERE `adminid` = '" . (int) $result['adminid'] . "'"; + Database::query($admin_update_query); + $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] edited user '" . $result['loginname'] . "'"); + + /* + * move customer to another admin/reseller; #1166 + */ + if ($move_to_admin > 0 && $move_to_admin != $result['adminid']) { + $move_result = moveCustomerToAdmin($id, $move_to_admin); + if ($move_result != true) { + standard_error('moveofcustomerfailed', $move_result, true); + } + } + + return $this->response(200, "successfull", $upd_data); + } } throw new Exception("Not allowed to execute given command.", 403); } @@ -672,9 +1119,11 @@ class Customers extends ApiCommand implements ResourceEntity /** * delete a customer entry by id * - * @param int $id customer-id - * @param bool $delete_userfiles optional, default false - * + * @param int $id + * customer-id + * @param bool $delete_userfiles + * optional, default false + * * @throws Exception * @return array */ @@ -907,8 +1356,9 @@ class Customers extends ApiCommand implements ResourceEntity /** * unlock a locked customer by id * - * @param int $id customer-id - * + * @param int $id + * customer-id + * * @throws Exception * @return array */ diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index 481a8ba2..c25b4d64 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -1,5 +1,19 @@ (2010-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package Panel + * + */ class Domains extends ApiCommand implements ResourceEntity { @@ -1525,7 +1539,10 @@ class Domains extends ApiCommand implements ResourceEntity * * @param int $id * domain-id - * @param bool $delete_mainsubdomains optional, remove also domains that are subdomains of this domain but added as main domains; default false + * @param bool $delete_mainsubdomains + * optional, remove also domains that are subdomains of this domain but added as main domains; default false + * @param bool $is_stdsubdomain + * optional, default false, specify whether it's a std-subdomain you are deleting as it does not count as subdomain-resource * * @throws Exception * @return array @@ -1534,11 +1551,11 @@ class Domains extends ApiCommand implements ResourceEntity { if ($this->isAdmin()) { $id = $this->getParam('id'); + $is_stdsubdomain = $this->getParam('is_stdsubdomain', true, 0); $remove_subbutmain_domains = $this->getParam('delete_mainsubdomains', true, 0); - + $json_result = Domains::getLocal($this->getUserData(), array( - 'id' => $id, - 'no_std_subdomain' => true + 'id' => $id ))->get(); $result = json_decode($json_result, true)['data']; @@ -1547,7 +1564,7 @@ class Domains extends ApiCommand implements ResourceEntity if ($remove_subbutmain_domains) { $rsd_sql .= " OR `ismainbutsubto` = :id"; } - + $subresult_stmt = Database::prepare(" SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE (`id` = :id OR `parentdomainid` = :id " . $rsd_sql . ")"); @@ -1595,22 +1612,24 @@ class Domains extends ApiCommand implements ResourceEntity $deleted_domains = $del_stmt->rowCount(); - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET - `subdomains_used` = `subdomains_used` - :domaincount - WHERE `customerid` = :customerid"); - Database::pexecute($upd_stmt, array( - 'domaincount' => ($deleted_domains - 1), - 'customerid' => $result['customerid'] - ), true, true); - - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_ADMINS . "` SET - `domains_used` = `domains_used` - 1 - WHERE `adminid` = :adminid"); - Database::pexecute($upd_stmt, array( - 'adminid' => $this->getUserDetail('adminid') - ), true, true); + if ($is_stdsubdomain == 0) { + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET + `subdomains_used` = `subdomains_used` - :domaincount + WHERE `customerid` = :customerid"); + Database::pexecute($upd_stmt, array( + 'domaincount' => ($deleted_domains - 1), + 'customerid' => $result['customerid'] + ), true, true); + + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_ADMINS . "` SET + `domains_used` = `domains_used` - 1 + WHERE `adminid` = :adminid"); + Database::pexecute($upd_stmt, array( + 'adminid' => $this->getUserDetail('adminid') + ), true, true); + } $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET diff --git a/lib/classes/api/commands/class.Froxlor.php b/lib/classes/api/commands/class.Froxlor.php index 01c316e5..4d6d1ea3 100644 --- a/lib/classes/api/commands/class.Froxlor.php +++ b/lib/classes/api/commands/class.Froxlor.php @@ -1,19 +1,91 @@ (2010-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package Panel + * + */ class Froxlor extends ApiCommand { + public function checkUpdate() + { + global $version, $branding; + + if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { + // log our actions + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] checking for updates"); + + // check for new version + define('UPDATE_URI', "https://version.froxlor.org/Froxlor/api/" . $version); + $latestversion = HttpClient::urlGet(UPDATE_URI); + $latestversion = explode('|', $latestversion); + + if (is_array($latestversion) && count($latestversion) >= 1) { + $_version = $latestversion[0]; + $_message = isset($latestversion[1]) ? $latestversion[1] : ''; + $_link = isset($latestversion[2]) ? $latestversion[2] : ''; + + // add the branding so debian guys are not gettings confused + // about their version-number + $version_label = $_version . $branding; + $version_link = $_link; + $message_addinfo = $_message; + + // not numeric -> error-message + if (! preg_match('/^((\d+\\.)(\d+\\.)(\d+\\.)?(\d+)?(\-(svn|dev|rc)(\d+))?)$/', $_version)) { + // check for customized version to not output + // "There is a newer version of froxlor" besides the error-message + $isnewerversion = - 1; + } elseif (version_compare2($version, $_version) == - 1) { + // there is a newer version - yay + $isnewerversion = 1; + } else { + // nothing new + $isnewerversion = 0; + } + + // anzeige über version-status mit ggfls. formular + // zum update schritt #1 -> download + if ($isnewerversion == 1) { + $text = 'There is a newer version available: "' . $_version . '" (Your current version is: ' . $version . ')'; + return $this->response(200, "successfull", array( + 'message' => $text, + 'link' => $version_link, + 'additional_info' => $message_addinfo + )); + } elseif ($isnewerversion == 0) { + // all good + standard_success('noupdatesavail', '', array(), true); + } else { + standard_error('customized_version', '', true); + } + } + } + throw new Exception("Not allowed to execute given command.", 403); + } + /** * returns a list of all available api functions * - * @param string $module optional, return list of functions for a specific module + * @param string $module + * optional, return list of functions for a specific module * * @throws Exception * @return array */ public function listFunctions() { - $module = $this->getParam('module'); + $module = $this->getParam('module', true, ''); $functions = array(); if ($module != null) { @@ -87,7 +159,7 @@ class Froxlor extends ApiCommand private function _getParamListFromDoc($module = null, $function = null) { try { - // set the module to be in our namespace + // set the module $cls = new \ReflectionMethod($module, $function); $comment = $cls->getDocComment(); if ($comment == false) { @@ -95,6 +167,7 @@ class Froxlor extends ApiCommand 'head' => 'There is no comment-block for "' . $module . '.' . $function . '"' ); } + $clines = explode("\n", $comment); $result = array(); $result['params'] = array(); diff --git a/lib/classes/api/commands/class.IpsAndPorts.php b/lib/classes/api/commands/class.IpsAndPorts.php index 17b90d3f..89aeb596 100644 --- a/lib/classes/api/commands/class.IpsAndPorts.php +++ b/lib/classes/api/commands/class.IpsAndPorts.php @@ -1,5 +1,19 @@ (2010-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package Panel + * + */ class IpsAndPorts extends ApiCommand implements ResourceEntity { @@ -31,8 +45,9 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity /** * return an ip/port entry by id * - * @param int $id ip-port-id - * + * @param int $id + * ip-port-id + * * @throws Exception * @return array */ @@ -58,7 +73,7 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity public function add() { if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { - + $ip = validate_ip2($this->getParam('ip'), false, 'invalidip', false, false, false, true); $port = validate($this->getParam('port', true, 80), 'port', '/^(([1-9])|([1-9][0-9])|([1-9][0-9][0-9])|([1-9][0-9][0-9][0-9])|([1-5][0-9][0-9][0-9][0-9])|(6[0-4][0-9][0-9][0-9])|(65[0-4][0-9][0-9])|(655[0-2][0-9])|(6553[0-5]))$/Di', array( 'stringisempty', @@ -203,7 +218,7 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity $vhostcontainer = $this->getParam('vhostcontainer', true, $result['vhostcontainer']); $specialsettings = validate(str_replace("\r\n", "\n", $this->getParam('specialsettings', true, $result['specialsettings'])), 'specialsettings', '/^[^\0]*$/', '', array(), true); $vhostcontainer_servername_statement = $this->getParam('vhostcontainer_servername_statement', true, $result['vhostcontainer_servername_statement']); - $default_vhostconf_domain = validate(str_replace("\r\n", "\n", $this->getParam('default_vhostconf_domain',true, $result['default_vhostconf_domain'])), 'default_vhostconf_domain', '/^[^\0]*$/', '', array(), true); + $default_vhostconf_domain = validate(str_replace("\r\n", "\n", $this->getParam('default_vhostconf_domain', true, $result['default_vhostconf_domain'])), 'default_vhostconf_domain', '/^[^\0]*$/', '', array(), true); $docroot = validate($this->getParam('docroot', true, $result['docroot']), 'docroot', '', '', array(), true); if ((int) Settings::Get('system.use_ssl') == 1) { @@ -331,8 +346,9 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity /** * delete an ip/port entry by id * - * @param int $id ip-port-id - * + * @param int $id + * ip-port-id + * * @throws Exception * @return array */ diff --git a/lib/functions/output/function.standard_success.php b/lib/functions/output/function.standard_success.php index ae996b25..1f319fb8 100644 --- a/lib/functions/output/function.standard_success.php +++ b/lib/functions/output/function.standard_success.php @@ -20,38 +20,39 @@ /** * Prints one ore more errormessages on screen * - * @param array Errormessages - * @param string A %s in the errormessage will be replaced by this string. + * @param + * array Errormessages + * @param + * string A %s in the errormessage will be replaced by this string. * @author Florian Lippert */ - -function standard_success($success_message = '', $replacer = '', $params = array()) +function standard_success($success_message = '', $replacer = '', $params = array(), $throw_exception = false) { global $s, $header, $footer, $lng, $theme; - - if(isset($lng['success'][$success_message])) - { - $success_message = strtr($lng['success'][$success_message], array('%s' => htmlentities($replacer))); + + if (isset($lng['success'][$success_message])) { + $success_message = strtr($lng['success'][$success_message], array( + '%s' => htmlentities($replacer) + )); } - if(is_array($params) && isset($params['filename'])) - { + if ($throw_exception) { + throw new Exception(strip_tags($success_message), 200); + } + + if (is_array($params) && isset($params['filename'])) { $redirect_url = $params['filename'] . '?s=' . $s; unset($params['filename']); - foreach($params as $varname => $value) - { - if($value != '') - { + foreach ($params as $varname => $value) { + if ($value != '') { $redirect_url .= '&' . $varname . '=' . $value; } } - } - else - { + } else { $redirect_url = ''; } - + eval("echo \"" . getTemplate('misc/success', '1') . "\";"); - exit; + exit(); } From 5524ff7cae4bfd48397f0c89e077ffa2f6c05646 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 19 Feb 2018 11:36:01 +0100 Subject: [PATCH 025/746] fixes in Customers::update() Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Customers.php | 15 +++++++-------- lib/classes/database/class.DbManager.php | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index 9f7ed7c4..00a03997 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -140,7 +140,7 @@ class Customers extends ApiCommand implements ResourceEntity $zipcode = validate($zipcode, 'zipcode', '/^[0-9 \-A-Z]*$/', '', array(), true); $city = validate($city, 'city', '', '', array(), true); $phone = validate($phone, 'phone', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); - $fax = validate(fax, 'fax', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); + $fax = validate($fax, 'fax', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); $idna_convert = new idna_convert_wrapper(); $email = $idna_convert->encode(validate($email, 'email', '', '', array(), true)); $customernumber = validate($customernumber, 'customer number', '/^[A-Za-z0-9 \-]*$/Di', '', array(), true); @@ -659,8 +659,7 @@ class Customers extends ApiCommand implements ResourceEntity $email = $this->getParam('email', true, $idna_convert->decode($result['email'])); $name = $this->getParam('name', true, $result['name']); $firstname = $this->getParam('firstname', true, $result['firstname']); - $company_required = (empty($name) && empty($first)); - $company = $this->getParam('company', $company_required, $result['company']); + $company = $this->getParam('company', true, $result['company']); $street = $this->getParam('street', true, $result['street']); $zipcode = $this->getParam('zipcode', true, $result['zipcode']); $city = $this->getParam('city', true, $result['city']); @@ -703,7 +702,7 @@ class Customers extends ApiCommand implements ResourceEntity $zipcode = validate($zipcode, 'zipcode', '/^[0-9 \-A-Z]*$/', '', array(), true); $city = validate($city, 'city', '', '', array(), true); $phone = validate($phone, 'phone', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); - $fax = validate(fax, 'fax', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); + $fax = validate($fax, 'fax', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); $email = $idna_convert->encode(validate($email, 'email', '', '', array(), true)); $customernumber = validate($customernumber, 'customer number', '/^[A-Za-z0-9 \-]*$/Di', '', array(), true); $def_language = validate($def_language, 'default language', '', '', array(), true); @@ -805,7 +804,7 @@ class Customers extends ApiCommand implements ResourceEntity } catch (Exception $e) { $this->logger()->logAction(ADM_ACTION, LOG_ERR, "[API] Unable to delete standard-subdomain: " . $e->getMessage()); } - $log->logAction(ADM_ACTION, LOG_NOTICE, "automatically deleted standardsubdomain for user '" . $result['loginname'] . "'"); + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] automatically deleted standardsubdomain for user '" . $result['loginname'] . "'"); inserttask('1'); } @@ -868,7 +867,7 @@ class Customers extends ApiCommand implements ResourceEntity Database::needRoot(true); $last_dbserver = 0; - $dbm = new DbManager($log); + $dbm = new DbManager($this->logger()); // For each of them while ($row_database = $databases_stmt->fetch(PDO::FETCH_ASSOC)) { @@ -897,7 +896,7 @@ class Customers extends ApiCommand implements ResourceEntity $dbm->getManager()->flushPrivileges(); Database::needRoot(false); - $log->logAction(ADM_ACTION, LOG_INFO, "deactivated user '" . $result['loginname'] . "'"); + $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] deactivated user '" . $result['loginname'] . "'"); inserttask('1'); } @@ -1149,7 +1148,7 @@ class Customers extends ApiCommand implements ResourceEntity Database::needRoot(true); $last_dbserver = 0; - $dbm = new DbManager($log); + $dbm = new DbManager($this->logger()); while ($row_database = $databases_stmt->fetch(PDO::FETCH_ASSOC)) { if ($last_dbserver != $row_database['dbserver']) { diff --git a/lib/classes/database/class.DbManager.php b/lib/classes/database/class.DbManager.php index 5e078026..91d0d0c5 100644 --- a/lib/classes/database/class.DbManager.php +++ b/lib/classes/database/class.DbManager.php @@ -49,7 +49,7 @@ class DbManager { * * @param FroxlorLogger $log */ - public function __construct(&$log = null) { + public function __construct($log = null) { $this->_log = $log; $this->_setManager(); } @@ -124,4 +124,4 @@ class DbManager { // TODO read different dbms from settings later $this->_manager = new DbManagerMySQL($this->_log); } -} \ No newline at end of file +} From 131efc544df95547d5cf5f94fd3a2e76c33c127f Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 19 Feb 2018 12:27:00 +0100 Subject: [PATCH 026/746] fix syntax error in lang file, fixes #522 Signed-off-by: Michael Kaufmann (d00p) --- lng/german.lng.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lng/german.lng.php b/lng/german.lng.php index 322ddb14..0924c1ac 100644 --- a/lng/german.lng.php +++ b/lng/german.lng.php @@ -1588,7 +1588,7 @@ $lng['integrity_check']['SubdomainLetsencrypt'] = 'Hauptdomains ohne zugewiesene $lng['admin']['mod_fcgid_umask']['title'] = 'Umask (Standard: 022)'; // Added for let's encrypt -$lng['admin']['letsencrypt']['title'] = 'SSL Zertifikat erstellen (Let\'s Encrypt'); +$lng['admin']['letsencrypt']['title'] = 'SSL Zertifikat erstellen (Let\'s Encrypt)'; $lng['admin']['letsencrypt']['description'] = 'Holt ein kostenloses Zertifikat von Let\'s Encrypt. Das Zertifikat wird automatisch erstellt und verlängert.
ACHTUNG: Wenn Wildcards aktiviert sind, wird diese Option automatisch deaktiviert. Dieses Feature befindet sich noch im Test.'; $lng['customer']['letsencrypt']['title'] = 'Benutze Let\'s Encrypt'; $lng['customer']['letsencrypt']['description'] = 'Holt ein kostenloses Zertifikat von Let\'s Encrypt. Das Zertifikat wird automatisch erstellt und verlängert.
ACHTUNG: Dieses Feature befindet sich noch im Test.'; From 8c6ae4f3a369ee64b6ad2b6552283c67388b4573 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 19 Feb 2018 13:36:15 +0100 Subject: [PATCH 027/746] add PhpSettings ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- admin_phpsettings.php | 302 +++------------ .../api/commands/class.PhpSettings.php | 364 ++++++++++++++++++ 2 files changed, 417 insertions(+), 249 deletions(-) create mode 100644 lib/classes/api/commands/class.PhpSettings.php diff --git a/admin_phpsettings.php b/admin_phpsettings.php index 1d922b3f..12b6b853 100644 --- a/admin_phpsettings.php +++ b/admin_phpsettings.php @@ -29,73 +29,29 @@ if ($page == 'overview') { if ($action == '') { + try { + $json_result = PhpSettings::getLocal($userinfo)->list(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; + $tablecontent = ''; $count = 0; - $result = Database::query(" - SELECT c.*, fd.description as fpmdesc - FROM `" . TABLE_PANEL_PHPCONFIGS . "` c - LEFT JOIN `" . TABLE_PANEL_FPMDAEMONS . "` fd ON fd.id = c.fpmsettingid - ORDER BY c.description ASC - "); - - while ($row = $result->fetch(PDO::FETCH_ASSOC)) { - - $domainresult = false; - $query_params = array( - 'id' => $row['id'] - ); - - $query = "SELECT * FROM `" . TABLE_PANEL_DOMAINS . "` - WHERE `phpsettingid` = :id - AND `parentdomainid` = '0'"; - - if ((int) $userinfo['domains_see_all'] == 0) { - $query .= " AND `adminid` = :adminid"; - $query_params['adminid'] = $userinfo['adminid']; - } - - if ((int) Settings::Get('panel.phpconfigs_hidestdsubdomain') == 1) { - $ssdids_res = Database::query(" - SELECT DISTINCT `standardsubdomain` FROM `" . TABLE_PANEL_CUSTOMERS . "` - WHERE `standardsubdomain` > 0 ORDER BY `standardsubdomain` ASC;"); - $ssdids = array(); - while ($ssd = $ssdids_res->fetch(PDO::FETCH_ASSOC)) { - $ssdids[] = $ssd['standardsubdomain']; + if (isset($result['count']) && $result['count'] > 0) { + foreach ($result['list'] as $row) { + if (isset($row['is_default']) && $row['is_default'] == true) { + $row['description'] = "" . $row['description'] . ""; } - if (count($ssdids) > 0) { - $query .= " AND `id` NOT IN (" . implode(', ', $ssdids) . ")"; + $domains = ""; + foreach ($row['domains'] as $configdomain) { + $domains .= $configdomain . "
"; } + $count++; + eval("\$tablecontent.=\"" . getTemplate("phpconfig/overview_overview") . "\";"); } - - $domainresult_stmt = Database::prepare($query); - Database::pexecute($domainresult_stmt, $query_params); - - $domains = ''; - if (Database::num_rows() > 0) { - while ($row2 = $domainresult_stmt->fetch(PDO::FETCH_ASSOC)) { - $domains .= $row2['domain'] . '
'; - } - } - - // check whether we use that config as froxor-vhost config - if (Settings::Get('system.mod_fcgid_defaultini_ownvhost') == $row['id'] || Settings::Get('phpfpm.vhost_defaultini') == $row['id']) { - $domains .= Settings::Get('system.hostname'); - } - - if ($domains == '') { - $domains = $lng['admin']['phpsettings']['notused']; - } - - // check whether this is our default config - if ((Settings::Get('system.mod_fcgid') == '1' && Settings::Get('system.mod_fcgid_defaultini') == $row['id']) || (Settings::Get('phpfpm.enabled') == '1' && Settings::Get('phpfpm.defaultini') == $row['id'])) { - $row['description'] = '' . $row['description'] . ''; - } - - $count ++; - eval("\$tablecontent.=\"" . getTemplate("phpconfig/overview_overview") . "\";"); } - $log->logAction(ADM_ACTION, LOG_INFO, "php.ini setting overview has been viewed by '" . $userinfo['loginname'] . "'"); eval("echo \"" . getTemplate("phpconfig/overview") . "\";"); } @@ -104,77 +60,11 @@ if ($page == 'overview') { if ((int) $userinfo['change_serversettings'] == 1) { if (isset($_POST['send']) && $_POST['send'] == 'send') { - $description = validate($_POST['description'], 'description'); - $phpsettings = validate(str_replace("\r\n", "\n", $_POST['phpsettings']), 'phpsettings', '/^[^\0]*$/'); - - if (Settings::Get('system.mod_fcgid') == 1) { - $binary = makeCorrectFile(validate($_POST['binary'], 'binary')); - $file_extensions = validate($_POST['file_extensions'], 'file_extensions', '/^[a-zA-Z0-9\s]*$/'); - $mod_fcgid_starter = validate($_POST['mod_fcgid_starter'], 'mod_fcgid_starter', '/^[0-9]*$/', '', array( - '-1', - '' - )); - $mod_fcgid_maxrequests = validate($_POST['mod_fcgid_maxrequests'], 'mod_fcgid_maxrequests', '/^[0-9]*$/', '', array( - '-1', - '' - )); - $mod_fcgid_umask = validate($_POST['mod_fcgid_umask'], 'mod_fcgid_umask', '/^[0-9]*$/'); - // disable fpm stuff - $fpm_config_id = 1; - $fpm_enableslowlog = 0; - $fpm_reqtermtimeout = 0; - $fpm_reqslowtimeout = 0; - $fpm_pass_authorizationheader = 0; - } elseif (Settings::Get('phpfpm.enabled') == 1) { - $fpm_config_id = intval($_POST['fpmconfig']); - $fpm_enableslowlog = isset($_POST['phpfpm_enable_slowlog']) ? (int) $_POST['phpfpm_enable_slowlog'] : 0; - $fpm_reqtermtimeout = validate($_POST['phpfpm_reqtermtimeout'], 'phpfpm_reqtermtimeout', '/^([0-9]+)(|s|m|h|d)$/'); - $fpm_reqslowtimeout = validate($_POST['phpfpm_reqslowtimeout'], 'phpfpm_reqslowtimeout', '/^([0-9]+)(|s|m|h|d)$/'); - $fpm_pass_authorizationheader = isset($_POST['phpfpm_pass_authorizationheader']) ? (int) $_POST['phpfpm_pass_authorizationheader'] : 0; - // disable fcgid stuff - $binary = '/usr/bin/php-cgi'; - $file_extensions = 'php'; - $mod_fcgid_starter = 0; - $mod_fcgid_maxrequests = 0; - $mod_fcgid_umask = "022"; + try { + PhpSettings::getLocal($userinfo, $_POST)->add(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - - if (strlen($description) == 0 || strlen($description) > 50) { - standard_error('descriptioninvalid'); - } - - $ins_stmt = Database::prepare(" - INSERT INTO `" . TABLE_PANEL_PHPCONFIGS . "` SET - `description` = :desc, - `binary` = :binary, - `file_extensions` = :fext, - `mod_fcgid_starter` = :starter, - `mod_fcgid_maxrequests` = :mreq, - `mod_fcgid_umask` = :umask, - `fpm_slowlog` = :fpmslow, - `fpm_reqterm` = :fpmreqterm, - `fpm_reqslow` = :fpmreqslow, - `phpsettings` = :phpsettings, - `fpmsettingid` = :fpmsettingid, - `pass_authorizationheader` = :fpmpassauth"); - $ins_data = array( - 'desc' => $description, - 'binary' => $binary, - 'fext' => $file_extensions, - 'starter' => $mod_fcgid_starter, - 'mreq' => $mod_fcgid_maxrequests, - 'umask' => $mod_fcgid_umask, - 'fpmslow' => $fpm_enableslowlog, - 'fpmreqterm' => $fpm_reqtermtimeout, - 'fpmreqslow' => $fpm_reqslowtimeout, - 'phpsettings' => $phpsettings, - 'fpmsettingid' => $fpm_config_id, - 'fpmpassauth' => $fpm_pass_authorizationheader - ); - Database::pexecute($ins_stmt, $ins_data); - - inserttask('1'); - $log->logAction(ADM_ACTION, LOG_INFO, "php.ini setting with description '" . $description . "' has been created by '" . $userinfo['loginname'] . "'"); redirectTo($filename, array( 'page' => $page, 's' => $s @@ -204,42 +94,23 @@ if ($page == 'overview') { } if ($action == 'delete') { - - $result_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_PHPCONFIGS . "` WHERE `id` = :id"); - $result = Database::pexecute_first($result_stmt, array( - 'id' => $id - )); - - if ((Settings::Get('system.mod_fcgid') == '1' && Settings::Get('system.mod_fcgid_defaultini_ownvhost') == $id) || (Settings::Get('phpfpm.enabled') == '1' && Settings::Get('phpfpm.vhost_defaultini') == $id)) { - standard_error('cannotdeletehostnamephpconfig'); - } - - if ((Settings::Get('system.mod_fcgid') == '1' && Settings::Get('system.mod_fcgid_defaultini') == $id) || (Settings::Get('phpfpm.enabled') == '1' && Settings::Get('phpfpm.defaultini') == $id)) { - standard_error('cannotdeletedefaultphpconfig'); + + try { + $json_result = PhpSettings::getLocal($userinfo, array('id' => $id))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } + $result = json_decode($json_result, true)['data']; if ($result['id'] != 0 && $result['id'] == $id && (int) $userinfo['change_serversettings'] == 1 && $id != 1) // cannot delete the default php.config { if (isset($_POST['send']) && $_POST['send'] == 'send') { - // set php-config to default for all domains using the - // config that is to be deleted - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_DOMAINS . "` SET - `phpsettingid` = '1' WHERE `phpsettingid` = :id"); - Database::pexecute($upd_stmt, array( - 'id' => $id - )); - - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_PANEL_PHPCONFIGS . "` WHERE `id` = :id"); - Database::pexecute($del_stmt, array( - 'id' => $id - )); - - inserttask('1'); - $log->logAction(ADM_ACTION, LOG_INFO, "php.ini setting with id #" . (int) $id . " has been deleted by '" . $userinfo['loginname'] . "'"); + try { + PhpSettings::getLocal($userinfo, array('id' => $id))->delete(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } redirectTo($filename, array( 'page' => $page, 's' => $s @@ -258,103 +129,36 @@ if ($page == 'overview') { if ($action == 'edit') { - $result_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_PHPCONFIGS . "` WHERE `id` = :id"); - $result = Database::pexecute_first($result_stmt, array( - 'id' => $id - )); + try { + $json_result = PhpSettings::getLocal($userinfo, array('id' => $id))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; if ($result['id'] != 0 && $result['id'] == $id && (int) $userinfo['change_serversettings'] == 1) { if (isset($_POST['send']) && $_POST['send'] == 'send') { - $description = validate($_POST['description'], 'description'); - $phpsettings = validate(str_replace("\r\n", "\n", $_POST['phpsettings']), 'phpsettings', '/^[^\0]*$/'); - - if (Settings::Get('system.mod_fcgid') == 1) { - $binary = makeCorrectFile(validate($_POST['binary'], 'binary')); - $file_extensions = validate($_POST['file_extensions'], 'file_extensions', '/^[a-zA-Z0-9\s]*$/'); - $mod_fcgid_starter = validate($_POST['mod_fcgid_starter'], 'mod_fcgid_starter', '/^[0-9]*$/', '', array( - '-1', - '' - )); - $mod_fcgid_maxrequests = validate($_POST['mod_fcgid_maxrequests'], 'mod_fcgid_maxrequests', '/^[0-9]*$/', '', array( - '-1', - '' - )); - $mod_fcgid_umask = validate($_POST['mod_fcgid_umask'], 'mod_fcgid_umask', '/^[0-9]*$/'); - // disable fpm stuff - $fpm_config_id = 1; - $fpm_enableslowlog = 0; - $fpm_reqtermtimeout = 0; - $fpm_reqslowtimeout = 0; - $fpm_pass_authorizationheader = 0; - } elseif (Settings::Get('phpfpm.enabled') == 1) { - $fpm_config_id = intval($_POST['fpmconfig']); - $fpm_enableslowlog = isset($_POST['phpfpm_enable_slowlog']) ? (int) $_POST['phpfpm_enable_slowlog'] : 0; - $fpm_reqtermtimeout = validate($_POST['phpfpm_reqtermtimeout'], 'phpfpm_reqtermtimeout', '/^([0-9]+)(|s|m|h|d)$/'); - $fpm_reqslowtimeout = validate($_POST['phpfpm_reqslowtimeout'], 'phpfpm_reqslowtimeout', '/^([0-9]+)(|s|m|h|d)$/'); - $fpm_pass_authorizationheader = isset($_POST['phpfpm_pass_authorizationheader']) ? (int) $_POST['phpfpm_pass_authorizationheader'] : 0; - // disable fcgid stuff - $binary = '/usr/bin/php-cgi'; - $file_extensions = 'php'; - $mod_fcgid_starter = 0; - $mod_fcgid_maxrequests = 0; - $mod_fcgid_umask = "022"; + try { + PhpSettings::getLocal($userinfo, $_POST)->update(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - - if (strlen($description) == 0 || strlen($description) > 50) { - standard_error('descriptioninvalid'); - } - - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_PHPCONFIGS . "` SET - `description` = :desc, - `binary` = :binary, - `file_extensions` = :fext, - `mod_fcgid_starter` = :starter, - `mod_fcgid_maxrequests` = :mreq, - `mod_fcgid_umask` = :umask, - `fpm_slowlog` = :fpmslow, - `fpm_reqterm` = :fpmreqterm, - `fpm_reqslow` = :fpmreqslow, - `phpsettings` = :phpsettings, - `fpmsettingid` = :fpmsettingid, - `pass_authorizationheader` = :fpmpassauth - WHERE `id` = :id"); - $upd_data = array( - 'desc' => $description, - 'binary' => $binary, - 'fext' => $file_extensions, - 'starter' => $mod_fcgid_starter, - 'mreq' => $mod_fcgid_maxrequests, - 'umask' => $mod_fcgid_umask, - 'fpmslow' => $fpm_enableslowlog, - 'fpmreqterm' => $fpm_reqtermtimeout, - 'fpmreqslow' => $fpm_reqslowtimeout, - 'phpsettings' => $phpsettings, - 'fpmsettingid' => $fpm_config_id, - 'fpmpassauth' => $fpm_pass_authorizationheader, - 'id' => $id - ); - Database::pexecute($upd_stmt, $upd_data); - - inserttask('1'); - $log->logAction(ADM_ACTION, LOG_INFO, "php.ini setting with description '" . $description . "' has been changed by '" . $userinfo['loginname'] . "'"); redirectTo($filename, array( 'page' => $page, 's' => $s )); } else { - + $fpmconfigs = ''; $configs = Database::query("SELECT * FROM `" . TABLE_PANEL_FPMDAEMONS . "` ORDER BY `description` ASC"); while ($row = $configs->fetch(PDO::FETCH_ASSOC)) { $fpmconfigs .= makeoption($row['description'], $row['id'], $result['fpmsettingid'], true, true); } - + $phpconfig_edit_data = include_once dirname(__FILE__) . '/lib/formfields/admin/phpconfig/formfield.phpconfig_edit.php'; $phpconfig_edit_form = htmlform::genHTMLForm($phpconfig_edit_data); - + $title = $phpconfig_edit_data['phpconfig_edit']['title']; $image = $phpconfig_edit_data['phpconfig_edit']['image']; @@ -373,7 +177,7 @@ if ($page == 'overview') { $result = Database::query("SELECT * FROM `" . TABLE_PANEL_FPMDAEMONS . "` ORDER BY `description` ASC"); while ($row = $result->fetch(PDO::FETCH_ASSOC)) { - + $query_params = array( 'id' => $row['id'] ); @@ -461,9 +265,9 @@ if ($page == 'overview') { } else { $pm_select = makeoption('static', 'static', 'static', true, true); - $pm_select.= makeoption('dynamic', 'dynamic', 'static', true, true); - $pm_select.= makeoption('ondemand', 'ondemand', 'static', true, true); - + $pm_select .= makeoption('dynamic', 'dynamic', 'static', true, true); + $pm_select .= makeoption('ondemand', 'ondemand', 'static', true, true); + $fpmconfig_add_data = include_once dirname(__FILE__) . '/lib/formfields/admin/phpconfig/formfield.fpmconfig_add.php'; $fpmconfig_add_form = htmlform::genHTMLForm($fpmconfig_add_data); @@ -484,11 +288,11 @@ if ($page == 'overview') { $result = Database::pexecute_first($result_stmt, array( 'id' => $id )); - + if ($id == 1) { standard_error('cannotdeletedefaultphpconfig'); } - + if ($result['id'] != 0 && $result['id'] == $id && (int) $userinfo['change_serversettings'] == 1 && $id != 1) // cannot delete the default php.config { @@ -592,9 +396,9 @@ if ($page == 'overview') { } else { $pm_select = makeoption('static', 'static', $result['pm'], true, true); - $pm_select.= makeoption('dynamic', 'dynamic', $result['pm'], true, true); - $pm_select.= makeoption('ondemand', 'ondemand', $result['pm'], true, true); - + $pm_select .= makeoption('dynamic', 'dynamic', $result['pm'], true, true); + $pm_select .= makeoption('ondemand', 'ondemand', $result['pm'], true, true); + $fpmconfig_edit_data = include_once dirname(__FILE__) . '/lib/formfields/admin/phpconfig/formfield.fpmconfig_edit.php'; $fpmconfig_edit_form = htmlform::genHTMLForm($fpmconfig_edit_data); diff --git a/lib/classes/api/commands/class.PhpSettings.php b/lib/classes/api/commands/class.PhpSettings.php new file mode 100644 index 00000000..f987c1cd --- /dev/null +++ b/lib/classes/api/commands/class.PhpSettings.php @@ -0,0 +1,364 @@ + (2010-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package Panel + * + */ +class PhpSettings extends ApiCommand implements ResourceEntity +{ + + /** + * lists all php-config entries + * + * @return array count|list + */ + public function list() + { + if ($this->isAdmin()) { + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] list php-configs"); + + $result = Database::query(" + SELECT c.*, fd.description as fpmdesc + FROM `" . TABLE_PANEL_PHPCONFIGS . "` c + LEFT JOIN `" . TABLE_PANEL_FPMDAEMONS . "` fd ON fd.id = c.fpmsettingid + ORDER BY c.description ASC + "); + + $phpconfigs = array(); + while ($row = $result->fetch(PDO::FETCH_ASSOC)) { + + $domainresult = false; + $query_params = array( + 'id' => $row['id'] + ); + + $query = "SELECT * FROM `" . TABLE_PANEL_DOMAINS . "` + WHERE `phpsettingid` = :id + AND `parentdomainid` = '0'"; + + if ((int) $this->getUserDetail('domains_see_all') == 0) { + $query .= " AND `adminid` = :adminid"; + $query_params['adminid'] = $this->getUserDetail('adminid'); + } + + if ((int) Settings::Get('panel.phpconfigs_hidestdsubdomain') == 1) { + $ssdids_res = Database::query(" + SELECT DISTINCT `standardsubdomain` FROM `" . TABLE_PANEL_CUSTOMERS . "` + WHERE `standardsubdomain` > 0 ORDER BY `standardsubdomain` ASC;"); + $ssdids = array(); + while ($ssd = $ssdids_res->fetch(PDO::FETCH_ASSOC)) { + $ssdids[] = $ssd['standardsubdomain']; + } + if (count($ssdids) > 0) { + $query .= " AND `id` NOT IN (" . implode(', ', $ssdids) . ")"; + } + } + + $domains = array(); + $domainresult_stmt = Database::prepare($query); + Database::pexecute($domainresult_stmt, $query_params, true, true); + + if (Database::num_rows() > 0) { + while ($row2 = $domainresult_stmt->fetch(PDO::FETCH_ASSOC)) { + $domains[] = $row2['domain']; + } + } + + // check whether we use that config as froxor-vhost config + if (Settings::Get('system.mod_fcgid_defaultini_ownvhost') == $row['id'] || Settings::Get('phpfpm.vhost_defaultini') == $row['id']) { + $domains[] = Settings::Get('system.hostname'); + } + + if (empty($domains)) { + $domains[] = $lng['admin']['phpsettings']['notused']; + } + + // check whether this is our default config + if ((Settings::Get('system.mod_fcgid') == '1' && Settings::Get('system.mod_fcgid_defaultini') == $row['id']) || (Settings::Get('phpfpm.enabled') == '1' && Settings::Get('phpfpm.defaultini') == $row['id'])) { + $row['is_default'] = true; + } + + $row['domains'] = $domains; + $phpconfigs[] = $row; + } + + return $this->response(200, "successfull", array( + 'count' => count($phpconfigs), + 'list' => $phpconfigs + )); + } + throw new Exception("Not allowed to execute given command.", 403); + } + + public function get() + { + if ($this->isAdmin()) { + $id = $this->getParam('id'); + + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_PHPCONFIGS . "` WHERE `id` = :id + "); + $result = Database::pexecute_first($result_stmt, array( + 'id' => $id + ), true, true); + if ($result) { + return $this->response(200, "successfull", $result); + } + throw new Exception("php-config with id #" . $id . " could not be found", 404); + } + throw new Exception("Not allowed to execute given command.", 403); + } + + public function add() + { + if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { + + // required parameter + $description = $this->getParam('description'); + $phpsettings = $this->getParam('phpsettings'); + + if (Settings::Get('system.mod_fcgid') == 1) { + $binary = $this->getParam('binary'); + } elseif (Settings::Get('phpfpm.enabled') == 1) { + $fpm_config_id = intval($this->getParam('fpmconfig')); + } + + // parameters + $file_extensions = $this->getParam('file_extensions', true, 'php'); + $mod_fcgid_starter = $this->getParam('mod_fcgid_starter', true, - 1); + $mod_fcgid_maxrequests = $this->getParam('mod_fcgid_maxrequests', true, - 1); + $mod_fcgid_umask = $this->getParam('mod_fcgid_umask', true, "022"); + $fpm_enableslowlog = $this->getParam('phpfpm_enable_slowlog', true, 0); + $fpm_reqtermtimeout = $this->getParam('phpfpm_reqtermtimeout', true, "60s"); + $fpm_reqslowtimeout = $this->getParam('phpfpm_reqslowtimeout', true, "5s"); + $fpm_pass_authorizationheader = $this->getParam('phpfpm_pass_authorizationheader', true, 0); + + // validation + $description = validate($description, 'description', '', '', array(), true); + $phpsettings = validate(str_replace("\r\n", "\n", $phpsettings), 'phpsettings', '/^[^\0]*$/', '', array(), true); + if (Settings::Get('system.mod_fcgid') == 1) { + $binary = makeCorrectFile(validate($binary, 'binary', '', '', array(), true)); + $file_extensions = validate($file_extensions, 'file_extensions', '/^[a-zA-Z0-9\s]*$/', '', array(), true); + $mod_fcgid_starter = validate($mod_fcgid_starter, 'mod_fcgid_starter', '/^[0-9]*$/', '', array( + '-1', + '' + ), true); + $mod_fcgid_maxrequests = validate($mod_fcgid_maxrequests, 'mod_fcgid_maxrequests', '/^[0-9]*$/', '', array( + '-1', + '' + ), true); + $mod_fcgid_umask = validate($mod_fcgid_umask, 'mod_fcgid_umask', '/^[0-9]*$/', '', array(), true); + // disable fpm stuff + $fpm_config_id = 1; + $fpm_enableslowlog = 0; + $fpm_reqtermtimeout = 0; + $fpm_reqslowtimeout = 0; + $fpm_pass_authorizationheader = 0; + } elseif (Settings::Get('phpfpm.enabled') == 1) { + $fpm_reqtermtimeout = validate($fpm_reqtermtimeout, 'phpfpm_reqtermtimeout', '/^([0-9]+)(|s|m|h|d)$/', '', array(), true); + $fpm_reqslowtimeout = validate($fpm_reqslowtimeout, 'phpfpm_reqslowtimeout', '/^([0-9]+)(|s|m|h|d)$/', '', array(), true); + // disable fcgid stuff + $binary = '/usr/bin/php-cgi'; + $file_extensions = 'php'; + $mod_fcgid_starter = 0; + $mod_fcgid_maxrequests = 0; + $mod_fcgid_umask = "022"; + } + + if (strlen($description) == 0 || strlen($description) > 50) { + standard_error('descriptioninvalid', '', true); + } + + $ins_stmt = Database::prepare(" + INSERT INTO `" . TABLE_PANEL_PHPCONFIGS . "` SET + `description` = :desc, + `binary` = :binary, + `file_extensions` = :fext, + `mod_fcgid_starter` = :starter, + `mod_fcgid_maxrequests` = :mreq, + `mod_fcgid_umask` = :umask, + `fpm_slowlog` = :fpmslow, + `fpm_reqterm` = :fpmreqterm, + `fpm_reqslow` = :fpmreqslow, + `phpsettings` = :phpsettings, + `fpmsettingid` = :fpmsettingid, + `pass_authorizationheader` = :fpmpassauth + "); + $ins_data = array( + 'desc' => $description, + 'binary' => $binary, + 'fext' => $file_extensions, + 'starter' => $mod_fcgid_starter, + 'mreq' => $mod_fcgid_maxrequests, + 'umask' => $mod_fcgid_umask, + 'fpmslow' => $fpm_enableslowlog, + 'fpmreqterm' => $fpm_reqtermtimeout, + 'fpmreqslow' => $fpm_reqslowtimeout, + 'phpsettings' => $phpsettings, + 'fpmsettingid' => $fpm_config_id, + 'fpmpassauth' => $fpm_pass_authorizationheader + ); + Database::pexecute($ins_stmt, $ins_data, true, true); + $ins_data['id'] = Database::lastInsertId(); + + inserttask('1'); + $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] php setting with description '" . $description . "' has been created by '" . $this->getUserDetail('loginname') . "'"); + return $this->response(200, "successfull", $ins_data); + } + throw new Exception("Not allowed to execute given command.", 403); + } + + public function update() + { + if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { + + // required parameter + $id = $this->getParam('id'); + + $json_result = PhpSettings::getLocal($this->getUserData(), array( + 'id' => $id + ))->get(); + $result = json_decode($json_result, true)['data']; + + // parameters + $description = $this->getParam('description', true, $result['description']); + $phpsettings = $this->getParam('phpsettings', true, $result['phpsettings']); + $binary = $this->getParam('binary', true, $result['binary']); + $fpm_config_id = intval($this->getParam('fpmconfig', true, $result['fpmsettingid'])); + $file_extensions = $this->getParam('file_extensions', true, $result['file_extensions']); + $mod_fcgid_starter = $this->getParam('mod_fcgid_starter', true, $result['mod_fcgid_starter']); + $mod_fcgid_maxrequests = $this->getParam('mod_fcgid_maxrequests', true, $result['mod_fcgid_maxrequests']); + $mod_fcgid_umask = $this->getParam('mod_fcgid_umask', true, $result['mod_fcgid_umask']); + $fpm_enableslowlog = $this->getParam('phpfpm_enable_slowlog', true, $result['fpm_slowlog']); + $fpm_reqtermtimeout = $this->getParam('phpfpm_reqtermtimeout', true, $result['fpm_reqterm']); + $fpm_reqslowtimeout = $this->getParam('phpfpm_reqslowtimeout', true, $result['fpm_reqslow']); + $fpm_pass_authorizationheader = $this->getParam('phpfpm_pass_authorizationheader', true, $result['pass_authorizationheader']); + + // validation + $description = validate($description, 'description', '', '', array(), true); + $phpsettings = validate(str_replace("\r\n", "\n", $phpsettings), 'phpsettings', '/^[^\0]*$/', '', array(), true); + if (Settings::Get('system.mod_fcgid') == 1) { + $binary = makeCorrectFile(validate($binary, 'binary', '', '', array(), true)); + $file_extensions = validate($file_extensions, 'file_extensions', '/^[a-zA-Z0-9\s]*$/', '', array(), true); + $mod_fcgid_starter = validate($mod_fcgid_starter, 'mod_fcgid_starter', '/^[0-9]*$/', '', array( + '-1', + '' + ), true); + $mod_fcgid_maxrequests = validate($mod_fcgid_maxrequests, 'mod_fcgid_maxrequests', '/^[0-9]*$/', '', array( + '-1', + '' + ), true); + $mod_fcgid_umask = validate($mod_fcgid_umask, 'mod_fcgid_umask', '/^[0-9]*$/', '', array(), true); + // disable fpm stuff + $fpm_config_id = 1; + $fpm_enableslowlog = 0; + $fpm_reqtermtimeout = 0; + $fpm_reqslowtimeout = 0; + $fpm_pass_authorizationheader = 0; + } elseif (Settings::Get('phpfpm.enabled') == 1) { + $fpm_reqtermtimeout = validate($fpm_reqtermtimeout, 'phpfpm_reqtermtimeout', '/^([0-9]+)(|s|m|h|d)$/', '', array(), true); + $fpm_reqslowtimeout = validate($fpm_reqslowtimeout, 'phpfpm_reqslowtimeout', '/^([0-9]+)(|s|m|h|d)$/', '', array(), true); + // disable fcgid stuff + $binary = '/usr/bin/php-cgi'; + $file_extensions = 'php'; + $mod_fcgid_starter = 0; + $mod_fcgid_maxrequests = 0; + $mod_fcgid_umask = "022"; + } + + if (strlen($description) == 0 || strlen($description) > 50) { + standard_error('descriptioninvalid', '', true); + } + + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_PHPCONFIGS . "` SET + `description` = :desc, + `binary` = :binary, + `file_extensions` = :fext, + `mod_fcgid_starter` = :starter, + `mod_fcgid_maxrequests` = :mreq, + `mod_fcgid_umask` = :umask, + `fpm_slowlog` = :fpmslow, + `fpm_reqterm` = :fpmreqterm, + `fpm_reqslow` = :fpmreqslow, + `phpsettings` = :phpsettings, + `fpmsettingid` = :fpmsettingid, + `pass_authorizationheader` = :fpmpassauth + WHERE `id` = :id + "); + $upd_data = array( + 'desc' => $description, + 'binary' => $binary, + 'fext' => $file_extensions, + 'starter' => $mod_fcgid_starter, + 'mreq' => $mod_fcgid_maxrequests, + 'umask' => $mod_fcgid_umask, + 'fpmslow' => $fpm_enableslowlog, + 'fpmreqterm' => $fpm_reqtermtimeout, + 'fpmreqslow' => $fpm_reqslowtimeout, + 'phpsettings' => $phpsettings, + 'fpmsettingid' => $fpm_config_id, + 'fpmpassauth' => $fpm_pass_authorizationheader, + 'id' => $id + ); + Database::pexecute($upd_stmt, $upd_data, true, true); + + inserttask('1'); + $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] php setting with description '" . $description . "' has been updated by '" . $this->getUserDetail('loginname') . "'"); + return $this->response(200, "successfull", $upd_data); + } + throw new Exception("Not allowed to execute given command.", 403); + } + + public function delete() + { + if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { + $id = $this->getParam('id'); + + $json_result = PhpSettings::getLocal($this->getUserData(), array( + 'id' => $id + ))->get(); + $result = json_decode($json_result, true)['data']; + + if ((Settings::Get('system.mod_fcgid') == '1' && Settings::Get('system.mod_fcgid_defaultini_ownvhost') == $id) || (Settings::Get('phpfpm.enabled') == '1' && Settings::Get('phpfpm.vhost_defaultini') == $id)) { + standard_error('cannotdeletehostnamephpconfig', '', true); + } + + if ((Settings::Get('system.mod_fcgid') == '1' && Settings::Get('system.mod_fcgid_defaultini') == $id) || (Settings::Get('phpfpm.enabled') == '1' && Settings::Get('phpfpm.defaultini') == $id)) { + standard_error('cannotdeletedefaultphpconfig', '', true); + } + + // set php-config to default for all domains using the + // config that is to be deleted + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_DOMAINS . "` SET + `phpsettingid` = '1' WHERE `phpsettingid` = :id + "); + Database::pexecute($upd_stmt, array( + 'id' => $id + ), true, true); + + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_PANEL_PHPCONFIGS . "` WHERE `id` = :id + "); + Database::pexecute($del_stmt, array( + 'id' => $id + ), true, true); + + inserttask('1'); + $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] php setting '" . $result['description'] . " has been deleted by '" . $this->getUserDetail('loginname') . "'"); + return $this->response(200, "successfull", $result); + } + throw new Exception("Not allowed to execute given command.", 403); + } +} From b2ac1fb593e357b4e523a9ac10115ec71e957c00 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 19 Feb 2018 13:58:39 +0100 Subject: [PATCH 028/746] add FpmDaemons ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- admin_phpsettings.php | 186 +++--------- lib/classes/api/commands/class.FpmDaemons.php | 277 ++++++++++++++++++ .../api/commands/class.PhpSettings.php | 2 +- 3 files changed, 317 insertions(+), 148 deletions(-) create mode 100644 lib/classes/api/commands/class.FpmDaemons.php diff --git a/admin_phpsettings.php b/admin_phpsettings.php index 12b6b853..6eee297a 100644 --- a/admin_phpsettings.php +++ b/admin_phpsettings.php @@ -171,38 +171,26 @@ if ($page == 'overview') { } elseif ($page == 'fpmdaemons') { if ($action == '') { + + try { + $json_result = FpmDaemons::getLocal($userinfo)->list(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; $tablecontent = ''; $count = 0; - $result = Database::query("SELECT * FROM `" . TABLE_PANEL_FPMDAEMONS . "` ORDER BY `description` ASC"); - - while ($row = $result->fetch(PDO::FETCH_ASSOC)) { - - $query_params = array( - 'id' => $row['id'] - ); - - $query = "SELECT * FROM `" . TABLE_PANEL_PHPCONFIGS . "` WHERE `fpmsettingid` = :id"; - - $configresult_stmt = Database::prepare($query); - Database::pexecute($configresult_stmt, $query_params); - - $configs = ''; - if (Database::num_rows() > 0) { - while ($row2 = $configresult_stmt->fetch(PDO::FETCH_ASSOC)) { - $configs .= $row2['description'] . '
'; + if (isset($result['count']) && $result['count'] > 0) { + foreach ($result['list'] as $row) { + $configs = ""; + foreach ($row['configs'] as $configused) { + $configs .= $configused . "
"; } + $count++; + eval("\$tablecontent.=\"" . getTemplate("phpconfig/fpmdaemons_overview") . "\";"); } - - if ($configs == '') { - $configs = $lng['admin']['phpsettings']['notused']; - } - - $count ++; - eval("\$tablecontent.=\"" . getTemplate("phpconfig/fpmdaemons_overview") . "\";"); } - - $log->logAction(ADM_ACTION, LOG_INFO, "fpm daemons setting overview has been viewed by '" . $userinfo['loginname'] . "'"); eval("echo \"" . getTemplate("phpconfig/fpmdaemons") . "\";"); } @@ -211,53 +199,11 @@ if ($page == 'overview') { if ((int) $userinfo['change_serversettings'] == 1) { if (isset($_POST['send']) && $_POST['send'] == 'send') { - $description = validate($_POST['description'], 'description'); - $reload_cmd = validate($_POST['reload_cmd'], 'reload_cmd'); - $config_dir = validate($_POST['config_dir'], 'config_dir'); - $pm = $_POST['pm']; - $max_children = isset($_POST['max_children']) ? (int) $_POST['max_children'] : 0; - $start_servers = isset($_POST['start_servers']) ? (int) $_POST['start_servers'] : 0; - $min_spare_servers = isset($_POST['min_spare_servers']) ? (int) $_POST['min_spare_servers'] : 0; - $max_spare_servers = isset($_POST['max_spare_servers']) ? (int) $_POST['max_spare_servers'] : 0; - $max_requests = isset($_POST['max_requests']) ? (int) $_POST['max_requests'] : 0; - $idle_timeout = isset($_POST['idle_timeout']) ? (int) $_POST['idle_timeout'] : 0; - $limit_extensions = validate($_POST['limit_extensions'], 'limit_extensions', '/^(\.[a-z]([a-z0-9]+)\ ?)+$/'); - - if (strlen($description) == 0 || strlen($description) > 50) { - standard_error('descriptioninvalid'); + try { + FpmDaemons::getLocal($userinfo, $_POST)->add(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - - $ins_stmt = Database::prepare(" - INSERT INTO `" . TABLE_PANEL_FPMDAEMONS . "` SET - `description` = :desc, - `reload_cmd` = :reload_cmd, - `config_dir` = :config_dir, - `pm` = :pm, - `max_children` = :max_children, - `start_servers` = :start_servers, - `min_spare_servers` = :min_spare_servers, - `max_spare_servers` = :max_spare_servers, - `max_requests` = :max_requests, - `idle_timeout` = :idle_timeout, - `limit_extensions` = :limit_extensions - "); - $ins_data = array( - 'desc' => $description, - 'reload_cmd' => $reload_cmd, - 'config_dir' => makeCorrectDir($config_dir), - 'pm' => $pm, - 'max_children' => $max_children, - 'start_servers' => $start_servers, - 'min_spare_servers' => $min_spare_servers, - 'max_spare_servers' => $max_spare_servers, - 'max_requests' => $max_requests, - 'idle_timeout' => $idle_timeout, - 'limit_extensions' => $limit_extensions - ); - Database::pexecute($ins_stmt, $ins_data); - - inserttask('1'); - $log->logAction(ADM_ACTION, LOG_INFO, "fpm-daemon setting with description '" . $description . "' has been created by '" . $userinfo['loginname'] . "'"); redirectTo($filename, array( 'page' => $page, 's' => $s @@ -283,11 +229,12 @@ if ($page == 'overview') { if ($action == 'delete') { - $result_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_FPMDAEMONS . "` WHERE `id` = :id"); - $result = Database::pexecute_first($result_stmt, array( - 'id' => $id - )); + try { + $json_result = FpmDaemons::getLocal($userinfo, array('id' => $id))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; if ($id == 1) { standard_error('cannotdeletedefaultphpconfig'); @@ -295,24 +242,12 @@ if ($page == 'overview') { if ($result['id'] != 0 && $result['id'] == $id && (int) $userinfo['change_serversettings'] == 1 && $id != 1) // cannot delete the default php.config { - if (isset($_POST['send']) && $_POST['send'] == 'send') { - // set default fpm daemon config for all php-config that use this config that is to be deleted - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_PHPCONFIGS . "` SET - `fpmsettingid` = '1' WHERE `fpmsettingid` = :id"); - Database::pexecute($upd_stmt, array( - 'id' => $id - )); - - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_PANEL_FPMDAEMONS . "` WHERE `id` = :id"); - Database::pexecute($del_stmt, array( - 'id' => $id - )); - - inserttask('1'); - $log->logAction(ADM_ACTION, LOG_INFO, "fpm-daemon setting with id #" . (int) $id . " has been deleted by '" . $userinfo['loginname'] . "'"); + try { + FpmDaemons::getLocal($userinfo, $_POST)->delete(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } redirectTo($filename, array( 'page' => $page, 's' => $s @@ -331,64 +266,21 @@ if ($page == 'overview') { if ($action == 'edit') { - $result_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_FPMDAEMONS . "` WHERE `id` = :id"); - $result = Database::pexecute_first($result_stmt, array( - 'id' => $id - )); + try { + $json_result = FpmDaemons::getLocal($userinfo, array('id' => $id))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; if ($result['id'] != 0 && $result['id'] == $id && (int) $userinfo['change_serversettings'] == 1) { if (isset($_POST['send']) && $_POST['send'] == 'send') { - $description = validate($_POST['description'], 'description'); - $reload_cmd = validate($_POST['reload_cmd'], 'reload_cmd'); - $config_dir = validate($_POST['config_dir'], 'config_dir'); - $pm = $_POST['pm']; - $max_children = isset($_POST['max_children']) ? (int) $_POST['max_children'] : $result['max_children']; - $start_servers = isset($_POST['start_servers']) ? (int) $_POST['start_servers'] : $result['start_servers']; - $min_spare_servers = isset($_POST['min_spare_servers']) ? (int) $_POST['min_spare_servers'] : $result['min_spare_servers']; - $max_spare_servers = isset($_POST['max_spare_servers']) ? (int) $_POST['max_spare_servers'] : $result['max_spare_servers']; - $max_requests = isset($_POST['max_requests']) ? (int) $_POST['max_requests'] : $result['max_requests']; - $idle_timeout = isset($_POST['idle_timeout']) ? (int) $_POST['idle_timeout'] : $result['idle_timeout']; - $limit_extensions = validate($_POST['limit_extensions'], 'limit_extensions', '/^(\.[a-z]([a-z0-9]+)\ ?)+$/'); - - if (strlen($description) == 0 || strlen($description) > 50) { - standard_error('descriptioninvalid'); + try { + FpmDaemons::getLocal($userinfo, $_POST)->update(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_FPMDAEMONS . "` SET - `description` = :desc, - `reload_cmd` = :reload_cmd, - `config_dir` = :config_dir, - `pm` = :pm, - `max_children` = :max_children, - `start_servers` = :start_servers, - `min_spare_servers` = :min_spare_servers, - `max_spare_servers` = :max_spare_servers, - `max_requests` = :max_requests, - `idle_timeout` = :idle_timeout, - `limit_extensions` = :limit_extensions - WHERE `id` = :id - "); - $upd_data = array( - 'desc' => $description, - 'reload_cmd' => $reload_cmd, - 'config_dir' => makeCorrectDir($config_dir), - 'pm' => $pm, - 'max_children' => $max_children, - 'start_servers' => $start_servers, - 'min_spare_servers' => $min_spare_servers, - 'max_spare_servers' => $max_spare_servers, - 'max_requests' => $max_requests, - 'idle_timeout' => $idle_timeout, - 'limit_extensions' => $limit_extensions, - 'id' => $id - ); - Database::pexecute($upd_stmt, $upd_data); - - inserttask('1'); - $log->logAction(ADM_ACTION, LOG_INFO, "fpm-daemon setting with description '" . $description . "' has been changed by '" . $userinfo['loginname'] . "'"); redirectTo($filename, array( 'page' => $page, 's' => $s diff --git a/lib/classes/api/commands/class.FpmDaemons.php b/lib/classes/api/commands/class.FpmDaemons.php new file mode 100644 index 00000000..9d906593 --- /dev/null +++ b/lib/classes/api/commands/class.FpmDaemons.php @@ -0,0 +1,277 @@ + (2010-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package Panel + * + */ +class FpmDaemons extends ApiCommand implements ResourceEntity +{ + + /** + * lists all php-config entries + * + * @return array count|list + */ + public function list() + { + if ($this->isAdmin()) { + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] list fpm-daemons"); + + $result = Database::query(" + SELECT * FROM `" . TABLE_PANEL_FPMDAEMONS . "` ORDER BY `description` ASC + "); + + $fpmdaemons = array(); + while ($row = $result->fetch(PDO::FETCH_ASSOC)) { + + $query_params = array( + 'id' => $row['id'] + ); + + $query = "SELECT * FROM `" . TABLE_PANEL_PHPCONFIGS . "` WHERE `fpmsettingid` = :id"; + + $configresult_stmt = Database::prepare($query); + Database::pexecute($configresult_stmt, $query_params, true, true); + + $configs = array(); + if (Database::num_rows() > 0) { + while ($row2 = $configresult_stmt->fetch(PDO::FETCH_ASSOC)) { + $configs[] = $row2['description']; + } + } + + if (empty($configs)) { + $configs[] = $lng['admin']['phpsettings']['notused']; + } + + $row['configs'] = $configs; + $fpmdaemons[] = $row; + } + + return $this->response(200, "successfull", array( + 'count' => count($fpmdaemons), + 'list' => $fpmdaemons + )); + } + throw new Exception("Not allowed to execute given command.", 403); + } + + public function get() + { + if ($this->isAdmin()) { + $id = $this->getParam('id'); + + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_FPMDAEMONS . "` WHERE `id` = :id + "); + $result = Database::pexecute_first($result_stmt, array( + 'id' => $id + ), true, true); + if ($result) { + return $this->response(200, "successfull", $result); + } + throw new Exception("fpm-daemon with id #" . $id . " could not be found", 404); + } + throw new Exception("Not allowed to execute given command.", 403); + } + + public function add() + { + if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { + + // required parameter + $description = $this->getParam('description'); + $reload_cmd = $this->getParam('reload_cmd'); + $config_dir = $this->getParam('config_dir'); + + // parameters + $pm = $this->getParam('pm', true, 'static'); + $max_children = $this->getParam('max_children', true, 0); + $start_servers = $this->getParam('start_servers', true, 0); + $min_spare_servers = $this->getParam('min_spare_servers', true, 0); + $max_spare_servers = $this->getParam('max_spare_servers', true, 0); + $max_requests = $this->getParam('max_requests', true, 0); + $idle_timeout = $this->getParam('idle_timeout', true, 0); + $limit_extensions = $this->getParam('limit_extensions', true, ''); + + // validation + $description = validate($description, 'description', '', '', array(), true); + $reload_cmd = validate($reload_cmd, 'reload_cmd', '', '', array(), true); + $config_dir = validate($config_dir, 'config_dir', '', '', array(), true); + if (! in_array($pm, array( + 'static', + 'dynamic', + 'ondemand' + ))) { + throw new ErrorException("Unknown process manager", 406); + } + $limit_extensions = validate($limit_extensions, 'limit_extensions', '/^(\.[a-z]([a-z0-9]+)\ ?)+$/', '', array(), true); + + if (strlen($description) == 0 || strlen($description) > 50) { + standard_error('descriptioninvalid', '', true); + } + + $ins_stmt = Database::prepare(" + INSERT INTO `" . TABLE_PANEL_FPMDAEMONS . "` SET + `description` = :desc, + `reload_cmd` = :reload_cmd, + `config_dir` = :config_dir, + `pm` = :pm, + `max_children` = :max_children, + `start_servers` = :start_servers, + `min_spare_servers` = :min_spare_servers, + `max_spare_servers` = :max_spare_servers, + `max_requests` = :max_requests, + `idle_timeout` = :idle_timeout, + `limit_extensions` = :limit_extensions + "); + $ins_data = array( + 'desc' => $description, + 'reload_cmd' => $reload_cmd, + 'config_dir' => makeCorrectDir($config_dir), + 'pm' => $pm, + 'max_children' => $max_children, + 'start_servers' => $start_servers, + 'min_spare_servers' => $min_spare_servers, + 'max_spare_servers' => $max_spare_servers, + 'max_requests' => $max_requests, + 'idle_timeout' => $idle_timeout, + 'limit_extensions' => $limit_extensions + ); + Database::pexecute($ins_stmt, $ins_data); + $ins_data['id'] = Database::lastInsertId(); + + inserttask('1'); + $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] fpm-daemon with description '" . $description . "' has been created by '" . $this->getUserDetail('loginname') . "'"); + return $this->response(200, "successfull", $ins_data); + } + throw new Exception("Not allowed to execute given command.", 403); + } + + public function update() + { + if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { + + // required parameter + $id = $this->getParam('id'); + + $json_result = PhpSettings::getLocal($this->getUserData(), array( + 'id' => $id + ))->get(); + $result = json_decode($json_result, true)['data']; + + // parameters + $description = $this->getParam('description', true, $result['description']); + $reload_cmd = $this->getParam('reload_cmd', true, $result['reload_cmd']); + $config_dir = $this->getParam('config_dir', true, $result['config_dir']); + $pm = $this->getParam('pm', true, $result['pm']); + $max_children = $this->getParam('max_children', true, $result['max_children']); + $start_servers = $this->getParam('start_servers', true, $result['start_servers']); + $min_spare_servers = $this->getParam('min_spare_servers', true, $result['min_spare_servers']); + $max_spare_servers = $this->getParam('max_spare_servers', true, $result['max_spare_servers']); + $max_requests = $this->getParam('max_requests', true, $result['max_requests']); + $idle_timeout = $this->getParam('idle_timeout', true, $result['idle_timeout']); + $limit_extensions = $this->getParam('limit_extensions', true, $result['limit_extensions']); + + // validation + $description = validate($description, 'description', '', '', array(), true); + $reload_cmd = validate($reload_cmd, 'reload_cmd', '', '', array(), true); + $config_dir = validate($config_dir, 'config_dir', '', '', array(), true); + if (! in_array($pm, array( + 'static', + 'dynamic', + 'ondemand' + ))) { + throw new ErrorException("Unknown process manager", 406); + } + $limit_extensions = validate($limit_extensions, 'limit_extensions', '/^(\.[a-z]([a-z0-9]+)\ ?)+$/', '', array(), true); + + if (strlen($description) == 0 || strlen($description) > 50) { + standard_error('descriptioninvalid', '', true); + } + + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_FPMDAEMONS . "` SET + `description` = :desc, + `reload_cmd` = :reload_cmd, + `config_dir` = :config_dir, + `pm` = :pm, + `max_children` = :max_children, + `start_servers` = :start_servers, + `min_spare_servers` = :min_spare_servers, + `max_spare_servers` = :max_spare_servers, + `max_requests` = :max_requests, + `idle_timeout` = :idle_timeout, + `limit_extensions` = :limit_extensions + WHERE `id` = :id + "); + $upd_data = array( + 'desc' => $description, + 'reload_cmd' => $reload_cmd, + 'config_dir' => makeCorrectDir($config_dir), + 'pm' => $pm, + 'max_children' => $max_children, + 'start_servers' => $start_servers, + 'min_spare_servers' => $min_spare_servers, + 'max_spare_servers' => $max_spare_servers, + 'max_requests' => $max_requests, + 'idle_timeout' => $idle_timeout, + 'limit_extensions' => $limit_extensions, + 'id' => $id + ); + Database::pexecute($upd_stmt, $upd_data, true, true); + + inserttask('1'); + $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] fpm-daemon with description '" . $description . "' has been updated by '" . $this->getUserDetail('loginname') . "'"); + return $this->response(200, "successfull", $upd_data); + } + throw new Exception("Not allowed to execute given command.", 403); + } + + public function delete() + { + if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { + $id = $this->getParam('id'); + + if ($id == 1) { + standard_error('cannotdeletedefaultphpconfig', '', true); + } + + $json_result = FpmDaemons::getLocal($this->getUserData(), array( + 'id' => $id + ))->get(); + $result = json_decode($json_result, true)['data']; + + // set default fpm daemon config for all php-config that use this config that is to be deleted + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_PHPCONFIGS . "` SET + `fpmsettingid` = '1' WHERE `fpmsettingid` = :id + "); + Database::pexecute($upd_stmt, array( + 'id' => $id + ), true, true); + + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_PANEL_FPMDAEMONS . "` WHERE `id` = :id + "); + Database::pexecute($del_stmt, array( + 'id' => $id + ), true, true); + + inserttask('1'); + $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] fpm-daemon setting '" . $result['description'] . "' has been deleted by '" . $this->getUserDetail('loginname') . "'"); + return $this->response(200, "successfull", $result); + } + throw new Exception("Not allowed to execute given command.", 403); + } +} diff --git a/lib/classes/api/commands/class.PhpSettings.php b/lib/classes/api/commands/class.PhpSettings.php index f987c1cd..686da052 100644 --- a/lib/classes/api/commands/class.PhpSettings.php +++ b/lib/classes/api/commands/class.PhpSettings.php @@ -356,7 +356,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity ), true, true); inserttask('1'); - $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] php setting '" . $result['description'] . " has been deleted by '" . $this->getUserDetail('loginname') . "'"); + $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] php setting '" . $result['description'] . "' has been deleted by '" . $this->getUserDetail('loginname') . "'"); return $this->response(200, "successfull", $result); } throw new Exception("Not allowed to execute given command.", 403); From 033393880d59babc2445a950d8096777603f7ebf Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 20 Feb 2018 14:14:47 +0100 Subject: [PATCH 029/746] fix typo in variable name Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Customers.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index 00a03997..2ce4bbae 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -96,7 +96,7 @@ class Customers extends ApiCommand implements ResourceEntity // parameters $name = $this->getParam('name', true, ''); $firstname = $this->getParam('firstname', true, ''); - $company_required = (empty($name) && empty($first)); + $company_required = (empty($name) && empty($firstname)); $company = $this->getParam('company', $company_required, ''); $street = $this->getParam('street', true, ''); $zipcode = $this->getParam('zipcode', true, ''); From eabc78c84f087518ec4f7ac39e2f7ba350d6cd09 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 20 Feb 2018 14:21:40 +0100 Subject: [PATCH 030/746] enhance check for requirement of company field Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Customers.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index 2ce4bbae..5cbaddb4 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -92,11 +92,11 @@ class Customers extends ApiCommand implements ResourceEntity // required parameters $email = $this->getParam('email'); - + // parameters $name = $this->getParam('name', true, ''); $firstname = $this->getParam('firstname', true, ''); - $company_required = (empty($name) && empty($firstname)); + $company_required = (! empty($name) && empty($firstname)) || (empty($name) && ! empty($firstname)) || (empty($name) && empty($firstname)); $company = $this->getParam('company', $company_required, ''); $street = $this->getParam('street', true, ''); $zipcode = $this->getParam('zipcode', true, ''); @@ -654,7 +654,7 @@ class Customers extends ApiCommand implements ResourceEntity // parameters $move_to_admin = intval_ressource($this->getParam('move_to_admin', true, 0)); - + $idna_convert = new idna_convert_wrapper(); $email = $this->getParam('email', true, $idna_convert->decode($result['email'])); $name = $this->getParam('name', true, $result['name']); @@ -670,7 +670,7 @@ class Customers extends ApiCommand implements ResourceEntity $gender = intval_ressource($this->getParam('gender', true, $result['gender'])); $custom_notes = $this->getParam('custom_notes', true, $result['custom_notes']); $custom_notes_show = $this->getParam('custom_notes_show', true, $result['custom_notes_show']); - + $dec_places = Settings::Get('panel.decimal_places'); $diskspace = $this->getUlParam('diskspace', 'diskspace_ul', true, round($result['diskspace'] / 1024, $dec_places)); $traffic = $this->getUlParam('traffic', 'traffic_ul', true, round($result['traffic'] / (1024 * 1024), $dec_places)); @@ -692,7 +692,7 @@ class Customers extends ApiCommand implements ResourceEntity $perlenabled = $this->getParam('perlenabled', true, $result['perlenabled']); $dnsenabled = $this->getParam('dnsenabled', true, $result['dnsenabled']); $deactivated = $this->getParam('deactivated', true, $result['deactivated']); - + // validation $idna_convert = new idna_convert_wrapper(); $name = validate($name, 'name', '', '', array(), true); @@ -707,15 +707,15 @@ class Customers extends ApiCommand implements ResourceEntity $customernumber = validate($customernumber, 'customer number', '/^[A-Za-z0-9 \-]*$/Di', '', array(), true); $def_language = validate($def_language, 'default language', '', '', array(), true); $custom_notes = validate(str_replace("\r\n", "\n", $custom_notes), 'custom_notes', '/^[^\0]*$/', '', array(), true); - + if (Settings::Get('system.mail_quota_enabled') != '1') { $email_quota = - 1; } - + if (Settings::Get('ticket.enabled') != '1') { $tickets = - 1; } - + // Either $name and $firstname or the $company must be inserted if ($name == '' && $company == '') { standard_error(array( From b9653c5abd33c2e0d46bc3abdbe5aae673304306 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 20 Feb 2018 19:11:33 +0100 Subject: [PATCH 031/746] fix company-required check for good now :P Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Customers.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index 5cbaddb4..29acb6ff 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -97,7 +97,7 @@ class Customers extends ApiCommand implements ResourceEntity $name = $this->getParam('name', true, ''); $firstname = $this->getParam('firstname', true, ''); $company_required = (! empty($name) && empty($firstname)) || (empty($name) && ! empty($firstname)) || (empty($name) && empty($firstname)); - $company = $this->getParam('company', $company_required, ''); + $company = $this->getParam('company', ($company_required ? false : true), ''); $street = $this->getParam('street', true, ''); $zipcode = $this->getParam('zipcode', true, ''); $city = $this->getParam('city', true, ''); From 5437fcdc898cb505348b89c5a3d09a16e29f67d2 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 20 Feb 2018 19:15:55 +0100 Subject: [PATCH 032/746] insert tasks to rebuild configs etc. after import of settings, thx to v3ng for noticing Signed-off-by: Michael Kaufmann (d00p) --- admin_settings.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/admin_settings.php b/admin_settings.php index ac8ff85c..8bc17a13 100644 --- a/admin_settings.php +++ b/admin_settings.php @@ -319,6 +319,12 @@ elseif ($page == 'importexport' && $userinfo['change_serversettings'] == '1') } catch(Exception $e) { dynamic_error($e->getMessage()); } + inserttask('1'); + inserttask('10'); + // Using nameserver, insert a task which rebuilds the server config + inserttask('4'); + // cron.d file + inserttask('99'); standard_success('settingsimported', '', array('filename' => 'admin_settings.php')); } dynamic_error("Upload failed"); From d5b9ad345235b4dd6ad6048f5ac1e08c26261fda Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 20 Feb 2018 21:37:06 +0100 Subject: [PATCH 033/746] darn, stay php-5.3 compatible, thx greppy Signed-off-by: Michael Kaufmann (d00p) --- scripts/jobs/cron_tasks.inc.http.10.apache.php | 3 ++- scripts/jobs/cron_tasks.inc.http.20.lighttpd.php | 3 ++- scripts/jobs/cron_tasks.inc.http.30.nginx.php | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/jobs/cron_tasks.inc.http.10.apache.php b/scripts/jobs/cron_tasks.inc.http.10.apache.php index 2a7ab516..e49ef34e 100644 --- a/scripts/jobs/cron_tasks.inc.http.10.apache.php +++ b/scripts/jobs/cron_tasks.inc.http.10.apache.php @@ -66,7 +66,8 @@ class apache extends HttpConfigBase foreach ($restart_cmds as $restart_cmd) { // check whether the config dir is empty (no domains uses this daemon) // so we need to create a dummy - $isDirEmpty = !(new \FilesystemIterator($restart_cmd['config_dir']))->valid(); + $fsi = new \FilesystemIterator($restart_cmd['config_dir']); + $isDirEmpty = !$fsi->valid(); if ($isDirEmpty) { $this->logger->logAction(CRON_ACTION, LOG_INFO, 'apache::reload: fpm config directory "' . $restart_cmd['config_dir'] . '" is empty. Creating dummy.'); phpinterface_fpm::createDummyPool($restart_cmd['config_dir']); diff --git a/scripts/jobs/cron_tasks.inc.http.20.lighttpd.php b/scripts/jobs/cron_tasks.inc.http.20.lighttpd.php index 99232c8c..84b88400 100644 --- a/scripts/jobs/cron_tasks.inc.http.20.lighttpd.php +++ b/scripts/jobs/cron_tasks.inc.http.20.lighttpd.php @@ -66,7 +66,8 @@ class lighttpd extends HttpConfigBase foreach ($restart_cmds as $restart_cmd) { // check whether the config dir is empty (no domains uses this daemon) // so we need to create a dummy - $isDirEmpty = !(new \FilesystemIterator($restart_cmd['config_dir']))->valid(); + $fsi = new \FilesystemIterator($restart_cmd['config_dir']); + $isDirEmpty = !$fsi->valid(); if ($isDirEmpty) { $this->logger->logAction(CRON_ACTION, LOG_INFO, 'lighttpd::reload: fpm config directory "' . $restart_cmd['config_dir'] . '" is empty. Creating dummy.'); phpinterface_fpm::createDummyPool($restart_cmd['config_dir']); diff --git a/scripts/jobs/cron_tasks.inc.http.30.nginx.php b/scripts/jobs/cron_tasks.inc.http.30.nginx.php index 7f86f605..e2eac04d 100644 --- a/scripts/jobs/cron_tasks.inc.http.30.nginx.php +++ b/scripts/jobs/cron_tasks.inc.http.30.nginx.php @@ -81,7 +81,8 @@ class nginx extends HttpConfigBase foreach ($restart_cmds as $restart_cmd) { // check whether the config dir is empty (no domains uses this daemon) // so we need to create a dummy - $isDirEmpty = !(new \FilesystemIterator($restart_cmd['config_dir']))->valid(); + $fsi = new \FilesystemIterator($restart_cmd['config_dir']); + $isDirEmpty = !$fsi->valid(); if ($isDirEmpty) { $this->logger->logAction(CRON_ACTION, LOG_INFO, 'nginx::reload: fpm config directory "' . $restart_cmd['config_dir'] . '" is empty. Creating dummy.'); phpinterface_fpm::createDummyPool($restart_cmd['config_dir']); From 26b9c030b53053761bfe70dc48b280b6e3785748 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Wed, 21 Feb 2018 10:57:46 +0100 Subject: [PATCH 034/746] leave default-values for adding std-subdomain when adding customer from the parameters-array; do not require ipandports parameter when adding domain but rather default to system.defaultip if no ipandport is given Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Customers.php | 21 +------------------- lib/classes/api/commands/class.Domains.php | 8 ++++++-- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index 29acb6ff..83fbb373 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -517,19 +517,9 @@ class Customers extends ApiCommand implements ResourceEntity 'domain' => $_stdsubdomain, 'customerid' => $customerid, 'adminid' => $this->getUserDetail('adminid'), - 'parentdomainid' => '0', 'docroot' => $documentroot, - 'adddate' => time(), 'phpenabled' => $phpenabled, - 'zonefile' => '', - 'isemaildomain' => '0', - 'caneditdomain' => '0', - 'openbasedir' => '1', - 'speciallogfile' => '0', - 'dkim_id' => '0', - 'dkim_privkey' => '', - 'dkim_pubkey' => '', - 'ipandport' => explode(',', Settings::Get('system.defaultip')) + 'openbasedir' => '1' ); $domainid = - 1; try { @@ -759,18 +749,9 @@ class Customers extends ApiCommand implements ResourceEntity 'domain' => $_stdsubdomain, 'customerid' => $result['customerid'], 'adminid' => $this->getUserDetail('adminid'), - 'parentdomainid' => '0', 'docroot' => $result['documentroot'], - 'adddate' => time(), 'phpenabled' => $phpenabled, - 'zonefile' => '', - 'isemaildomain' => '0', - 'caneditdomain' => '0', 'openbasedir' => '1', - 'speciallogfile' => '0', - 'dkim_id' => '0', - 'dkim_privkey' => '', - 'dkim_pubkey' => '', 'ipandport' => explode(',', Settings::Get('system.defaultip')) ); $domainid = - 1; diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index c25b4d64..e3ae5878 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -97,9 +97,9 @@ class Domains extends ApiCommand implements ResourceEntity // parameters $p_domain = $this->getParam('domain'); $customerid = intval($this->getParam('customerid')); - $p_ipandports = $this->getParam('ipandport'); // optional parameters + $p_ipandports = $this->getParam('ipandport', true, explode(',', Settings::Get('system.defaultip'))); $adminid = intval($this->getParam('adminid', true, $this->getUserDetail('adminid'))); $subcanemaildomain = $this->getParam('subcanemaildomain', true, 0); $isemaildomain = $this->getParam('isemaildomain', true, 0); @@ -310,7 +310,11 @@ class Domains extends ApiCommand implements ResourceEntity $additional_ip_condition = ''; $aip_param = array(); } - + + if (empty($p_ipandports)) { + throw new Exception("No IPs given, unable to add domain (no default IPs set?", 406); + } + $ipandports = array(); if (! empty($p_ipandport) && ! is_array($p_ipandports)) { $p_ipandports = unserialize($p_ipandports); From 5480fcbf5d172067fec3d20fb0ddda8dbbbf2eed Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Wed, 21 Feb 2018 11:50:34 +0100 Subject: [PATCH 035/746] add default-ssl-ip setting Signed-off-by: Michael Kaufmann (d00p) --- actions/admin/settings/120.system.php | 10 ++++ install/froxlor.sql | 3 +- .../updates/froxlor/0.10/update_0.10.inc.php | 11 +++- lib/classes/api/abstract.ApiCommand.php | 14 +++++ lib/classes/api/commands/class.Customers.php | 3 +- lib/classes/api/commands/class.Domains.php | 4 +- .../api/commands/class.IpsAndPorts.php | 2 +- .../integrity/class.IntegrityCheck.php | 4 +- lib/classes/settings/class.SImExporter.php | 1 + .../function.getIpPortCombinations.php | 4 ++ .../function.storeSettingDefaultIp.php | 52 +++++++++++++++++++ lib/version.inc.php | 2 +- lng/english.lng.php | 2 + lng/german.lng.php | 4 +- 14 files changed, 106 insertions(+), 10 deletions(-) diff --git a/actions/admin/settings/120.system.php b/actions/admin/settings/120.system.php index e1b4c5cd..644ee5f4 100644 --- a/actions/admin/settings/120.system.php +++ b/actions/admin/settings/120.system.php @@ -60,6 +60,16 @@ return array( 'default' => '', 'save_method' => 'storeSettingDefaultIp', ), + 'system_defaultsslip' => array( + 'label' => $lng['serversettings']['defaultsslip'], + 'settinggroup' => 'system', + 'varname' => 'defaultsslip', + 'type' => 'option', + 'option_mode' => 'multiple', + 'option_options_method' => 'getSslIpPortCombinations', + 'default' => '', + 'save_method' => 'storeSettingDefaultSslIp', + ), 'system_hostname' => array( 'label' => $lng['serversettings']['hostname'], 'settinggroup' => 'system', diff --git a/install/froxlor.sql b/install/froxlor.sql index 6a8534a8..0f5b1dcd 100644 --- a/install/froxlor.sql +++ b/install/froxlor.sql @@ -540,6 +540,7 @@ opcache.interned_strings_buffer'), ('system', 'mysql_access_host', 'localhost'), ('system', 'lastcronrun', ''), ('system', 'defaultip', '1'), + ('system', 'defaultsslip', ''), ('system', 'phpappendopenbasedir', '/tmp/'), ('system', 'deactivateddocroot', ''), ('system', 'mailpwcleartext', '1'), @@ -689,7 +690,7 @@ opcache.interned_strings_buffer'), ('panel', 'password_special_char', '!?<>§$%+#=@'), ('panel', 'customer_hide_options', ''), ('panel', 'version', '0.10.0'), - ('panel', 'db_version', '201802150'); + ('panel', 'db_version', '201802210'); DROP TABLE IF EXISTS `panel_tasks`; diff --git a/install/updates/froxlor/0.10/update_0.10.inc.php b/install/updates/froxlor/0.10/update_0.10.inc.php index 7e796501..bb9cf1c9 100644 --- a/install/updates/froxlor/0.10/update_0.10.inc.php +++ b/install/updates/froxlor/0.10/update_0.10.inc.php @@ -14,7 +14,7 @@ * @package Install * */ -if (!defined('_CRON_UPDATE')) { +if (! defined('_CRON_UPDATE')) { if (! defined('AREA') || (defined('AREA') && AREA != 'admin') || ! isset($userinfo['loginname']) || (isset($userinfo['loginname']) && $userinfo['loginname'] == '')) { header('Location: ../../../../index.php'); exit(); @@ -26,3 +26,12 @@ if (isFroxlorVersion('0.9.39.5')) { showUpdateStep("Updating from 0.9.39.5 to 0.10.0", false); updateToVersion('0.10.0'); } + +if (isDatabaseVersion('201802150')) { + + showUpdateStep("Adding new default-ssl-ip setting"); + Settings::AddNew('system.defaultsslip', ''); + lastStepStatus(0); + + updateToDbVersion('201802210'); +} diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index 5a749e63..abf2eda8 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -184,6 +184,20 @@ abstract class ApiCommand return $this->cmd_params[$param]; } + /** + * get specific parameter which also has and unlimited-field + * + * @param string $param + * parameter to get out of the request-parameter list + * @param string $ul_field + * parameter to get out of the request-parameter list + * @param bool $optional + * default: false + * @param mixed $default + * value which is returned if optional=true and param is not set + * + * @return mixed + */ protected function getUlParam($param = null, $ul_field = null, $optional = false, $default = 0) { $param_value = intval_ressource($this->getParam($param, $optional, $default)); diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index 83fbb373..ecef61f4 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -751,8 +751,7 @@ class Customers extends ApiCommand implements ResourceEntity 'adminid' => $this->getUserDetail('adminid'), 'docroot' => $result['documentroot'], 'phpenabled' => $phpenabled, - 'openbasedir' => '1', - 'ipandport' => explode(',', Settings::Get('system.defaultip')) + 'openbasedir' => '1' ); $domainid = - 1; try { diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index e3ae5878..6d4085d3 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -124,7 +124,7 @@ class Domains extends ApiCommand implements ResourceEntity $mod_fcgid_maxrequests = $this->getParam('mod_fcgid_maxrequests', true, - 1); $ssl_redirect = $this->getParam('ssl_redirect', true, 0); $letsencrypt = $this->getParam('letsencrypt', true, 0); - $p_ssl_ipandports = $this->getParam('ssl_ipandport', true, array()); + $p_ssl_ipandports = $this->getParam('ssl_ipandport', true, explode(',', Settings::Get('system.defaultsslip'))); $http2 = $this->getParam('http2', true, 0); $hsts_maxage = $this->getParam('hsts_maxage', true, 0); $hsts_sub = $this->getParam('hsts_sub', true, 0); @@ -312,7 +312,7 @@ class Domains extends ApiCommand implements ResourceEntity } if (empty($p_ipandports)) { - throw new Exception("No IPs given, unable to add domain (no default IPs set?", 406); + throw new Exception("No IPs given, unable to add domain (no default IPs set?)", 406); } $ipandports = array(); diff --git a/lib/classes/api/commands/class.IpsAndPorts.php b/lib/classes/api/commands/class.IpsAndPorts.php index 89aeb596..596a0a42 100644 --- a/lib/classes/api/commands/class.IpsAndPorts.php +++ b/lib/classes/api/commands/class.IpsAndPorts.php @@ -370,7 +370,7 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity ), true, true); if ($result_checkdomain['id'] == '') { - if (! in_array($result['id'], explode(',', Settings::Get('system.defaultip')))) { + if (! in_array($result['id'], explode(',', Settings::Get('system.defaultip'))) && ! in_array($result['id'], explode(',', Settings::Get('system.defaultsslip')))) { $result_sameipotherport_stmt = Database::prepare(" SELECT `id` FROM `" . TABLE_PANEL_IPSANDPORTS . "` diff --git a/lib/classes/integrity/class.IntegrityCheck.php b/lib/classes/integrity/class.IntegrityCheck.php index 1f8a3fb7..bc0a7dad 100644 --- a/lib/classes/integrity/class.IntegrityCheck.php +++ b/lib/classes/integrity/class.IntegrityCheck.php @@ -126,10 +126,12 @@ class IntegrityCheck { // Cache all IPs the admins have assigned $adm_stmt = Database::prepare("SELECT `adminid`, `ip` FROM `" . TABLE_PANEL_ADMINS . "` ORDER BY `adminid` ASC"); Database::pexecute($adm_stmt); + $default_ips = explode(',', Settings::Get('system.defaultip')); + $default_ssl_ips = explode(',', Settings::Get('system.defaultsslip')); while ($row = $adm_stmt->fetch(PDO::FETCH_ASSOC)) { if ($row['ip'] < 0 || is_null($row['ip']) || empty($row['ip'])) { // Admin uses default-IP - $admips[$row['adminid']] = explode(',', Settings::Get('system.defaultip')); + $admips[$row['adminid']] = array_merge($default_ips, $default_ssl_ips); } else { $admips[$row['adminid']] = array($row['ip']); } diff --git a/lib/classes/settings/class.SImExporter.php b/lib/classes/settings/class.SImExporter.php index 10e8aa32..366efc64 100644 --- a/lib/classes/settings/class.SImExporter.php +++ b/lib/classes/settings/class.SImExporter.php @@ -48,6 +48,7 @@ class SImExporter 'system.mysql_access_host', 'system.lastcronrun', 'system.defaultip', + 'system.defaultsslip'. 'system.last_tasks_run', 'system.last_archive_run', 'system.leprivatekey', diff --git a/lib/functions/froxlor/function.getIpPortCombinations.php b/lib/functions/froxlor/function.getIpPortCombinations.php index 5d67a4dd..f0043a74 100644 --- a/lib/functions/froxlor/function.getIpPortCombinations.php +++ b/lib/functions/froxlor/function.getIpPortCombinations.php @@ -62,3 +62,7 @@ function getIpPortCombinations($ssl = false) { return $system_ipaddress_array; } + +function getSslIpPortCombinations() { + return getIpPortCombinations(true); +} diff --git a/lib/functions/settings/function.storeSettingDefaultIp.php b/lib/functions/settings/function.storeSettingDefaultIp.php index 22d08d66..5a8994c2 100644 --- a/lib/functions/settings/function.storeSettingDefaultIp.php +++ b/lib/functions/settings/function.storeSettingDefaultIp.php @@ -67,3 +67,55 @@ function storeSettingDefaultIp($fieldname, $fielddata, $newfieldvalue) { return $returnvalue; } + +function storeSettingDefaultSslIp($fieldname, $fielddata, $newfieldvalue) { + $defaultips_old = Settings::Get('system.defaultsslip'); + + $returnvalue = storeSettingField($fieldname, $fielddata, $newfieldvalue); + + if ($returnvalue !== false + && is_array($fielddata) + && isset($fielddata['settinggroup']) + && $fielddata['settinggroup'] == 'system' + && isset($fielddata['varname']) + && $fielddata['varname'] == 'defaultsslip' + ) { + + $customerstddomains_result_stmt = Database::prepare(" + SELECT `standardsubdomain` FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `standardsubdomain` <> '0' + "); + Database::pexecute($customerstddomains_result_stmt); + + $ids = array(); + + while ($customerstddomains_row = $customerstddomains_result_stmt->fetch(PDO::FETCH_ASSOC)) { + $ids[] = (int)$customerstddomains_row['standardsubdomain']; + } + + if (count($ids) > 0) { + $defaultips_new = explode(',', $newfieldvalue); + + // Delete the existing mappings linking to default IPs + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_DOMAINTOIP . "` + WHERE `id_domain` IN (" . implode(', ', $ids) . ") + AND `id_ipandports` IN (" . $defaultips_old . ", " . $newfieldvalue . ") + "); + Database::pexecute($del_stmt); + + // Insert the new mappings + $ins_stmt = Database::prepare(" + INSERT INTO `" . TABLE_DOMAINTOIP . "` + SET `id_domain` = :domainid, `id_ipandports` = :ipandportid + "); + + foreach ($ids as $id) { + foreach ($defaultips_new as $defaultip_new) { + Database::pexecute($ins_stmt, array('domainid' => $id, 'ipandportid' => $defaultip_new)); + } + } + } + } + + return $returnvalue; +} diff --git a/lib/version.inc.php b/lib/version.inc.php index 8870fe02..29fa7c32 100644 --- a/lib/version.inc.php +++ b/lib/version.inc.php @@ -19,7 +19,7 @@ $version = '0.10.0'; // Database version (YYYYMMDDC where C is a daily counter) -$dbversion = '201802150'; +$dbversion = '201802210'; // Distribution branding-tag (used for Debian etc.) $branding = ''; diff --git a/lng/english.lng.php b/lng/english.lng.php index a5d33367..f9ed0d54 100644 --- a/lng/english.lng.php +++ b/lng/english.lng.php @@ -436,6 +436,8 @@ $lng['error']['webftpiswrong'] = 'The WebFTP-link is not a valid link.'; $lng['domains']['hasaliasdomains'] = 'Has alias domain(s)'; $lng['serversettings']['defaultip']['title'] = 'Default IP/Port'; $lng['serversettings']['defaultip']['description'] = 'Select all IP-addresses you want to use as default for new domains'; +$lng['serversettings']['defaultsslip']['title'] = 'Default SSL IP/Port'; +$lng['serversettings']['defaultsslip']['description'] = 'Select all ssl-enabled IP-addresses you want to use as default for new domains'; $lng['domains']['statstics'] = 'Usage Statistics'; $lng['panel']['ascending'] = 'ascending'; $lng['panel']['descending'] = 'descending'; diff --git a/lng/german.lng.php b/lng/german.lng.php index 0924c1ac..a4567f2a 100644 --- a/lng/german.lng.php +++ b/lng/german.lng.php @@ -429,8 +429,10 @@ $lng['error']['phpmyadminiswrong'] = 'Die "phpMyAdmin-URL" ist keine gültige UR $lng['error']['webmailiswrong'] = 'Die "Webmail-URL" ist keine gültige URL.'; $lng['error']['webftpiswrong'] = 'Die "WebFTP-URL" ist keine gültige URL.'; $lng['domains']['hasaliasdomains'] = 'Hat Aliasdomain(s)'; -$lng['serversettings']['defaultip']['title'] = 'Standard-IP/Port'; +$lng['serversettings']['defaultip']['title'] = 'Standard IP/Port'; $lng['serversettings']['defaultip']['description'] = 'Welche IP/Port-Kombination sollen standardmäßig verwendet werden?'; +$lng['serversettings']['defaultsslip']['title'] = 'Standard SSL IP/Port'; +$lng['serversettings']['defaultsslip']['description'] = 'Welche ssl-fähigen IP/Port-Kombination sollen standardmäßig verwendet werden?'; $lng['domains']['statstics'] = 'Statistiken'; $lng['panel']['ascending'] = 'aufsteigend'; $lng['panel']['descending'] = 'absteigend'; From b56414ed0e54633cce72fe8b069637a09c8d8b82 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Wed, 21 Feb 2018 12:16:50 +0100 Subject: [PATCH 036/746] add sql-query of last statement to sql-debug for debugging purposes; fix default-ssl-ip setting and allow 'none' value Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/database/class.Database.php | 6 ++-- .../function.getIpPortCombinations.php | 3 +- .../function.storeSettingDefaultIp.php | 30 +++++++++++++------ lng/english.lng.php | 1 + lng/german.lng.php | 1 + 5 files changed, 29 insertions(+), 12 deletions(-) diff --git a/lib/classes/database/class.Database.php b/lib/classes/database/class.Database.php index 7f6c7d46..350e71e3 100644 --- a/lib/classes/database/class.Database.php +++ b/lib/classes/database/class.Database.php @@ -71,7 +71,7 @@ class Database { try { $stmt->execute($params); } catch (PDOException $e) { - self::_showerror($e, $showerror, $json_response); + self::_showerror($e, $showerror, $json_response, $stmt); } } @@ -309,7 +309,7 @@ class Database { * @param PDOException $error * @param bool $showerror if set to false, the error will be logged but we go on */ - private static function _showerror($error, $showerror = true, $json_response = false) { + private static function _showerror($error, $showerror = true, $json_response = false, PDOStatement $stmt = null) { global $userinfo, $theme, $linker; // include userdata.inc.php @@ -374,6 +374,8 @@ class Database { if ($showerror) { if (empty($sql['debug'])) { $error_trace = ''; + } elseif (!is_null($stmt)) { + $error_trace .= "

".$stmt->queryString; } // fallback diff --git a/lib/functions/froxlor/function.getIpPortCombinations.php b/lib/functions/froxlor/function.getIpPortCombinations.php index f0043a74..1a0c75f6 100644 --- a/lib/functions/froxlor/function.getIpPortCombinations.php +++ b/lib/functions/froxlor/function.getIpPortCombinations.php @@ -64,5 +64,6 @@ function getIpPortCombinations($ssl = false) { } function getSslIpPortCombinations() { - return getIpPortCombinations(true); + global $lng; + return array('' => $lng['panel']['none_value']) + getIpPortCombinations(true); } diff --git a/lib/functions/settings/function.storeSettingDefaultIp.php b/lib/functions/settings/function.storeSettingDefaultIp.php index 5a8994c2..a3ca6b1f 100644 --- a/lib/functions/settings/function.storeSettingDefaultIp.php +++ b/lib/functions/settings/function.storeSettingDefaultIp.php @@ -95,23 +95,35 @@ function storeSettingDefaultSslIp($fieldname, $fielddata, $newfieldvalue) { if (count($ids) > 0) { $defaultips_new = explode(',', $newfieldvalue); + if (!empty($defaultips_old) && !empty($newfieldvalue)) + { + $in_value = $defaultips_old . ", " . $newfieldvalue; + } elseif (!empty($defaultips_old) && empty($newfieldvalue)) + { + $in_value = $defaultips_old; + } else { + $in_value = $newfieldvalue; + } + // Delete the existing mappings linking to default IPs $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` IN (" . implode(', ', $ids) . ") - AND `id_ipandports` IN (" . $defaultips_old . ", " . $newfieldvalue . ") + AND `id_ipandports` IN (" . $in_value . ") "); Database::pexecute($del_stmt); - // Insert the new mappings - $ins_stmt = Database::prepare(" - INSERT INTO `" . TABLE_DOMAINTOIP . "` - SET `id_domain` = :domainid, `id_ipandports` = :ipandportid - "); + if (count($defaultips_new) > 0) { + // Insert the new mappings + $ins_stmt = Database::prepare(" + INSERT INTO `" . TABLE_DOMAINTOIP . "` + SET `id_domain` = :domainid, `id_ipandports` = :ipandportid + "); - foreach ($ids as $id) { - foreach ($defaultips_new as $defaultip_new) { - Database::pexecute($ins_stmt, array('domainid' => $id, 'ipandportid' => $defaultip_new)); + foreach ($ids as $id) { + foreach ($defaultips_new as $defaultip_new) { + Database::pexecute($ins_stmt, array('domainid' => $id, 'ipandportid' => $defaultip_new)); + } } } } diff --git a/lng/english.lng.php b/lng/english.lng.php index f9ed0d54..8adc8a40 100644 --- a/lng/english.lng.php +++ b/lng/english.lng.php @@ -2119,3 +2119,4 @@ $lng['admin']['plans']['use_plan'] = 'Apply plan'; $lng['question']['plan_reallydelete'] = 'Do you really want to delete the hosting plan %s?'; $lng['admin']['notryfiles']['title'] = 'No autogenerated try_files'; $lng['admin']['notryfiles']['description'] = 'Say yes here if you want to specify a custom try_files directive in specialsettings (needed for some wordpress plugins for example).'; +$lng['panel']['none_value'] = 'None'; diff --git a/lng/german.lng.php b/lng/german.lng.php index a4567f2a..888303b1 100644 --- a/lng/german.lng.php +++ b/lng/german.lng.php @@ -1769,3 +1769,4 @@ $lng['admin']['plans']['use_plan'] = 'Plan übernehmen'; $lng['question']['plan_reallydelete'] = 'Wollen Sie den Hosting-Plan "%s" wirklich löschen?'; $lng['admin']['notryfiles']['title'] = 'Keine generierte try_files Anweisung'; $lng['admin']['notryfiles']['description'] = 'Wähle "Ja", wenn eine eigene try_files Direktive in den "eigenen Vhost Einstellungen" angegeben werden soll (z.B. nötig für manche Wordpress Plugins).'; +$lng['panel']['none_value'] = 'Keine'; From 8310e8554b74c9ea4359b7ae82d9a4cfd4f1ff08 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Wed, 21 Feb 2018 13:14:54 +0100 Subject: [PATCH 037/746] enable usage of 'domainname' as an alternative to 'id' for Domains::get() and Domains::delete(); enable usage of 'loginname' as an alternative to 'id' for Customers::get() and Customers::delete() Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Customers.php | 39 ++++++++++++----- lib/classes/api/commands/class.Domains.php | 45 +++++++++++++++----- 2 files changed, 64 insertions(+), 20 deletions(-) diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index ecef61f4..852fafe9 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -52,10 +52,12 @@ class Customers extends ApiCommand implements ResourceEntity } /** - * return a customer entry by id + * return a customer entry by either id or loginname * * @param int $id - * customer-id + * optional, the customer-id + * @param string $loginname + * optional, the loginname * * @throws Exception * @return array @@ -63,13 +65,20 @@ class Customers extends ApiCommand implements ResourceEntity public function get() { if ($this->isAdmin()) { - $id = $this->getParam('id'); + $id = $this->getParam('id', true, 0); + $ln_optional = ($id <= 0 ? false : true); + $loginname = $this->getParam('loginname', $ln_optional, ''); + + if ($id <= 0 && empty($loginname)) { + throw new Exception("Either 'id' or 'loginname' parameter must be given", 406); + } + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] get customer #" . $id); $result_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_CUSTOMERS . "` - WHERE `customerid` = :id" . ($this->getUserDetail('customers_see_all') ? '' : " AND `adminid` = :adminid")); + WHERE ".($id > 0 ? "`customerid` = :idln" : "`loginname` = :idln") . ($this->getUserDetail('customers_see_all') ? '' : " AND `adminid` = :adminid")); $params = array( - 'id' => $id + 'idln' => ($id <= 0 ? $loginname : $id) ); if ($this->getUserDetail('customers_see_all') == '0') { $params['adminid'] = $this->getUserDetail('adminid'); @@ -1096,10 +1105,12 @@ class Customers extends ApiCommand implements ResourceEntity } /** - * delete a customer entry by id + * delete a customer entry by either id or loginname * * @param int $id - * customer-id + * optional, the customer-id + * @param string $loginname + * optional, the loginname * @param bool $delete_userfiles * optional, default false * @@ -1109,13 +1120,21 @@ class Customers extends ApiCommand implements ResourceEntity public function delete() { if ($this->isAdmin()) { - $id = $this->getParam('id'); + $id = $this->getParam('id', true, 0); + $ln_optional = ($id <= 0 ? false : true); + $loginname = $this->getParam('loginname', $ln_optional, ''); $delete_userfiles = $this->getParam('delete_userfiles', true, 0); - + + if ($id <= 0 && empty($loginname)) { + throw new Exception("Either 'id' or 'loginname' parameter must be given", 406); + } + $json_result = Customers::getLocal($this->getUserData(), array( - 'id' => $id + 'id' => $id, + 'loginname' => $loginname ))->get(); $result = json_decode($json_result, true)['data']; + $id = $result['customerid']; // @fixme use Databases-ApiCommand later $databases_stmt = Database::prepare(" diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index 6d4085d3..90084e20 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -52,10 +52,12 @@ class Domains extends ApiCommand implements ResourceEntity } /** - * return a domain entry by id + * return a domain entry by either id or domainname * * @param int $id - * domain-id + * optional, the domain-id + * @param string $domainname + * optional, the domainname * @param boolean $no_std_subdomain * optional, default false * @@ -65,17 +67,30 @@ class Domains extends ApiCommand implements ResourceEntity public function get() { if ($this->isAdmin()) { - $id = $this->getParam('id'); + $id = $this->getParam('id', true, 0); + $dn_optional = ($id <= 0 ? false : true); + $domainname = $this->getParam('domainname', $dn_optional, ''); $no_std_subdomain = $this->getParam('no_std_subdomain', true, false); $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] get domain #" . $id); + + if ($id <= 0 && empty($domainname)) { + throw new Exception("Either 'id' or 'domainname' parameter must be given", 406); + } + + // convert possible idn domain to punycode + if (substr($domainname, 0, 4) != 'xn--') { + $idna_convert = new idna_convert_wrapper(); + $domainname = $idna_convert->encode($domainname); + } + $result_stmt = Database::prepare(" SELECT `d`.*, `c`.`customerid` FROM `" . TABLE_PANEL_DOMAINS . "` `d` LEFT JOIN `" . TABLE_PANEL_CUSTOMERS . "` `c` USING(`customerid`) WHERE `d`.`parentdomainid` = '0' - AND `d`.`id` = :id" . ($no_std_subdomain ? ' AND `d.`id` <> `c`.`standardsubdomain`' : '') . ($this->getUserDetail('customers_see_all') ? '' : " AND `d`.`adminid` = :adminid")); + AND ".($id > 0 ? "`d`.`id` = :iddn" : "`d`.`domain` = :iddn") . ($no_std_subdomain ? ' AND `d.`id` <> `c`.`standardsubdomain`' : '') . ($this->getUserDetail('customers_see_all') ? '' : " AND `d`.`adminid` = :adminid")); $params = array( - 'id' => $id + 'iddn' => ($id <= 0 ? $domainname : $id) ); if ($this->getUserDetail('customers_see_all') == '0') { $params['adminid'] = $this->getUserDetail('adminid'); @@ -1539,10 +1554,12 @@ class Domains extends ApiCommand implements ResourceEntity } /** - * delete a domain entry by id + * delete a domain entry by either id or domainname * * @param int $id - * domain-id + * optional, the domain-id + * @param string $domainname + * optional, the domainname * @param bool $delete_mainsubdomains * optional, remove also domains that are subdomains of this domain but added as main domains; default false * @param bool $is_stdsubdomain @@ -1554,14 +1571,22 @@ class Domains extends ApiCommand implements ResourceEntity public function delete() { if ($this->isAdmin()) { - $id = $this->getParam('id'); + $id = $this->getParam('id', true, 0); + $dn_optional = ($id <= 0 ? false : true); + $domainname = $this->getParam('domainname', $dn_optional, ''); $is_stdsubdomain = $this->getParam('is_stdsubdomain', true, 0); $remove_subbutmain_domains = $this->getParam('delete_mainsubdomains', true, 0); - + + if ($id <= 0 && empty($domainname)) { + throw new Exception("Either 'id' or 'domainname' parameter must be given", 406); + } + $json_result = Domains::getLocal($this->getUserData(), array( - 'id' => $id + 'id' => $id, + 'domainname' => $domainname ))->get(); $result = json_decode($json_result, true)['data']; + $id = $result['id']; // check for deletion of main-domains which are logically subdomains, #329 $rsd_sql = ''; From 689ca853c3204d355cb80f045d53b71dac00fc3b Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Wed, 21 Feb 2018 14:38:27 +0100 Subject: [PATCH 038/746] minor fixes in Customers and Domains ApiCommands, added list() and get() for Admins-ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Admins.php | 123 +++++++++++++++++++ lib/classes/api/commands/class.Customers.php | 24 ++-- lib/classes/api/commands/class.Domains.php | 19 +-- 3 files changed, 146 insertions(+), 20 deletions(-) create mode 100644 lib/classes/api/commands/class.Admins.php diff --git a/lib/classes/api/commands/class.Admins.php b/lib/classes/api/commands/class.Admins.php new file mode 100644 index 00000000..65d53782 --- /dev/null +++ b/lib/classes/api/commands/class.Admins.php @@ -0,0 +1,123 @@ + (2010-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package Panel + * + */ +class Admins extends ApiCommand implements ResourceEntity +{ + + /** + * lists all admin entries + * + * @return array count|list + */ + public function list() + { + if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] list admins"); + $result_stmt = Database::prepare(" + SELECT * + FROM `" . TABLE_PANEL_ADMINS . "` + ORDER BY `loginname` ASC + "); + Database::pexecute($result_stmt, null, true, true); + $result = array(); + while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { + $result[] = $row; + } + return $this->response(200, "successfull", array( + 'count' => count($result), + 'list' => $result + )); + } + throw new Exception("Not allowed to execute given command.", 403); + } + + /** + * return an admin entry by either id or loginname + * + * @param int $id + * optional, the admin-id + * @param string $loginname + * optional, the loginname + * + * @throws Exception + * @return array + */ + public function get() + { + $id = $this->getParam('id', true, 0); + $ln_optional = ($id <= 0 ? false : true); + $loginname = $this->getParam('loginname', $ln_optional, ''); + + if ($id <= 0 && empty($loginname)) { + throw new Exception("Either 'id' or 'loginname' parameter must be given", 406); + } + + if ($this->isAdmin() && ($this->getUserDetail('change_serversettings') == 1 || ($this->getUserDetail('adminid') == $id || $this->getUserDetail('loginname') == $loginname))) { + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_ADMINS . "` + WHERE " . ($id > 0 ? "`adminid` = :idln" : "`loginname` = :idln")); + $params = array( + 'idln' => ($id <= 0 ? $loginname : $id) + ); + $result = Database::pexecute_first($result_stmt, $params, true, true); + if ($result) { + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] get admin '" . $result['loginname'] . "'"); + return $this->response(200, "successfull", $result); + } + $key = ($id > 0 ? "id #" . $id : "loginname '" . $loginname . "'"); + throw new Exception("Admin with " . $key . " could not be found", 404); + } + throw new Exception("Not allowed to execute given command.", 403); + } + + public function add() + { + } + + public function update() + { + } + + /** + * delete a admin entry by either id or loginname + * + * @param int $id + * optional, the customer-id + * @param string $loginname + * optional, the loginname + * @param bool $delete_userfiles + * optional, default false + * + * @throws Exception + * @return array + */ + public function delete() + { + } + + /** + * unlock a locked admin by id + * + * @param int $id + * customer-id + * + * @throws Exception + * @return array + */ + public function unlock() + { + } +} diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index 852fafe9..015a09c5 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -27,10 +27,11 @@ class Customers extends ApiCommand implements ResourceEntity if ($this->isAdmin()) { $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] list customers"); $result_stmt = Database::prepare(" - SELECT `c`.*, `a`.`loginname` AS `adminname` - FROM `" . TABLE_PANEL_CUSTOMERS . "` `c`, `" . TABLE_PANEL_ADMINS . "` `a` - WHERE " . ($this->getUserDetail('customers_see_all') ? '' : " `c`.`adminid` = :adminid AND ") . " - `c`.`adminid` = `a`.`adminid` + SELECT `c`.*, `a`.`loginname` AS `adminname` + FROM `" . TABLE_PANEL_CUSTOMERS . "` `c`, `" . TABLE_PANEL_ADMINS . "` `a` + WHERE " . ($this->getUserDetail('customers_see_all') ? '' : " `c`.`adminid` = :adminid AND ") . " + `c`.`adminid` = `a`.`adminid` + ORDER BY `c`.`loginname` ASC "); $params = array(); if ($this->getUserDetail('customers_see_all') == '0') { @@ -68,15 +69,14 @@ class Customers extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); $loginname = $this->getParam('loginname', $ln_optional, ''); - + if ($id <= 0 && empty($loginname)) { throw new Exception("Either 'id' or 'loginname' parameter must be given", 406); } - - $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] get customer #" . $id); + $result_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_CUSTOMERS . "` - WHERE ".($id > 0 ? "`customerid` = :idln" : "`loginname` = :idln") . ($this->getUserDetail('customers_see_all') ? '' : " AND `adminid` = :adminid")); + WHERE " . ($id > 0 ? "`customerid` = :idln" : "`loginname` = :idln") . ($this->getUserDetail('customers_see_all') ? '' : " AND `adminid` = :adminid")); $params = array( 'idln' => ($id <= 0 ? $loginname : $id) ); @@ -85,9 +85,11 @@ class Customers extends ApiCommand implements ResourceEntity } $result = Database::pexecute_first($result_stmt, $params, true, true); if ($result) { + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] get customer '" . $result['loginname'] . "'"); return $this->response(200, "successfull", $result); } - throw new Exception("Customer with id #" . $id . " could not be found", 404); + $key = ($id > 0 ? "id #" . $id : "loginname '" . $loginname . "'"); + throw new Exception("Customer with " . $key . " could not be found", 404); } throw new Exception("Not allowed to execute given command.", 403); } @@ -1124,11 +1126,11 @@ class Customers extends ApiCommand implements ResourceEntity $ln_optional = ($id <= 0 ? false : true); $loginname = $this->getParam('loginname', $ln_optional, ''); $delete_userfiles = $this->getParam('delete_userfiles', true, 0); - + if ($id <= 0 && empty($loginname)) { throw new Exception("Either 'id' or 'loginname' parameter must be given", 406); } - + $json_result = Customers::getLocal($this->getUserData(), array( 'id' => $id, 'loginname' => $loginname diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index 90084e20..0220095e 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -72,23 +72,23 @@ class Domains extends ApiCommand implements ResourceEntity $domainname = $this->getParam('domainname', $dn_optional, ''); $no_std_subdomain = $this->getParam('no_std_subdomain', true, false); $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] get domain #" . $id); - + if ($id <= 0 && empty($domainname)) { throw new Exception("Either 'id' or 'domainname' parameter must be given", 406); } - + // convert possible idn domain to punycode if (substr($domainname, 0, 4) != 'xn--') { $idna_convert = new idna_convert_wrapper(); $domainname = $idna_convert->encode($domainname); } - + $result_stmt = Database::prepare(" SELECT `d`.*, `c`.`customerid` FROM `" . TABLE_PANEL_DOMAINS . "` `d` LEFT JOIN `" . TABLE_PANEL_CUSTOMERS . "` `c` USING(`customerid`) WHERE `d`.`parentdomainid` = '0' - AND ".($id > 0 ? "`d`.`id` = :iddn" : "`d`.`domain` = :iddn") . ($no_std_subdomain ? ' AND `d.`id` <> `c`.`standardsubdomain`' : '') . ($this->getUserDetail('customers_see_all') ? '' : " AND `d`.`adminid` = :adminid")); + AND " . ($id > 0 ? "`d`.`id` = :iddn" : "`d`.`domain` = :iddn") . ($no_std_subdomain ? ' AND `d.`id` <> `c`.`standardsubdomain`' : '') . ($this->getUserDetail('customers_see_all') ? '' : " AND `d`.`adminid` = :adminid")); $params = array( 'iddn' => ($id <= 0 ? $domainname : $id) ); @@ -99,7 +99,8 @@ class Domains extends ApiCommand implements ResourceEntity if ($result) { return $this->response(200, "successfull", $result); } - throw new Exception("Domain with id #" . $id . " could not be found", 404); + $key = ($id > 0 ? "id #" . $id : "domainname '" . $domainname . "'"); + throw new Exception("Domain with " . $key . " could not be found", 404); } throw new Exception("Not allowed to execute given command.", 403); } @@ -325,11 +326,11 @@ class Domains extends ApiCommand implements ResourceEntity $additional_ip_condition = ''; $aip_param = array(); } - + if (empty($p_ipandports)) { throw new Exception("No IPs given, unable to add domain (no default IPs set?)", 406); } - + $ipandports = array(); if (! empty($p_ipandport) && ! is_array($p_ipandports)) { $p_ipandports = unserialize($p_ipandports); @@ -1576,11 +1577,11 @@ class Domains extends ApiCommand implements ResourceEntity $domainname = $this->getParam('domainname', $dn_optional, ''); $is_stdsubdomain = $this->getParam('is_stdsubdomain', true, 0); $remove_subbutmain_domains = $this->getParam('delete_mainsubdomains', true, 0); - + if ($id <= 0 && empty($domainname)) { throw new Exception("Either 'id' or 'domainname' parameter must be given", 406); } - + $json_result = Domains::getLocal($this->getUserData(), array( 'id' => $id, 'domainname' => $domainname From f8fe4be3efff60de245ce6f6d4309347e198ba7d Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Wed, 21 Feb 2018 14:52:23 +0100 Subject: [PATCH 039/746] fix parameters for checkboxes when passed via webinterface Signed-off-by: Michael Kaufmann (d00p) --- templates/Sparkle/formfields/bool.tpl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/templates/Sparkle/formfields/bool.tpl b/templates/Sparkle/formfields/bool.tpl index 7f01b3d1..3ae7e894 100644 --- a/templates/Sparkle/formfields/bool.tpl +++ b/templates/Sparkle/formfields/bool.tpl @@ -1,4 +1,7 @@ {$label} - disabled="disabled" type="checkbox" name="{$fieldname}" value="1" checked="checked" /> + + + disabled="disabled" type="checkbox" name="{$fieldname}" value="1" checked="checked" /> + From c93e2678f755714bc857a53f157b198d57f03df7 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 22 Feb 2018 10:47:14 +0100 Subject: [PATCH 040/746] make Customers::update() also work with loginname as an alternative to the id Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Admins.php | 39 ++++++++++++++++++-- lib/classes/api/commands/class.Customers.php | 34 +++++++++++++---- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/lib/classes/api/commands/class.Admins.php b/lib/classes/api/commands/class.Admins.php index 65d53782..9d23534f 100644 --- a/lib/classes/api/commands/class.Admins.php +++ b/lib/classes/api/commands/class.Admins.php @@ -95,7 +95,7 @@ class Admins extends ApiCommand implements ResourceEntity * delete a admin entry by either id or loginname * * @param int $id - * optional, the customer-id + * optional, the admin-id * @param string $loginname * optional, the loginname * @param bool $delete_userfiles @@ -109,15 +109,46 @@ class Admins extends ApiCommand implements ResourceEntity } /** - * unlock a locked admin by id + * unlock a locked admin by either id or loginname * * @param int $id - * customer-id - * + * optional, the admin-id + * @param string $loginname + * optional, the loginname + * * @throws Exception * @return array */ public function unlock() { + if ($this->isAdmin()) { + $id = $this->getParam('id', true, 0); + $ln_optional = ($id <= 0 ? false : true); + $loginname = $this->getParam('loginname', $ln_optional, ''); + + if ($id <= 0 && empty($loginname)) { + throw new Exception("Either 'id' or 'loginname' parameter must be given", 406); + } + + $json_result = Admins::getLocal($this->getUserData(), array( + 'id' => $id, + 'loginname' => $loginname + ))->get(); + $result = json_decode($json_result, true)['data']; + $id = $result['adminid']; + + $result_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_ADMINS . "` SET + `loginfail_count` = '0' + WHERE `adminid`= :id + "); + Database::pexecute($result_stmt, array( + 'id' => $id + ), true, true); + + $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] unlocked admin '" . $result['loginname'] . "'"); + return $this->response(200, "successfull", $result); + } + throw new Exception("Not allowed to execute given command.", 403); } } diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index 015a09c5..4aeadf28 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -646,12 +646,20 @@ class Customers extends ApiCommand implements ResourceEntity public function update() { if ($this->isAdmin()) { - $id = $this->getParam('id'); - + $id = $this->getParam('id', true, 0); + $ln_optional = ($id <= 0 ? false : true); + $loginname = $this->getParam('loginname', $ln_optional, ''); + + if ($id <= 0 && empty($loginname)) { + throw new Exception("Either 'id' or 'loginname' parameter must be given", 406); + } + $json_result = Customers::getLocal($this->getUserData(), array( - 'id' => $id + 'id' => $id, + 'loginname' => $loginname ))->get(); $result = json_decode($json_result, true)['data']; + $id = $result['customerid']; // parameters $move_to_admin = intval_ressource($this->getParam('move_to_admin', true, 0)); @@ -1354,10 +1362,12 @@ class Customers extends ApiCommand implements ResourceEntity } /** - * unlock a locked customer by id + * unlock a locked customer by either id or loginname * * @param int $id - * customer-id + * optional, the customer-id + * @param string $loginname + * optional, the loginname * * @throws Exception * @return array @@ -1365,12 +1375,20 @@ class Customers extends ApiCommand implements ResourceEntity public function unlock() { if ($this->isAdmin()) { - $id = $this->getParam('id'); - + $id = $this->getParam('id', true, 0); + $ln_optional = ($id <= 0 ? false : true); + $loginname = $this->getParam('loginname', $ln_optional, ''); + + if ($id <= 0 && empty($loginname)) { + throw new Exception("Either 'id' or 'loginname' parameter must be given", 406); + } + $json_result = Customers::getLocal($this->getUserData(), array( - 'id' => $id + 'id' => $id, + 'loginname' => $loginname ))->get(); $result = json_decode($json_result, true)['data']; + $id = $result['customerid']; $result_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET From 332e29be24b6e228c0f4efec8ae4d4d5a4657413 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 22 Feb 2018 11:16:55 +0100 Subject: [PATCH 041/746] lots of phpdoc; fix Customers::update() Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/abstract.ApiCommand.php | 93 +++++++++++++++++++ lib/classes/api/api_includes.inc.php | 16 ++++ lib/classes/api/commands/class.Admins.php | 3 +- lib/classes/api/commands/class.Customers.php | 30 +++++- lib/classes/api/commands/class.Domains.php | 15 ++- lib/classes/api/commands/class.FpmDaemons.php | 20 +++- lib/classes/api/commands/class.Froxlor.php | 9 +- .../api/commands/class.IpsAndPorts.php | 3 +- .../api/commands/class.PhpSettings.php | 18 +++- lib/classes/api/interface.ResourceEntity.php | 15 +++ 10 files changed, 211 insertions(+), 11 deletions(-) diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index abf2eda8..1dafc054 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -1,20 +1,76 @@ (2010-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package API + * @since 0.10.0 + * + */ abstract class ApiCommand { + /** + * debug flag + * + * @var boolean + */ private $debug = true; + /** + * is admin flag + * + * @var boolean + */ private $is_admin = false; + /** + * internal user data array + * + * @var array + */ private $user_data = null; + /** + * logger interface + * + * @var FroxlorLogger + */ private $logger = null; + /** + * mail interface + * + * @var PHPMailer + */ private $mail = null; + /** + * array of parameters passed to the command + * + * @var array + */ private $cmd_params = null; + /** + * + * @param array $header + * optional, passed via API + * @param array $params + * optional, array of parameters (var=>value) for the command + * @param array $userinfo + * optional, passed via WebInterface (instead of $header) + * + * @throws Exception + */ public function __construct($header = null, $params = null, $userinfo = null) { global $lng; @@ -38,6 +94,10 @@ abstract class ApiCommand } } + /** + * initialize global $lng variable to have + * localized strings available for the ApiCommands + */ private function initLang() { global $lng; @@ -77,6 +137,9 @@ abstract class ApiCommand include_once makeSecurePath(FROXLOR_INSTALL_DIR . '/lng/lng_references.php'); } + /** + * initialize mail interface so an API wide mail-object is available + */ private function initMail() { /** @@ -108,6 +171,18 @@ abstract class ApiCommand } } + /** + * returns an instance of the wanted ApiCommand (e.g. + * Customers, Domains, etc); + * this is used widely in the WebInterface + * + * @param array $userinfo + * array of user-data + * @param array $params + * array of parameters for the command + * + * @return ApiCommand + */ public static function getLocal($userinfo = null, $params = null) { return new static(null, $params, $userinfo); @@ -276,6 +351,15 @@ abstract class ApiCommand return $this->mail; } + /** + * return api-compatible response in JSON format and send corresponding http-header + * + * @param int $status + * @param string $status_message + * @param mixed $data + * + * @return string json-encoded response message + */ protected function response($status, $status_message, $data = null) { header("HTTP/1.1 " . $status); @@ -288,6 +372,15 @@ abstract class ApiCommand return $json_response; } + /** + * read user data from database by api-request-header fields + * + * @param array $header + * api-request header + * + * @throws Exception + * @return boolean + */ private function readUserData($header = null) { $sel_stmt = Database::prepare("SELECT * FROM `api_keys` WHERE `apikey` = :ak AND `secret` = :as"); diff --git a/lib/classes/api/api_includes.inc.php b/lib/classes/api/api_includes.inc.php index 0e0eb0ad..f7446b51 100644 --- a/lib/classes/api/api_includes.inc.php +++ b/lib/classes/api/api_includes.inc.php @@ -1,4 +1,20 @@ (2010-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package API + * @since 0.10.0 + * + */ if (! defined('FROXLOR_INSTALL_DIR')) { define('FROXLOR_INSTALL_DIR', dirname(dirname(dirname(__DIR__)))); require_once FROXLOR_INSTALL_DIR . '/lib/tables.inc.php'; diff --git a/lib/classes/api/commands/class.Admins.php b/lib/classes/api/commands/class.Admins.php index 9d23534f..6a095e0e 100644 --- a/lib/classes/api/commands/class.Admins.php +++ b/lib/classes/api/commands/class.Admins.php @@ -11,7 +11,8 @@ * @copyright (c) the authors * @author Froxlor team (2010-) * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt - * @package Panel + * @package API + * @since 0.10.0 * */ class Admins extends ApiCommand implements ResourceEntity diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index 4aeadf28..bc2fb55b 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -11,7 +11,8 @@ * @copyright (c) the authors * @author Froxlor team (2010-) * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt - * @package Panel + * @package API + * @since 0.10.0 * */ class Customers extends ApiCommand implements ResourceEntity @@ -724,7 +725,32 @@ class Customers extends ApiCommand implements ResourceEntity if (Settings::Get('ticket.enabled') != '1') { $tickets = - 1; } - + + $diskspace = $diskspace * 1024; + $traffic = $traffic * 1024 * 1024; + + if (((($this->getUserDetail('diskspace_used') + $diskspace - $result['diskspace']) > $this->getUserDetail('diskspace')) && ($this->getUserDetail('diskspace') / 1024) != '-1') + || ((($this->getUserDetail('mysqls_used') + $mysqls - $result['mysqls']) > $this->getUserDetail('mysqls')) && $this->getUserDetail('mysqls') != '-1') + || ((($this->getUserDetail('emails_used') + $emails - $result['emails']) > $this->getUserDetail('emails')) && $this->getUserDetail('emails') != '-1') + || ((($this->getUserDetail('email_accounts_used') + $email_accounts - $result['email_accounts']) > $this->getUserDetail('email_accounts')) && $this->getUserDetail('email_accounts') != '-1') + || ((($this->getUserDetail('email_forwarders_used') + $email_forwarders - $result['email_forwarders']) > $this->getUserDetail('email_forwarders')) && $this->getUserDetail('email_forwarders') != '-1') + || ((($this->getUserDetail('email_quota_used') + $email_quota - $result['email_quota']) > $this->getUserDetail('email_quota')) && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') + || ((($this->getUserDetail('ftps_used') + $ftps - $result['ftps']) > $this->getUserDetail('ftps')) && $this->getUserDetail('ftps') != '-1') + || ((($this->getUserDetail('tickets_used') + $tickets - $result['tickets']) > $this->getUserDetail('tickets')) && $this->getUserDetail('tickets') != '-1') + || ((($this->getUserDetail('subdomains_used') + $subdomains - $result['subdomains']) > $this->getUserDetail('subdomains')) && $this->getUserDetail('subdomains') != '-1') + || (($diskspace / 1024) == '-1' && ($this->getUserDetail('diskspace') / 1024) != '-1') + || ($mysqls == '-1' && $this->getUserDetail('mysqls') != '-1') + || ($emails == '-1' && $this->getUserDetail('emails') != '-1') + || ($email_accounts == '-1' && $this->getUserDetail('email_accounts') != '-1') + || ($email_forwarders == '-1' && $this->getUserDetail('email_forwarders') != '-1') + || ($email_quota == '-1' && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') + || ($ftps == '-1' && $this->getUserDetail('ftps') != '-1') + || ($tickets == '-1' && $this->getUserDetail('tickets') != '-1') + || ($subdomains == '-1' && $this->getUserDetail('subdomains') != '-1') + ) { + standard_error('youcantallocatemorethanyouhave', '', true); + } + // Either $name and $firstname or the $company must be inserted if ($name == '' && $company == '') { standard_error(array( diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index 0220095e..92866870 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -11,7 +11,8 @@ * @copyright (c) the authors * @author Froxlor team (2010-) * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt - * @package Panel + * @package API + * @since 0.10.0 * */ class Domains extends ApiCommand implements ResourceEntity @@ -759,14 +760,22 @@ class Domains extends ApiCommand implements ResourceEntity if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { // parameters - $id = $this->getParam('id'); - + $id = $this->getParam('id', true, 0); + $dn_optional = ($id <= 0 ? false : true); + $domainname = $this->getParam('domainname', $dn_optional, ''); + + if ($id <= 0 && empty($domainname)) { + throw new Exception("Either 'id' or 'domainname' parameter must be given", 406); + } + // get requested domain $json_result = Domains::getLocal($this->getUserData(), array( 'id' => $id, + 'domainname' => $domainname, 'no_std_subdomain' => true ))->get(); $result = json_decode($json_result, true)['data']; + $id = $result['id']; // optional parameters $p_domain = $this->getParam('domain', true, $result['domain']); diff --git a/lib/classes/api/commands/class.FpmDaemons.php b/lib/classes/api/commands/class.FpmDaemons.php index 9d906593..205d6be8 100644 --- a/lib/classes/api/commands/class.FpmDaemons.php +++ b/lib/classes/api/commands/class.FpmDaemons.php @@ -11,14 +11,15 @@ * @copyright (c) the authors * @author Froxlor team (2010-) * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt - * @package Panel + * @package API + * @since 0.10.0 * */ class FpmDaemons extends ApiCommand implements ResourceEntity { /** - * lists all php-config entries + * lists all fpm-daemon entries * * @return array count|list */ @@ -66,6 +67,13 @@ class FpmDaemons extends ApiCommand implements ResourceEntity throw new Exception("Not allowed to execute given command.", 403); } + /** + * return a fpm-daemon entry by id + * + * @param int $id fpm-daemon-id + * + * @return array + */ public function get() { if ($this->isAdmin()) { @@ -238,6 +246,14 @@ class FpmDaemons extends ApiCommand implements ResourceEntity throw new Exception("Not allowed to execute given command.", 403); } + /** + * delete a fpm-daemon entry by id + * + * @param int $id fpm-daemon-id + * + * @throws Exception + * @return array + */ public function delete() { if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { diff --git a/lib/classes/api/commands/class.Froxlor.php b/lib/classes/api/commands/class.Froxlor.php index 4d6d1ea3..b005634c 100644 --- a/lib/classes/api/commands/class.Froxlor.php +++ b/lib/classes/api/commands/class.Froxlor.php @@ -11,12 +11,19 @@ * @copyright (c) the authors * @author Froxlor team (2010-) * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt - * @package Panel + * @package API + * @since 0.10.0 * */ class Froxlor extends ApiCommand { + /** + * checks whether there is a newer version of froxlor available + * + * @throws Exception + * @return string + */ public function checkUpdate() { global $version, $branding; diff --git a/lib/classes/api/commands/class.IpsAndPorts.php b/lib/classes/api/commands/class.IpsAndPorts.php index 596a0a42..fe0affa0 100644 --- a/lib/classes/api/commands/class.IpsAndPorts.php +++ b/lib/classes/api/commands/class.IpsAndPorts.php @@ -11,7 +11,8 @@ * @copyright (c) the authors * @author Froxlor team (2010-) * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt - * @package Panel + * @package API + * @since 0.10.0 * */ class IpsAndPorts extends ApiCommand implements ResourceEntity diff --git a/lib/classes/api/commands/class.PhpSettings.php b/lib/classes/api/commands/class.PhpSettings.php index 686da052..5507e454 100644 --- a/lib/classes/api/commands/class.PhpSettings.php +++ b/lib/classes/api/commands/class.PhpSettings.php @@ -11,7 +11,8 @@ * @copyright (c) the authors * @author Froxlor team (2010-) * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt - * @package Panel + * @package API + * @since 0.10.0 * */ class PhpSettings extends ApiCommand implements ResourceEntity @@ -100,6 +101,13 @@ class PhpSettings extends ApiCommand implements ResourceEntity throw new Exception("Not allowed to execute given command.", 403); } + /** + * return a php-config entry by id + * + * @param int $id php-settings-id + * + * @return array + */ public function get() { if ($this->isAdmin()) { @@ -320,6 +328,14 @@ class PhpSettings extends ApiCommand implements ResourceEntity throw new Exception("Not allowed to execute given command.", 403); } + /** + * delete a php-config entry by id + * + * @param int $id php-config-id + * + * @throws Exception + * @return array + */ public function delete() { if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { diff --git a/lib/classes/api/interface.ResourceEntity.php b/lib/classes/api/interface.ResourceEntity.php index 6361eb46..b10713a8 100644 --- a/lib/classes/api/interface.ResourceEntity.php +++ b/lib/classes/api/interface.ResourceEntity.php @@ -1,5 +1,20 @@ (2010-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package API + * @since 0.10.0 + * + */ interface ResourceEntity { From c1875132ef3da5b8f535a8c5d18cf34e8c701e25 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 22 Feb 2018 11:34:40 +0100 Subject: [PATCH 042/746] fix unchecked-checkbox value passed to API Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/output/class.htmlform.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/classes/output/class.htmlform.php b/lib/classes/output/class.htmlform.php index 6f1c4a3b..c7ded086 100644 --- a/lib/classes/output/class.htmlform.php +++ b/lib/classes/output/class.htmlform.php @@ -283,7 +283,12 @@ class htmlform } } } - $output .= ''; + $output .= ''; } return $output; From a468fe50df22fe82abd34a620a301d856ea4eac6 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 22 Feb 2018 11:42:13 +0100 Subject: [PATCH 043/746] filter deactivated users who want to use the API; fix error-output in Customers::update() Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/abstract.ApiCommand.php | 6 ++++++ lib/classes/api/commands/class.Customers.php | 12 ++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index 1dafc054..f499140b 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -86,6 +86,12 @@ abstract class ApiCommand } $this->logger = FroxlorLogger::getInstanceOf($this->user_data); + // check whether the user is deactivated + if ($this->getUserDetail('deactivated') == 1) { + $this->logger()->logAction(LOG_ERROR, LOG_INFO, "[API] User '" . $this->getUserDetail('loginnname') . "' tried to use API but is deactivated"); + throw new Exception("Account suspended", 406); + } + $this->initLang(); $this->initMail(); diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index bc2fb55b..bfad0ba9 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -199,7 +199,7 @@ class Customers extends ApiCommand implements ResourceEntity standard_error(array( 'stringisempty', 'myname' - )); + ), '', true); } elseif ($firstname == '' && $company == '') { standard_error(array( 'stringisempty', @@ -756,23 +756,23 @@ class Customers extends ApiCommand implements ResourceEntity standard_error(array( 'stringisempty', 'myname' - )); + ), '', true); } elseif ($firstname == '' && $company == '') { standard_error(array( 'stringisempty', 'myfirstname' - )); + ), '', true); } elseif ($email == '') { standard_error(array( 'stringisempty', 'emailadd' - )); + ), '', true); } elseif (! validateEmail($email)) { - standard_error('emailiswrong', $email); + standard_error('emailiswrong', $email, true); } else { if ($password != '') { - $password = validatePassword($password); + $password = validatePassword($password, true); $password = makeCryptPassword($password); } else { $password = $result['password']; From b42a7b1b26d434b41626a3ac2db8637f69e5c2be Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 22 Feb 2018 13:41:28 +0100 Subject: [PATCH 044/746] show basic api doc in webinterface (top-menu - options - API help) Signed-off-by: Michael Kaufmann (d00p) --- admin_index.php | 4 + apihelp.php | 112 +++++++++++++++++++++ customer_index.php | 3 + lib/classes/api/commands/class.Froxlor.php | 30 ++++-- lib/navigation/00.froxlor.main.php | 10 ++ lng/english.lng.php | 3 + lng/german.lng.php | 3 + templates/Sparkle/apihelp/index.tpl | 13 +++ templates/Sparkle/header.tpl | 3 + 9 files changed, 171 insertions(+), 10 deletions(-) create mode 100644 apihelp.php create mode 100644 templates/Sparkle/apihelp/index.tpl diff --git a/admin_index.php b/admin_index.php index 174f65a0..959a4632 100644 --- a/admin_index.php +++ b/admin_index.php @@ -51,6 +51,7 @@ if (isset($_POST['id'])) { } if ($page == 'overview') { + $log->logAction(ADM_ACTION, LOG_NOTICE, "viewed admin_index"); $overview_stmt = Database::prepare("SELECT COUNT(*) AS `number_customers`, SUM(`diskspace_used`) AS `diskspace_used`, @@ -404,3 +405,6 @@ if ($page == 'overview') { redirectTo($filename, array('s' => $s)); } } +elseif ($page == 'apihelp' && Settings::Get('api.enabled') == 1) { + require_once __DIR__ . '/apihelp.php'; +} diff --git a/apihelp.php b/apihelp.php new file mode 100644 index 00000000..2f426f3a --- /dev/null +++ b/apihelp.php @@ -0,0 +1,112 @@ + (2018-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package Panel + * @since 0.10.0 + * + */ + +// This file is being included in admin_index and customer_index +// and therefore does not need to require lib/init.php + +try { + $json_result = Froxlor::getLocal($userinfo)->listFunctions(); +} catch (Exception $e) { + dynamic_error($e->getMessage()); +} +$result = json_decode($json_result, true)['data']; + +// get response data +$m_arr = $result; + +// initialize output-array +$output_arr = array(); + +// check every module +foreach ($m_arr as $module) { + + // initialize module array for sorting + if (! isset($output_arr[$module['module']]) || ! is_array($output_arr[$module['module']])) { + $output_arr[$module['module']] = array(); + } + + // set necessary data + $output_arr[$module['module']][$module['function']] = array( + 'return_type' => (isset($module['return']['type']) && $module['return']['type'] != "" ? $module['return']['type'] : - 1), + 'params_list' => array(), + 'head' => $module['head'] + ); + + if (isset($module['params']) && is_array($module['params'])) { + foreach ($module['params'] as $param) { + $output_arr[$module['module']][$module['function']]['params_list'][] = array( + 'type' => $param['type'], + 'name' => $param['parameter'], + 'desc' => $param['desc'] + ); + } + } +} + +// sort array +ksort($output_arr); + +$apihelp = ""; + +// output ALL the modules +foreach ($output_arr as $module => $functions) { + + // sort by function + ksort($functions); + + // output ALL the functions + foreach ($functions as $function => $funcdata) { + $apihelp .= "
"; + $apihelp .= "

" . ($funcdata['return_type'] == - 1 ? "no-return-type" : $funcdata['return_type']) . " "; + $apihelp .= "" . $module . "." . $function . "

"; + // description + if (strtoupper(substr($funcdata['head'], 0, 4)) == "TODO") + $apihelp .= ""; + $apihelp .= $funcdata['head']; + if (strtoupper(substr($funcdata['head'], 0, 4)) == "TODO") + $apihelp .= ""; + // output ALL the params; + if (count($funcdata['params_list']) > 0) { + $parms = "

Parameters:
    "; + // separate and format them + foreach ($funcdata['params_list'] as $index => $param) { + $parms .= "
  • "; + // check whether the parameter is optional + if (! empty($param['desc']) && strtolower(substr(trim($param['desc']), 0, 8)) == "optional") { + $parms .= "optional "; + $param['desc'] = substr(trim($param['desc']), 8); + if (substr($param['desc'], 0, 1) == ',') { + $param['desc'] = substr(trim($param['desc']), 1); + } + } + $parms .= "" . (strtolower($param['type']) == 'unknown' ? "unknown" : $param['type']) . " " . $param['name'] . ""; + if (! empty($param['desc'])) { + $parms .= " " . trim($param['desc']); + } + $parms .= "
  • "; + } + $apihelp .= "
" . $parms; + } + $apihelp .= "


"; + } + $apihelp .= "
"; +} + +eval("echo \"" . getTemplate("apihelp/index", 1) . "\";"); diff --git a/customer_index.php b/customer_index.php index bb1305c9..71877c71 100644 --- a/customer_index.php +++ b/customer_index.php @@ -319,3 +319,6 @@ if ($page == 'overview') { redirectTo($filename, array('s' => $s)); } } +elseif ($page == 'apihelp' && Settings::Get('api.enabled') == 1) { + require_once __DIR__ . '/apihelp.php'; +} diff --git a/lib/classes/api/commands/class.Froxlor.php b/lib/classes/api/commands/class.Froxlor.php index b005634c..99a49a28 100644 --- a/lib/classes/api/commands/class.Froxlor.php +++ b/lib/classes/api/commands/class.Froxlor.php @@ -20,7 +20,7 @@ class Froxlor extends ApiCommand /** * checks whether there is a newer version of froxlor available - * + * * @throws Exception * @return string */ @@ -178,6 +178,7 @@ class Froxlor extends ApiCommand $clines = explode("\n", $comment); $result = array(); $result['params'] = array(); + $param_desc = false; foreach ($clines as $c) { $c = trim($c); // check param-section @@ -189,6 +190,7 @@ class Froxlor extends ApiCommand 'type' => $r[1], 'desc' => (isset($r[3]) ? trim($r['3']) : '') ); + $param_desc = true; } // check return-section elseif (strpos($c, '@return')) { preg_match('/^\*\s\@return\s(\w+)(\s.*)?/', $c, $r); @@ -201,18 +203,26 @@ class Froxlor extends ApiCommand 'desc' => (isset($r[2]) ? trim($r[2]) : '') ); } else if (! empty($c) && strpos($c, '@throws') === false) { - if (substr($c, 0, 3) == "/**") + if (substr($c, 0, 3) == "/**") { continue; - if (substr($c, 0, 2) == "*/") + } + if (substr($c, 0, 2) == "*/") { continue; - if (substr($c, 0, 1) == "*") + } + if (substr($c, 0, 1) == "*") { $c = trim(substr($c, 1)); - if (empty($c)) - continue; - if (! isset($result['head']) || empty($result['head'])) { - $result['head'] = $c . " "; - } else { - $result['head'] .= $c . " "; + if (empty($c)) { + continue; + } + if ($param_desc) { + $result['params'][count($result['params']) - 1]['desc'] .= $c; + } else { + if (! isset($result['head']) || empty($result['head'])) { + $result['head'] = $c . " "; + } else { + $result['head'] .= $c . " "; + } + } } } } diff --git a/lib/navigation/00.froxlor.main.php b/lib/navigation/00.froxlor.main.php index a02a3a07..61c7fe6f 100644 --- a/lib/navigation/00.froxlor.main.php +++ b/lib/navigation/00.froxlor.main.php @@ -38,6 +38,11 @@ return array( 'label' => $lng['menue']['main']['changetheme'], 'show_element' => (Settings::Get('panel.allow_theme_change_customer') == true) ), + array( + 'url' => 'customer_index.php?page=apihelp', + 'label' => $lng['menue']['main']['apihelp'], + 'show_element' => (Settings::Get('api.enabled') == true) + ), array( 'url' => 'customer_index.php?action=logout', 'label' => $lng['login']['logout'] @@ -179,6 +184,11 @@ return array( 'label' => $lng['menue']['main']['changetheme'], 'show_element' => (Settings::Get('panel.allow_theme_change_admin') == true) ), + array( + 'url' => 'admin_index.php?page=apihelp', + 'label' => $lng['menue']['main']['apihelp'], + 'show_element' => (Settings::Get('api.enabled') == true) + ), array( 'url' => 'admin_index.php?action=logout', 'label' => $lng['login']['logout'] diff --git a/lng/english.lng.php b/lng/english.lng.php index 8adc8a40..b0f87d35 100644 --- a/lng/english.lng.php +++ b/lng/english.lng.php @@ -2119,4 +2119,7 @@ $lng['admin']['plans']['use_plan'] = 'Apply plan'; $lng['question']['plan_reallydelete'] = 'Do you really want to delete the hosting plan %s?'; $lng['admin']['notryfiles']['title'] = 'No autogenerated try_files'; $lng['admin']['notryfiles']['description'] = 'Say yes here if you want to specify a custom try_files directive in specialsettings (needed for some wordpress plugins for example).'; + +// added in froxlor 0.10.0 $lng['panel']['none_value'] = 'None'; +$lng['menue']['main']['apihelp'] = 'API help'; diff --git a/lng/german.lng.php b/lng/german.lng.php index 888303b1..4693d860 100644 --- a/lng/german.lng.php +++ b/lng/german.lng.php @@ -1769,4 +1769,7 @@ $lng['admin']['plans']['use_plan'] = 'Plan übernehmen'; $lng['question']['plan_reallydelete'] = 'Wollen Sie den Hosting-Plan "%s" wirklich löschen?'; $lng['admin']['notryfiles']['title'] = 'Keine generierte try_files Anweisung'; $lng['admin']['notryfiles']['description'] = 'Wähle "Ja", wenn eine eigene try_files Direktive in den "eigenen Vhost Einstellungen" angegeben werden soll (z.B. nötig für manche Wordpress Plugins).'; + +// added in froxlor 0.10.0 $lng['panel']['none_value'] = 'Keine'; +$lng['menue']['main']['apihelp'] = 'API Hilfe'; diff --git a/templates/Sparkle/apihelp/index.tpl b/templates/Sparkle/apihelp/index.tpl new file mode 100644 index 00000000..793a57a8 --- /dev/null +++ b/templates/Sparkle/apihelp/index.tpl @@ -0,0 +1,13 @@ +$header +
+

+ API help +

+ +
+
+ {$apihelp} +
+ +
+$footer diff --git a/templates/Sparkle/header.tpl b/templates/Sparkle/header.tpl index 2909fc6d..d858f52c 100644 --- a/templates/Sparkle/header.tpl +++ b/templates/Sparkle/header.tpl @@ -67,6 +67,9 @@
  • {$lng['panel']['theme']}
  • + +
  • {$lng['menue']['main']['apihelp']}
  • +
  • {$lng['login']['logout']}
  • From dfb5d33a561f803e315bc3868e7548cebe482ba4 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 22 Feb 2018 16:51:56 +0100 Subject: [PATCH 045/746] add Admins.add() Signed-off-by: Michael Kaufmann (d00p) --- admin_admins.php | 245 +----------------- lib/classes/api/commands/class.Admins.php | 218 +++++++++++++++- lib/classes/api/commands/class.Customers.php | 34 +-- .../api/commands/class.PhpSettings.php | 8 +- 4 files changed, 237 insertions(+), 268 deletions(-) diff --git a/admin_admins.php b/admin_admins.php index 4e5a54d5..ce0f8488 100644 --- a/admin_admins.php +++ b/admin_admins.php @@ -201,247 +201,12 @@ if ($page == 'admins' if (isset($_POST['send']) && $_POST['send'] == 'send' ) { - - $name = validate($_POST['name'], 'name'); - $email = $idna_convert->encode(validate($_POST['email'], 'email')); - - $custom_notes = validate(str_replace("\r\n", "\n", $_POST['custom_notes']), 'custom_notes', '/^[^\0]*$/'); - $custom_notes_show = 0; - if (isset($_POST['custom_notes_show'])) { - $custom_notes_show = intval_ressource($_POST['custom_notes_show']); + try { + Admins::getLocal($userinfo, $_POST)->add(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - - $loginname = validate($_POST['loginname'], 'loginname'); - $password = validate($_POST['admin_password'], 'password'); - $password = validatePassword($password); - $def_language = validate($_POST['def_language'], 'default language'); - - $customers = intval_ressource($_POST['customers']); - if (isset($_POST['customers_ul'])) { - $customers = -1; - } - - $domains = intval_ressource($_POST['domains']); - if (isset($_POST['domains_ul'])) { - $domains = -1; - } - - $subdomains = intval_ressource($_POST['subdomains']); - if (isset($_POST['subdomains_ul'])) { - $subdomains = -1; - } - - $emails = intval_ressource($_POST['emails']); - if (isset($_POST['emails_ul'])) { - $emails = -1; - } - - $email_accounts = intval_ressource($_POST['email_accounts']); - if (isset($_POST['email_accounts_ul'])) { - $email_accounts = -1; - } - - $email_forwarders = intval_ressource($_POST['email_forwarders']); - if (isset($_POST['email_forwarders_ul'])) { - $email_forwarders = -1; - } - - if (Settings::Get('system.mail_quota_enabled') == '1') { - - $email_quota = validate($_POST['email_quota'], 'email_quota', '/^\d+$/', 'vmailquotawrong', array('0', '')); - if (isset($_POST['email_quota_ul'])) { - $email_quota = -1; - } - } else { - $email_quota = -1; - } - - $ftps = intval_ressource($_POST['ftps']); - if (isset($_POST['ftps_ul'])) { - $ftps = -1; - } - - if (Settings::Get('ticket.enabled') == 1) { - - $tickets = intval_ressource($_POST['tickets']); - if (isset($_POST['tickets_ul'])) { - $tickets = -1; - } - } else { - $tickets = 0; - } - - $mysqls = intval_ressource($_POST['mysqls']); - if (isset($_POST['mysqls_ul'])) { - $mysqls = -1; - } - - $customers_see_all = 0; - if (isset($_POST['customers_see_all'])) { - $customers_see_all = intval($_POST['customers_see_all']); - } - - $domains_see_all = 0; - if (isset($_POST['domains_see_all'])) { - $domains_see_all = intval($_POST['domains_see_all']); - } - - $caneditphpsettings = 0; - if (isset($_POST['caneditphpsettings'])) { - $caneditphpsettings = intval($_POST['caneditphpsettings']); - } - - $change_serversettings = 0; - if (isset($_POST['change_serversettings'])) { - $change_serversettings = intval($_POST['change_serversettings']); - } - - $diskspace = intval_ressource($_POST['diskspace']); - if (isset($_POST['diskspace_ul'])) { - $diskspace = -1; - } - - $traffic = doubleval_ressource($_POST['traffic']); - if (isset($_POST['traffic_ul'])) { - $traffic = -1; - } - - $tickets_see_all = 0; - if (isset($_POST['tickets_see_all'])) { - $tickets_see_all = intval($_POST['tickets_see_all']); - } - - $diskspace = $diskspace * 1024; - $traffic = $traffic * 1024 * 1024; - $ipaddress = intval_ressource($_POST['ipaddress']); - - // Check if the account already exists - $loginname_check_stmt = Database::prepare(" - SELECT `loginname` FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `loginname` = :login - "); - $loginname_check = Database::pexecute_first($loginname_check_stmt, array('login' => $loginname)); - - $loginname_check_admin_stmt = Database::prepare(" - SELECT `loginname` FROM `" . TABLE_PANEL_ADMINS . "` WHERE `loginname` = :login - "); - $loginname_check_admin = Database::pexecute_first($loginname_check_admin_stmt, array('login' => $loginname)); - - if ($loginname == '') { - standard_error(array('stringisempty', 'myloginname')); - } - elseif (strtolower($loginname_check['loginname']) == strtolower($loginname) - || strtolower($loginname_check_admin['loginname']) == strtolower($loginname) - ) { - standard_error('loginnameexists', $loginname); - } - // Accounts which match systemaccounts are not allowed, filtering them - elseif (preg_match('/^' . preg_quote(Settings::Get('customer.accountprefix'), '/') . '([0-9]+)/', $loginname)) { - standard_error('loginnameissystemaccount', Settings::Get('customer.accountprefix')); - } - elseif (!validateUsername($loginname)) { - standard_error('loginnameiswrong', $loginname); - } - elseif ($name == '') { - standard_error(array('stringisempty', 'myname')); - } - elseif ($email == '') { - standard_error(array('stringisempty', 'emailadd')); - } - elseif ($password == '') { - standard_error(array('stringisempty', 'mypassword')); - } - elseif (!validateEmail($email)) { - standard_error('emailiswrong', $email); - - } else { - - if ($customers_see_all != '1') { - $customers_see_all = '0'; - } - - if ($domains_see_all != '1') { - $domains_see_all = '0'; - } - - if ($caneditphpsettings != '1') { - $caneditphpsettings = '0'; - } - - if ($change_serversettings != '1') { - $change_serversettings = '0'; - } - - if ($tickets_see_all != '1') { - $tickets_see_all = '0'; - } - - $_theme = Settings::Get('panel.default_theme'); - - $ins_data = array( - 'loginname' => $loginname, - 'password' => makeCryptPassword($password), - 'name' => $name, - 'email' => $email, - 'lang' => $def_language, - 'change_serversettings' => $change_serversettings, - 'customers' => $customers, - 'customers_see_all' => $customers_see_all, - 'domains' => $domains, - 'domains_see_all' => $domains_see_all, - 'caneditphpsettings' => $caneditphpsettings, - 'diskspace' => $diskspace, - 'traffic' => $traffic, - 'subdomains' => $subdomains, - 'emails' => $emails, - 'accounts' => $email_accounts, - 'forwarders' => $email_forwarders, - 'quota' => $email_quota, - 'ftps' => $ftps, - 'tickets' => $tickets, - 'tickets_see_all' => $tickets_see_all, - 'mysqls' => $mysqls, - 'ip' => $ipaddress, - 'theme' => $_theme, - 'custom_notes' => $custom_notes, - 'custom_notes_show' => $custom_notes_show - ); - - $ins_stmt = Database::prepare(" - INSERT INTO `" . TABLE_PANEL_ADMINS . "` SET - `loginname` = :loginname, - `password` = :password, - `name` = :name, - `email` = :email, - `def_language` = :lang, - `change_serversettings` = :change_serversettings, - `customers` = :customers, - `customers_see_all` = :customers_see_all, - `domains` = :domains, - `domains_see_all` = :domains_see_all, - `caneditphpsettings` = :caneditphpsettings, - `diskspace` = :diskspace, - `traffic` = :traffic, - `subdomains` = :subdomains, - `emails` = :emails, - `email_accounts` = :accounts, - `email_forwarders` = :forwarders, - `email_quota` = :quota, - `ftps` = :ftps, - `tickets` = :tickets, - `tickets_see_all` = :tickets_see_all, - `mysqls` = :mysqls, - `ip` = :ip, - `theme` = :theme, - `custom_notes` = :custom_notes, - `custom_notes_show` = :custom_notes_show - "); - Database::pexecute($ins_stmt, $ins_data); - - $adminid = Database::lastInsertId(); - $log->logAction(ADM_ACTION, LOG_INFO, "added admin '" . $loginname . "'"); - redirectTo($filename, array('page' => $page, 's' => $s)); - } - + redirectTo($filename, array('page' => $page, 's' => $s)); } else { $language_options = ''; diff --git a/lib/classes/api/commands/class.Admins.php b/lib/classes/api/commands/class.Admins.php index 6a095e0e..68781b59 100644 --- a/lib/classes/api/commands/class.Admins.php +++ b/lib/classes/api/commands/class.Admins.php @@ -86,11 +86,212 @@ class Admins extends ApiCommand implements ResourceEntity public function add() { + if ($this->isAdmin()) { + + // required parameters + $name = $this->getParam('name'); + $email = $this->getParam('email'); + + // parameters + $def_language = $this->getParam('def_language', true, ''); + $custom_notes = $this->getParam('custom_notes', true, ''); + $custom_notes_show = $this->getParam('custom_notes_show', true, 0); + $password = $this->getParam('admin_password', true, ''); + $sendpassword = $this->getParam('sendpassword', true, 0); + $loginname = $this->getParam('new_loginname', true, ''); + + $diskspace = $this->getUlParam('diskspace', 'diskspace_ul', true, 0); + $traffic = $this->getUlParam('traffic', 'traffic_ul', true, 0); + $customers = $this->getUlParam('customers', 'customers_ul', true, 0); + $domains = $this->getUlParam('domains', 'domains_ul', true, 0); + $subdomains = $this->getUlParam('subdomains', 'subdomains_ul', true, 0); + $emails = $this->getUlParam('emails', 'emails_ul', true, 0); + $email_accounts = $this->getUlParam('email_accounts', 'email_accounts_ul', true, 0); + $email_forwarders = $this->getUlParam('email_forwarders', 'email_forwarders_ul', true, 0); + $email_quota = $this->getUlParam('email_quota', 'email_quota_ul', true, 0); + $ftps = $this->getUlParam('ftps', 'ftps_ul', true, 0); + $tickets = $this->getUlParam('tickets', 'tickets_ul', true, 0); + $mysqls = $this->getUlParam('mysqls', 'mysqls_ul', true, 0); + + $customers_see_all = $this->getParam('customers_see_all', true, 0); + $domains_see_all = $this->getParam('domains_see_all', true, 0); + $tickets_see_all = $this->getParam('tickets_see_all', true, 0); + $caneditphpsettings = $this->getParam('caneditphpsettings', true, 0); + $change_serversettings = $this->getParam('change_serversettings', true, 0); + $ipaddress = intval_ressource($this->getParam('ipaddress', true, -1)); + + // validation + $name = validate($name, 'name', '', '', array(), true); + $idna_convert = new idna_convert_wrapper(); + $email = $idna_convert->encode(validate($email, 'email', '', '', array(), true)); + $def_language = validate($def_language, 'default language', '', '', array(), true); + $custom_notes = validate(str_replace("\r\n", "\n", $custom_notes), 'custom_notes', '/^[^\0]*$/', '', array(), true); + + if (Settings::Get('system.mail_quota_enabled') != '1') { + $email_quota = - 1; + } + + if (Settings::Get('ticket.enabled') != '1') { + $tickets = - 1; + } + + $password = validate($password, 'password', '', '', array(), true); + // only check if not empty, + // cause empty == generate password automatically + if ($password != '') { + $password = validatePassword($password, true); + } + + $diskspace = $diskspace * 1024; + $traffic = $traffic * 1024 * 1024; + + // Check if the account already exists + try { + $dup_check_result = Customers::getLocal($this->getUserData(), array( + 'loginname' => $loginname + ))->get(); + $loginname_check = json_decode($dup_check_result, true)['data']; + } catch (Exception $e) { + $loginname_check = array( + 'loginname' => '' + ); + } + + // Check if an admin with the loginname already exists + try { + $dup_check_result = Admins::getLocal($this->getUserData(), array( + 'loginname' => $loginname + ))->get(); + $loginname_check_admin = json_decode($dup_check_result, true)['data']; + } catch (Exception $e) { + $loginname_check_admin = array( + 'loginname' => '' + ); + } + + if ($loginname == '') { + standard_error(array( + 'stringisempty', + 'myloginname' + ), '', true); + } elseif (strtolower($loginname_check['loginname']) == strtolower($loginname) || strtolower($loginname_check_admin['loginname']) == strtolower($loginname)) { + standard_error('loginnameexists', $loginname, true); + } // Accounts which match systemaccounts are not allowed, filtering them + elseif (preg_match('/^' . preg_quote(Settings::Get('customer.accountprefix'), '/') . '([0-9]+)/', $loginname)) { + standard_error('loginnameissystemaccount', Settings::Get('customer.accountprefix'), true); + } elseif (! validateUsername($loginname)) { + standard_error('loginnameiswrong', $loginname, true); + } elseif ($name == '') { + standard_error(array( + 'stringisempty', + 'myname' + ), '', true); + } elseif ($email == '') { + standard_error(array( + 'stringisempty', + 'emailadd' + ), '', true); + } elseif (! validateEmail($email)) { + standard_error('emailiswrong', $email, true); + } else { + + if ($customers_see_all != '1') { + $customers_see_all = '0'; + } + + if ($domains_see_all != '1') { + $domains_see_all = '0'; + } + + if ($caneditphpsettings != '1') { + $caneditphpsettings = '0'; + } + + if ($change_serversettings != '1') { + $change_serversettings = '0'; + } + + if ($tickets_see_all != '1') { + $tickets_see_all = '0'; + } + + if ($password == '') { + $password = generatePassword(); + } + + $_theme = Settings::Get('panel.default_theme'); + + $ins_data = array( + 'loginname' => $loginname, + 'password' => makeCryptPassword($password), + 'name' => $name, + 'email' => $email, + 'lang' => $def_language, + 'change_serversettings' => $change_serversettings, + 'customers' => $customers, + 'customers_see_all' => $customers_see_all, + 'domains' => $domains, + 'domains_see_all' => $domains_see_all, + 'caneditphpsettings' => $caneditphpsettings, + 'diskspace' => $diskspace, + 'traffic' => $traffic, + 'subdomains' => $subdomains, + 'emails' => $emails, + 'accounts' => $email_accounts, + 'forwarders' => $email_forwarders, + 'quota' => $email_quota, + 'ftps' => $ftps, + 'tickets' => $tickets, + 'tickets_see_all' => $tickets_see_all, + 'mysqls' => $mysqls, + 'ip' => $ipaddress, + 'theme' => $_theme, + 'custom_notes' => $custom_notes, + 'custom_notes_show' => $custom_notes_show + ); + + $ins_stmt = Database::prepare(" + INSERT INTO `" . TABLE_PANEL_ADMINS . "` SET + `loginname` = :loginname, + `password` = :password, + `name` = :name, + `email` = :email, + `def_language` = :lang, + `change_serversettings` = :change_serversettings, + `customers` = :customers, + `customers_see_all` = :customers_see_all, + `domains` = :domains, + `domains_see_all` = :domains_see_all, + `caneditphpsettings` = :caneditphpsettings, + `diskspace` = :diskspace, + `traffic` = :traffic, + `subdomains` = :subdomains, + `emails` = :emails, + `email_accounts` = :accounts, + `email_forwarders` = :forwarders, + `email_quota` = :quota, + `ftps` = :ftps, + `tickets` = :tickets, + `tickets_see_all` = :tickets_see_all, + `mysqls` = :mysqls, + `ip` = :ip, + `theme` = :theme, + `custom_notes` = :custom_notes, + `custom_notes_show` = :custom_notes_show + "); + Database::pexecute($ins_stmt, $ins_data, true, true); + + $adminid = Database::lastInsertId(); + $ins_data['adminid'] = $adminid; + $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] added admin '" . $loginname . "'"); + return $this->response(200, "successfull", $admin_ins_data); + } + } + throw new Exception("Not allowed to execute given command.", 403); } public function update() - { - } + {} /** * delete a admin entry by either id or loginname @@ -106,8 +307,7 @@ class Admins extends ApiCommand implements ResourceEntity * @return array */ public function delete() - { - } + {} /** * unlock a locked admin by either id or loginname @@ -116,7 +316,7 @@ class Admins extends ApiCommand implements ResourceEntity * optional, the admin-id * @param string $loginname * optional, the loginname - * + * * @throws Exception * @return array */ @@ -126,18 +326,18 @@ class Admins extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); $loginname = $this->getParam('loginname', $ln_optional, ''); - + if ($id <= 0 && empty($loginname)) { throw new Exception("Either 'id' or 'loginname' parameter must be given", 406); } - + $json_result = Admins::getLocal($this->getUserData(), array( 'id' => $id, 'loginname' => $loginname ))->get(); $result = json_decode($json_result, true)['data']; $id = $result['adminid']; - + $result_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_ADMINS . "` SET `loginfail_count` = '0' @@ -146,7 +346,7 @@ class Admins extends ApiCommand implements ResourceEntity Database::pexecute($result_stmt, array( 'id' => $id ), true, true); - + $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] unlocked admin '" . $result['loginname'] . "'"); return $this->response(200, "successfull", $result); } diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index bfad0ba9..d707c7f8 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -144,7 +144,6 @@ class Customers extends ApiCommand implements ResourceEntity $loginname = $this->getParam('new_loginname', true, ''); // validation - $idna_convert = new idna_convert_wrapper(); $name = validate($name, 'name', '', '', array(), true); $firstname = validate($firstname, 'first name', '', '', array(), true); $company = validate($company, 'company', '', '', array(), true); @@ -233,20 +232,25 @@ class Customers extends ApiCommand implements ResourceEntity } // Check if the account already exists - $loginname_check_stmt = Database::prepare(" - SELECT `loginname` FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `loginname` = :loginname - "); - $loginname_check = Database::pexecute_first($loginname_check_stmt, array( - 'loginname' => $loginname - ), true, true); - - $loginname_check_admin_stmt = Database::prepare(" - SELECT `loginname` FROM `" . TABLE_PANEL_ADMINS . "` WHERE `loginname` = :loginname - "); - $loginname_check_admin = Database::pexecute_first($loginname_check_admin_stmt, array( - 'loginname' => $loginname - ), true, true); - + try { + $dup_check_result = Customers::getLocal($this->getUserData(), array( + 'loginname' => $loginname + ))->get(); + $loginname_check = json_decode($dup_check_result, true)['data']; + } catch (Exception $e) { + $loginname_check = array('loginname' => ''); + } + + // Check if an admin with the loginname already exists + try { + $dup_check_result = Admins::getLocal($this->getUserData(), array( + 'loginname' => $loginname + ))->get(); + $loginname_check_admin = json_decode($dup_check_result, true)['data']; + } catch (Exception $e) { + $loginname_check_admin = array('loginname' => ''); + } + if (strtolower($loginname_check['loginname']) == strtolower($loginname) || strtolower($loginname_check_admin['loginname']) == strtolower($loginname)) { standard_error('loginnameexists', $loginname, true); } elseif (! validateUsername($loginname, Settings::Get('panel.unix_names'), 14 - strlen(Settings::Get('customer.mysqlprefix')))) { diff --git a/lib/classes/api/commands/class.PhpSettings.php b/lib/classes/api/commands/class.PhpSettings.php index 5507e454..8d0a5dd8 100644 --- a/lib/classes/api/commands/class.PhpSettings.php +++ b/lib/classes/api/commands/class.PhpSettings.php @@ -19,7 +19,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity { /** - * lists all php-config entries + * lists all php-setting entries * * @return array count|list */ @@ -102,7 +102,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity } /** - * return a php-config entry by id + * return a php-setting entry by id * * @param int $id php-settings-id * @@ -329,9 +329,9 @@ class PhpSettings extends ApiCommand implements ResourceEntity } /** - * delete a php-config entry by id + * delete a php-setting entry by id * - * @param int $id php-config-id + * @param int $id php-settings-id * * @throws Exception * @return array From d9ec214e17d2c14567b2f8e6d2d6e0e3b6e5c6c5 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 22 Feb 2018 21:05:58 +0100 Subject: [PATCH 046/746] secure included webinterface-modules; add settings-functions to Froxlor-ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- apihelp.php | 10 ++- dns_editor.php | 8 +- lib/classes/api/commands/class.Froxlor.php | 80 +++++++++++++++++++ .../filedir/function.makeCorrectDir.php | 35 ++++---- ssl_certificates.php | 8 +- 5 files changed, 112 insertions(+), 29 deletions(-) diff --git a/apihelp.php b/apihelp.php index 2f426f3a..9e9be7a5 100644 --- a/apihelp.php +++ b/apihelp.php @@ -1,6 +1,8 @@ $functions) { $apihelp .= "

    " . ($funcdata['return_type'] == - 1 ? "no-return-type" : $funcdata['return_type']) . " "; $apihelp .= "" . $module . "." . $function . "

    "; // description - if (strtoupper(substr($funcdata['head'], 0, 4)) == "TODO") + if (strtoupper(substr($funcdata['head'], 0, 5)) == "@TODO") $apihelp .= ""; $apihelp .= $funcdata['head']; - if (strtoupper(substr($funcdata['head'], 0, 4)) == "TODO") + if (strtoupper(substr($funcdata['head'], 0, 5)) == "@TODO") $apihelp .= ""; // output ALL the params; if (count($funcdata['params_list']) > 0) { diff --git a/dns_editor.php b/dns_editor.php index 98a4d683..cd720fdc 100644 --- a/dns_editor.php +++ b/dns_editor.php @@ -1,6 +1,8 @@ fetch(PDO::FETCH_ASSOC)) { + $result[] = array( + 'key' => $row['settinggroup'] . '.' . $row['varname'], + 'value' => $row['value'] + ); + } + return $this->response(200, "successfull", array( + 'count' => count($result), + 'list' => $result + )); + } + + /** + * return a setting by settinggroup.varname couple + * + * @param string $key + * settinggroup.varname couple + * + * @throws Exception + * @return string + */ + public function getSetting() + { + if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { + $setting = $this->getParam('key'); + return $this->response(200, "successfull", Settings::Get($setting)); + } + throw new Exception("Not allowed to execute given command.", 403); + } + + /** + * updates a setting + * + * @param string $key + * settinggroup.varname couple + * @param string $value + * optional the new value, default is '' + * + * @throws Exception + * @return string + */ + public function updateSetting() + { + if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { + $setting = $this->getParam('key'); + $value = $this->getParam('value', true, ''); + $oldvalue = Settings::Get($setting); + if (is_null($oldvalue)) { + throw new Exception("Setting '" . $setting . "' could not be found"); + } + $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] Changing setting '" . $setting . "' from '" . $oldvalue . "' to '" . $value . "'"); + return $this->response(200, "successfull", Settings::Set($setting, $value, true)); + } + throw new Exception("Not allowed to execute given command.", 403); + } + /** * returns a list of all available api functions * diff --git a/lib/functions/filedir/function.makeCorrectDir.php b/lib/functions/filedir/function.makeCorrectDir.php index dcc91ca2..b3ac5cba 100644 --- a/lib/functions/filedir/function.makeCorrectDir.php +++ b/lib/functions/filedir/function.makeCorrectDir.php @@ -20,26 +20,23 @@ /** * Function which returns a correct dirname, means to add slashes at the beginning and at the end if there weren't some * - * @param string The dirname + * @param string $dir + * The dirname + * * @return string The corrected dirname - * @author Florian Lippert */ -function makeCorrectDir($dir) { - - if (version_compare("5.4.6", PHP_VERSION, ">")) { - assert('is_string($dir) && strlen($dir) > 0 /* $dir does not look like an actual folder name */'); - } else { - assert('is_string($dir) && strlen($dir) > 0', 'Value "' . $dir .'" does not look like an actual folder name'); +function makeCorrectDir($dir) +{ + if (is_string($dir) && strlen($dir) > 0) { + $dir = trim($dir); + if (substr($dir, - 1, 1) != '/') { + $dir .= '/'; + } + if (substr($dir, 0, 1) != '/') { + $dir = '/' . $dir; + } + $dir = makeSecurePath($dir); + return $dir; } - - $dir = trim($dir); - - if (substr($dir, -1, 1) != '/') { - $dir.= '/'; - } - if (substr($dir, 0, 1) != '/') { - $dir = '/' . $dir; - } - $dir = makeSecurePath($dir); - return $dir; + throw new Exception("Cannot validate directory in " . __FUNCTION__ . " which is very dangerous."); } diff --git a/ssl_certificates.php b/ssl_certificates.php index 875b903e..dd0bba10 100644 --- a/ssl_certificates.php +++ b/ssl_certificates.php @@ -1,6 +1,8 @@ Date: Fri, 23 Feb 2018 11:08:24 +0100 Subject: [PATCH 047/746] check remote-ip when ip-restriction is set in api_keys table Signed-off-by: Michael Kaufmann (d00p) --- api.php | 8 ++++++-- lib/classes/api/abstract.ApiCommand.php | 6 +++++- lib/classes/api/class.FroxlorRPC.php | 27 +++++++++++++++++++++---- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/api.php b/api.php index c792d18b..a5dd6fe3 100644 --- a/api.php +++ b/api.php @@ -52,9 +52,13 @@ exit(); * * @return void */ -function json_response($status, $status_message, $data = null) +function json_response($status, $status_message = '', $data = null) { - header("HTTP/1.1 " . $status); + $resheader = $_SERVER["SERVER_PROTOCOL"] . " " . $status; + if (! empty($status_message)) { + $resheader .= ' ' . $status_message; + } + header($resheader); $response['status'] = $status; $response['status_message'] = $status_message; diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index f499140b..333985d5 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -368,7 +368,11 @@ abstract class ApiCommand */ protected function response($status, $status_message, $data = null) { - header("HTTP/1.1 " . $status); + $resheader = $_SERVER["SERVER_PROTOCOL"] . " " . $status; + if (! empty($status_message)) { + $resheader .= ' ' . $status_message; + } + header($resheader); $response['status'] = $status; $response['status_message'] = $status_message; diff --git a/lib/classes/api/class.FroxlorRPC.php b/lib/classes/api/class.FroxlorRPC.php index 693d4ee7..07d466fa 100644 --- a/lib/classes/api/class.FroxlorRPC.php +++ b/lib/classes/api/class.FroxlorRPC.php @@ -1,5 +1,20 @@ (2010-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package API + * @since 0.10.0 + * + */ class FroxlorRPC { @@ -48,13 +63,17 @@ class FroxlorRPC if ($result['apikey'] == $key && $result['secret'] == $secret && ($result['valid_until'] == -1 || $result['valid_until'] >= time())) { if (!empty($result['allowed_from'])) { $ip_list = explode(",", $result['allowed_from']); - $access_ip = $_SERVER['REMOTE_ADDR']; - // @fixme finish me + $ip_list = array_map('inet_pton', $ip_list); + $access_ip = inet_pton($_SERVER['REMOTE_ADDR']); + if (in_array($access_ip, $ip_list)) { + return true; + } + } else { + return true; } - return true; } } - throw new Exception("Invalid authorization credentials", 400); + throw new Exception("Invalid authorization credentials", 403); } /** From 8e0bfe9d09cd8dbc0bc9e063870165c967617784 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Fri, 23 Feb 2018 11:57:29 +0100 Subject: [PATCH 048/746] add Admins.update() Signed-off-by: Michael Kaufmann (d00p) --- admin_admins.php | 356 ++----------------- lib/classes/api/commands/class.Admins.php | 326 ++++++++++++++++- lib/classes/api/commands/class.Customers.php | 12 +- 3 files changed, 362 insertions(+), 332 deletions(-) diff --git a/admin_admins.php b/admin_admins.php index ce0f8488..1404aa6d 100644 --- a/admin_admins.php +++ b/admin_admins.php @@ -107,10 +107,14 @@ if ($page == 'admins' } elseif($action == 'su') { - $result_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_ADMINS . "` WHERE `adminid` = :adminid - "); - $result = Database::pexecute_first($result_stmt, array('adminid' => $id)); + try { + $json_result = Admins::getLocal($userinfo, array( + 'id' => $id + ))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; $destination_admin = $result['loginname']; if ($destination_admin != '' @@ -147,10 +151,14 @@ if ($page == 'admins' } elseif ($action == 'delete' && $id != 0 ) { - $result_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_ADMINS . "` WHERE `adminid` = :adminid - "); - $result = Database::pexecute_first($result_stmt, array('adminid' => $id)); + try { + $json_result = Admins::getLocal($userinfo, array( + 'id' => $id + ))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; if ($result['loginname'] != '') { if ($result['adminid'] == $userinfo['userid']) { @@ -160,37 +168,10 @@ if ($page == 'admins' if (isset($_POST['send']) && $_POST['send'] == 'send' ) { - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_PANEL_ADMINS . "` WHERE `adminid` = :adminid - "); - Database::pexecute($del_stmt, array('adminid' => $id)); - - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_PANEL_TRAFFIC_ADMINS . "` WHERE `adminid` = :adminid - "); - Database::pexecute($del_stmt, array('adminid' => $id)); - - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_PANEL_DISKSPACE_ADMINS . "` WHERE `adminid` = :adminid - "); - Database::pexecute($del_stmt, array('adminid' => $id)); - - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET - `adminid` = :userid WHERE `adminid` = :adminid - "); - Database::pexecute($upd_stmt, array('userid' => $userinfo['userid'], 'adminid' => $id)); - - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_DOMAINS . "` SET - `adminid` = :userid WHERE `adminid` = :adminid - "); - Database::pexecute($upd_stmt, array('userid' => $userinfo['userid'], 'adminid' => $id)); - - $log->logAction(ADM_ACTION, LOG_INFO, "deleted admin '" . $result['loginname'] . "'"); - updateCounters(); + Admins::getLocal($this->getUserData(), array( + 'id' => $id + ))->delete(); redirectTo($filename, array('page' => $page, 's' => $s)); - } else { ask_yesno('admin_admin_reallydelete', $filename, array('id' => $id, 'page' => $page, 'action' => $action), $result['loginname']); } @@ -249,299 +230,26 @@ if ($page == 'admins' && $id != 0 ) { - $result_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_ADMINS . "` WHERE `adminid` = :adminid - "); - $result = Database::pexecute_first($result_stmt, array('adminid' => $id)); + try { + $json_result = Admins::getLocal($userinfo, array( + 'id' => $id + ))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; if ($result['loginname'] != '') { if (isset($_POST['send']) && $_POST['send'] == 'send' ) { - $name = validate($_POST['name'], 'name'); - $email = $idna_convert->encode(validate($_POST['email'], 'email')); - - $custom_notes = validate(str_replace("\r\n", "\n", $_POST['custom_notes']), 'custom_notes', '/^[^\0]*$/'); - $custom_notes_show = $result['custom_notes_show']; - if (isset($_POST['custom_notes_show'])) { - $custom_notes_show = intval_ressource($_POST['custom_notes_show']); + try { + Admins::getLocal($userinfo, $_POST)->update(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - - if ($result['adminid'] == $userinfo['userid']) { - - $password = ''; - $def_language = $result['def_language']; - $deactivated = $result['deactivated']; - $customers = $result['customers']; - $domains = $result['domains']; - $subdomains = $result['subdomains']; - $emails = $result['emails']; - $email_accounts = $result['email_accounts']; - $email_forwarders = $result['email_forwarders']; - $email_quota = $result['email_quota']; - $ftps = $result['ftps']; - $tickets = $result['tickets']; - $mysqls = $result['mysqls']; - $tickets_see_all = $result['tickets_see_all']; - $customers_see_all = $result['customers_see_all']; - $domains_see_all = $result['domains_see_all']; - $caneditphpsettings = $result['caneditphpsettings']; - $change_serversettings = $result['change_serversettings']; - $diskspace = $result['diskspace']; - $traffic = $result['traffic']; - $ipaddress = $result['ip']; - - } else { - - $password = validate($_POST['admin_password'], 'new password'); - $def_language = validate($_POST['def_language'], 'default language'); - $deactivated = isset($_POST['deactivated']) ? 1 : 0; - - $customers = intval_ressource($_POST['customers']); - if (isset($_POST['customers_ul'])) { - $customers = -1; - } - - $domains = intval_ressource($_POST['domains']); - if (isset($_POST['domains_ul'])) { - $domains = -1; - } - - $subdomains = intval_ressource($_POST['subdomains']); - if (isset($_POST['subdomains_ul'])) { - $subdomains = -1; - } - - $emails = intval_ressource($_POST['emails']); - if (isset($_POST['emails_ul'])) { - $emails = -1; - } - - $email_accounts = intval_ressource($_POST['email_accounts']); - if (isset($_POST['email_accounts_ul'])) { - $email_accounts = -1; - } - - $email_forwarders = intval_ressource($_POST['email_forwarders']); - if (isset($_POST['email_forwarders_ul'])) { - $email_forwarders = -1; - } - - if (Settings::Get('system.mail_quota_enabled') == '1') { - $email_quota = validate($_POST['email_quota'], 'email_quota', '/^\d+$/', 'vmailquotawrong', array('0', '')); - if (isset($_POST['email_quota_ul'])) { - $email_quota = -1; - } - } else { - $email_quota = -1; - } - - $ftps = intval_ressource($_POST['ftps']); - if (isset($_POST['ftps_ul'])) { - $ftps = -1; - } - - if (Settings::Get('ticket.enabled') == 1) { - $tickets = intval_ressource($_POST['tickets']); - if (isset($_POST['tickets_ul'])) { - $tickets = -1; - } - } else { - $tickets = 0; - } - - $mysqls = intval_ressource($_POST['mysqls']); - if (isset($_POST['mysqls_ul'])) { - $mysqls = -1; - } - - $customers_see_all = 0; - if (isset($_POST['customers_see_all'])) { - $customers_see_all = intval($_POST['customers_see_all']); - } - - $domains_see_all = 0; - if (isset($_POST['domains_see_all'])) { - $domains_see_all = intval($_POST['domains_see_all']); - } - - $caneditphpsettings = 0; - if (isset($_POST['caneditphpsettings'])) { - $caneditphpsettings = intval($_POST['caneditphpsettings']); - } - - $change_serversettings = 0; - if (isset($_POST['change_serversettings'])) { - $change_serversettings = isset($_POST['change_serversettings']) ? 1 : 0; - } - - $tickets_see_all = 0; - if (isset($_POST['tickets_see_all'])) { - $tickets_see_all = intval($_POST['tickets_see_all']); - } - - $diskspace = intval($_POST['diskspace']); - if (isset($_POST['diskspace_ul'])) { - $diskspace = -1; - } - - $traffic = doubleval_ressource($_POST['traffic']); - if (isset($_POST['traffic_ul'])) { - $traffic = -1; - } - - $diskspace = $diskspace * 1024; - $traffic = $traffic * 1024 * 1024; - $ipaddress = intval_ressource($_POST['ipaddress']); - } - - if ($name == '') { - standard_error(array('stringisempty', 'myname')); - } elseif($email == '') { - standard_error(array('stringisempty', 'emailadd')); - } elseif(!validateEmail($email)) { - standard_error('emailiswrong', $email); - } else { - if ($password != '') { - $password = validatePassword($password); - $password = makeCryptPassword($password); - } else { - $password = $result['password']; - } - - if ($deactivated != '1') { - $deactivated = '0'; - } - - if ($customers_see_all != '1') { - $customers_see_all = '0'; - } - - if ($domains_see_all != '1') { - $domains_see_all = '0'; - } - - if ($caneditphpsettings != '1') { - $caneditphpsettings = '0'; - } - - if ($change_serversettings != '1') { - $change_serversettings = '0'; - } - - if ($tickets_see_all != '1') { - $tickets_see_all = '0'; - } - - // check if a resource was set to something lower - // than actually used by the admin/reseller - $res_warning = ""; - if ($customers != $result['customers'] && $customers != -1 && $customers < $result['customers_used']) { - $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'customers'); - } - if ($domains != $result['domains'] && $domains != -1 && $domains < $result['domains_used']) { - $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'domains'); - } - if ($diskspace != $result['diskspace'] && ($diskspace / 1024) != -1 && $diskspace < $result['diskspace_used']) { - $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'diskspace'); - } - if ($traffic != $result['traffic'] && ($traffic / 1024 / 1024) != -1 && $traffic < $result['traffic_used']) { - $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'traffic'); - } - if ($emails != $result['emails'] && $emails != -1 && $emails < $result['emails_used']) { - $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'emails'); - } - if ($email_accounts != $result['email_accounts'] && $email_accounts != -1 && $email_accounts < $result['email_accounts_used']) { - $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'email accounts'); - } - if ($email_forwarders != $result['email_forwarders'] && $email_forwarders != -1 && $email_forwarders < $result['email_forwarders_used']) { - $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'email forwarders'); - } - if ($email_quota != $result['email_quota'] && $email_quota != -1 && $email_quota < $result['email_quota_used']) { - $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'email quota'); - } - if ($ftps != $result['ftps'] && $ftps != -1 && $ftps < $result['ftps_used']) { - $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'ftps'); - } - if ($tickets != $result['tickets'] && $tickets != -1 && $tickets < $result['tickets_used']) { - $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'tickets'); - } - if ($mysqls != $result['mysqls'] && $mysqls != -1 && $mysqls < $result['mysqls_used']) { - $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'mysqls'); - } - - if ($res_warning != "") { - $link = ''; - $error = $res_warning; - eval("echo \"" . getTemplate('misc/error', '1') . "\";"); - exit; - } - - $upd_data = array( - 'password' => $password, - 'name' => $name, - 'email' => $email, - 'lang' => $def_language, - 'change_serversettings' => $change_serversettings, - 'customers' => $customers, - 'customers_see_all' => $customers_see_all, - 'domains' => $domains, - 'domains_see_all' => $domains_see_all, - 'caneditphpsettings' => $caneditphpsettings, - 'diskspace' => $diskspace, - 'traffic' => $traffic, - 'subdomains' => $subdomains, - 'emails' => $emails, - 'accounts' => $email_accounts, - 'forwarders' => $email_forwarders, - 'quota' => $email_quota, - 'ftps' => $ftps, - 'tickets' => $tickets, - 'tickets_see_all' => $tickets_see_all, - 'mysqls' => $mysqls, - 'ip' => $ipaddress, - 'deactivated' => $deactivated, - 'custom_notes' => $custom_notes, - 'custom_notes_show' => $custom_notes_show, - 'adminid' => $id - ); - - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_ADMINS . "` SET - `password` = :password, - `name` = :name, - `email` = :email, - `def_language` = :lang, - `change_serversettings` = :change_serversettings, - `customers` = :customers, - `customers_see_all` = :customers_see_all, - `domains` = :domains, - `domains_see_all` = :domains_see_all, - `caneditphpsettings` = :caneditphpsettings, - `diskspace` = :diskspace, - `traffic` = :traffic, - `subdomains` = :subdomains, - `emails` = :emails, - `email_accounts` = :accounts, - `email_forwarders` = :forwarders, - `email_quota` = :quota, - `ftps` = :ftps, - `tickets` = :tickets, - `tickets_see_all` = :tickets_see_all, - `mysqls` = :mysqls, - `ip` = :ip, - `deactivated` = :deactivated, - `custom_notes` = :custom_notes, - `custom_notes_show` = :custom_notes_show - WHERE `adminid` = :adminid - "); - Database::pexecute($upd_stmt, $upd_data); - - $log->logAction(ADM_ACTION, LOG_INFO, "edited admin '#" . $id . "'"); - redirectTo($filename, array('page' => $page, 's' => $s)); - } - + redirectTo($filename, array('page' => $page, 's' => $s)); } else { $dec_places = Settings::Get('panel.decimal_places'); diff --git a/lib/classes/api/commands/class.Admins.php b/lib/classes/api/commands/class.Admins.php index 68781b59..5779b68c 100644 --- a/lib/classes/api/commands/class.Admins.php +++ b/lib/classes/api/commands/class.Admins.php @@ -86,7 +86,7 @@ class Admins extends ApiCommand implements ResourceEntity public function add() { - if ($this->isAdmin()) { + if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { // required parameters $name = $this->getParam('name'); @@ -291,7 +291,269 @@ class Admins extends ApiCommand implements ResourceEntity } public function update() - {} + { + if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { + + $id = $this->getParam('id', true, 0); + $ln_optional = ($id <= 0 ? false : true); + $loginname = $this->getParam('loginname', $ln_optional, ''); + + if ($id <= 0 && empty($loginname)) { + throw new Exception("Either 'id' or 'loginname' parameter must be given", 406); + } + + $json_result = Admins::getLocal($this->getUserData(), array( + 'id' => $id, + 'loginname' => $loginname + ))->get(); + $result = json_decode($json_result, true)['data']; + $id = $result['adminid']; + + // parameters + $name = $this->getParam('name', true, $result['name']); + $idna_convert = new idna_convert_wrapper(); + $email = $this->getParam('email', true, $idna_convert->decode($result['email'])); + $custom_notes = $this->getParam('custom_notes', true, $result['custom_notes']); + $custom_notes_show = $this->getParam('custom_notes_show', true, $result['custom_notes_show']); + $theme = $this->getParam('theme', true, $result['theme']); + + // you cannot edit some of the details of yourself + if ($result['adminid'] == $this->getUserDetail('userid')) { + $password = ''; + $def_language = $result['def_language']; + $deactivated = $result['deactivated']; + $customers = $result['customers']; + $domains = $result['domains']; + $subdomains = $result['subdomains']; + $emails = $result['emails']; + $email_accounts = $result['email_accounts']; + $email_forwarders = $result['email_forwarders']; + $email_quota = $result['email_quota']; + $ftps = $result['ftps']; + $tickets = $result['tickets']; + $mysqls = $result['mysqls']; + $tickets_see_all = $result['tickets_see_all']; + $customers_see_all = $result['customers_see_all']; + $domains_see_all = $result['domains_see_all']; + $caneditphpsettings = $result['caneditphpsettings']; + $change_serversettings = $result['change_serversettings']; + $diskspace = $result['diskspace']; + $traffic = $result['traffic']; + $ipaddress = $result['ip']; + } else { + $password = $this->getParam('admin_password', true, ''); + $def_language = $this->getParam('def_language', true, $result['def_language']); + $deactivated = $this->getParam('deactivated', true, $result['deactivated']); + + $dec_places = Settings::Get('panel.decimal_places'); + $diskspace = $this->getUlParam('diskspace', 'diskspace_ul', true, round($result['diskspace'] / 1024, $dec_places)); + $traffic = $this->getUlParam('traffic', 'traffic_ul', true, round($result['traffic'] / (1024 * 1024), $dec_places)); + $customers = $this->getUlParam('customers', 'customers_ul', true, $result['customers']); + $domains = $this->getUlParam('domains', 'domains_ul', true, $result['domains']); + $subdomains = $this->getUlParam('subdomains', 'subdomains_ul', true, $result['subdomains']); + $emails = $this->getUlParam('emails', 'emails_ul', true, $result['emails']); + $email_accounts = $this->getUlParam('email_accounts', 'email_accounts_ul', true, $result['email_accounts']); + $email_forwarders = $this->getUlParam('email_forwarders', 'email_forwarders_ul', true, $result['email_forwarders']); + $email_quota = $this->getUlParam('email_quota', 'email_quota_ul', true, $result['email_quota']); + $ftps = $this->getUlParam('ftps', 'ftps_ul', true, $result['ftps']); + $tickets = $this->getUlParam('tickets', 'tickets_ul', true, $result['tickets']); + $mysqls = $this->getUlParam('mysqls', 'mysqls_ul', true, $result['mysqls']); + + $customers_see_all = $this->getParam('customers_see_all', true, $result['customers_see_all']); + $domains_see_all = $this->getParam('domains_see_all', true, $result['domains_see_all']); + $tickets_see_all = $this->getParam('tickets_see_all', true, $result['tickets_see_all']); + $caneditphpsettings = $this->getParam('caneditphpsettings', true, $result['caneditphpsettings']); + $change_serversettings = $this->getParam('change_serversettings', true, $result['change_serversettings']); + $ipaddress = intval_ressource($this->getParam('ipaddress', true, $result['ip'])); + + $diskspace = $diskspace * 1024; + $traffic = $traffic * 1024 * 1024; + } + + // validation + $name = validate($name, 'name', '', '', array(), true); + $idna_convert = new idna_convert_wrapper(); + $email = $idna_convert->encode(validate($email, 'email', '', '', array(), true)); + $def_language = validate($def_language, 'default language', '', '', array(), true); + $custom_notes = validate(str_replace("\r\n", "\n", $custom_notes), 'custom_notes', '/^[^\0]*$/', '', array(), true); + $theme = validate($theme, 'theme', '', '', array(), true); + + if (Settings::Get('system.mail_quota_enabled') != '1') { + $email_quota = - 1; + } + + if (Settings::Get('ticket.enabled') != '1') { + $tickets = - 1; + } + + if (empty($theme)) { + $theme = Settings::Get('panel.default_theme'); + } + + $password = validate($password, 'password', '', '', array(), true); + // only check if not empty, + // cause empty == generate password automatically + if ($password != '') { + $password = validatePassword($password, true); + } + + $diskspace = $diskspace * 1024; + $traffic = $traffic * 1024 * 1024; + + if ($name == '') { + standard_error(array( + 'stringisempty', + 'myname' + ), '', true); + } elseif ($email == '') { + standard_error(array( + 'stringisempty', + 'emailadd' + ), '', true); + } elseif (! validateEmail($email)) { + standard_error('emailiswrong', $email, true); + } else { + + if ($deactivated != '1') { + $deactivated = '0'; + } + + if ($customers_see_all != '1') { + $customers_see_all = '0'; + } + + if ($domains_see_all != '1') { + $domains_see_all = '0'; + } + + if ($caneditphpsettings != '1') { + $caneditphpsettings = '0'; + } + + if ($change_serversettings != '1') { + $change_serversettings = '0'; + } + + if ($tickets_see_all != '1') { + $tickets_see_all = '0'; + } + + if ($password != '') { + $password = validatePassword($password, true); + $password = makeCryptPassword($password); + } else { + $password = $result['password']; + } + + // check if a resource was set to something lower + // than actually used by the admin/reseller + $res_warning = ""; + if ($customers != $result['customers'] && $customers != -1 && $customers < $result['customers_used']) { + $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'customers'); + } + if ($domains != $result['domains'] && $domains != -1 && $domains < $result['domains_used']) { + $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'domains'); + } + if ($diskspace != $result['diskspace'] && ($diskspace / 1024) != -1 && $diskspace < $result['diskspace_used']) { + $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'diskspace'); + } + if ($traffic != $result['traffic'] && ($traffic / 1024 / 1024) != -1 && $traffic < $result['traffic_used']) { + $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'traffic'); + } + if ($emails != $result['emails'] && $emails != -1 && $emails < $result['emails_used']) { + $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'emails'); + } + if ($email_accounts != $result['email_accounts'] && $email_accounts != -1 && $email_accounts < $result['email_accounts_used']) { + $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'email accounts'); + } + if ($email_forwarders != $result['email_forwarders'] && $email_forwarders != -1 && $email_forwarders < $result['email_forwarders_used']) { + $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'email forwarders'); + } + if ($email_quota != $result['email_quota'] && $email_quota != -1 && $email_quota < $result['email_quota_used']) { + $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'email quota'); + } + if ($ftps != $result['ftps'] && $ftps != -1 && $ftps < $result['ftps_used']) { + $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'ftps'); + } + if ($tickets != $result['tickets'] && $tickets != -1 && $tickets < $result['tickets_used']) { + $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'tickets'); + } + if ($mysqls != $result['mysqls'] && $mysqls != -1 && $mysqls < $result['mysqls_used']) { + $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'mysqls'); + } + + if (!empty($res_warning)) { + throw new Exception($res_warning, 406); + } + + $upd_data = array( + 'password' => $password, + 'name' => $name, + 'email' => $email, + 'lang' => $def_language, + 'change_serversettings' => $change_serversettings, + 'customers' => $customers, + 'customers_see_all' => $customers_see_all, + 'domains' => $domains, + 'domains_see_all' => $domains_see_all, + 'caneditphpsettings' => $caneditphpsettings, + 'diskspace' => $diskspace, + 'traffic' => $traffic, + 'subdomains' => $subdomains, + 'emails' => $emails, + 'accounts' => $email_accounts, + 'forwarders' => $email_forwarders, + 'quota' => $email_quota, + 'ftps' => $ftps, + 'tickets' => $tickets, + 'tickets_see_all' => $tickets_see_all, + 'mysqls' => $mysqls, + 'ip' => $ipaddress, + 'deactivated' => $deactivated, + 'custom_notes' => $custom_notes, + 'custom_notes_show' => $custom_notes_show, + 'theme' => $theme, + 'adminid' => $id + ); + + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_ADMINS . "` SET + `password` = :password, + `name` = :name, + `email` = :email, + `def_language` = :lang, + `change_serversettings` = :change_serversettings, + `customers` = :customers, + `customers_see_all` = :customers_see_all, + `domains` = :domains, + `domains_see_all` = :domains_see_all, + `caneditphpsettings` = :caneditphpsettings, + `diskspace` = :diskspace, + `traffic` = :traffic, + `subdomains` = :subdomains, + `emails` = :emails, + `email_accounts` = :accounts, + `email_forwarders` = :forwarders, + `email_quota` = :quota, + `ftps` = :ftps, + `tickets` = :tickets, + `tickets_see_all` = :tickets_see_all, + `mysqls` = :mysqls, + `ip` = :ip, + `deactivated` = :deactivated, + `custom_notes` = :custom_notes, + `custom_notes_show` = :custom_notes_show, + `theme` = :theme + WHERE `adminid` = :adminid + "); + Database::pexecute($upd_stmt, $upd_data, true, true); + + $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] edited admin '" . $result['loginname'] . "'"); + return $this->response(200, "successfull", $upd_data); + } + } + throw new Exception("Not allowed to execute given command.", 403); + } /** * delete a admin entry by either id or loginname @@ -300,14 +562,66 @@ class Admins extends ApiCommand implements ResourceEntity * optional, the admin-id * @param string $loginname * optional, the loginname - * @param bool $delete_userfiles - * optional, default false * * @throws Exception * @return array */ public function delete() - {} + { + if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { + $id = $this->getParam('id', true, 0); + $ln_optional = ($id <= 0 ? false : true); + $loginname = $this->getParam('loginname', $ln_optional, ''); + + if ($id <= 0 && empty($loginname)) { + throw new Exception("Either 'id' or 'loginname' parameter must be given", 406); + } + + $json_result = Admins::getLocal($this->getUserData(), array( + 'id' => $id, + 'loginname' => $loginname + ))->get(); + $result = json_decode($json_result, true)['data']; + $id = $result['adminid']; + + // don't be stupid + if ($id == $this->getUserDetail('userid')) { + standard_error('youcantdeleteyourself', '', true); + } + + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_PANEL_ADMINS . "` WHERE `adminid` = :adminid + "); + Database::pexecute($del_stmt, array('adminid' => $id), true, true); + + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_PANEL_TRAFFIC_ADMINS . "` WHERE `adminid` = :adminid + "); + Database::pexecute($del_stmt, array('adminid' => $id), true, true); + + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_PANEL_DISKSPACE_ADMINS . "` WHERE `adminid` = :adminid + "); + Database::pexecute($del_stmt, array('adminid' => $id), true, true); + + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET + `adminid` = :userid WHERE `adminid` = :adminid + "); + Database::pexecute($upd_stmt, array('userid' => $this->getUserDetail('userid'), 'adminid' => $id), true, true); + + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_DOMAINS . "` SET + `adminid` = :userid WHERE `adminid` = :adminid + "); + Database::pexecute($upd_stmt, array('userid' => $this->getUserDetail('userid'), 'adminid' => $id), true, true); + + $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] deleted admin '" . $result['loginname'] . "'"); + updateCounters(); + return $this->response(200, "successfull", $result); + } + throw new Exception("Not allowed to execute given command.", 403); + } /** * unlock a locked admin by either id or loginname @@ -322,7 +636,7 @@ class Admins extends ApiCommand implements ResourceEntity */ public function unlock() { - if ($this->isAdmin()) { + if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); $loginname = $this->getParam('loginname', $ln_optional, ''); diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index d707c7f8..339631b2 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -706,6 +706,7 @@ class Customers extends ApiCommand implements ResourceEntity $perlenabled = $this->getParam('perlenabled', true, $result['perlenabled']); $dnsenabled = $this->getParam('dnsenabled', true, $result['dnsenabled']); $deactivated = $this->getParam('deactivated', true, $result['deactivated']); + $theme = $this->getParam('theme', true, $result['theme']); // validation $idna_convert = new idna_convert_wrapper(); @@ -721,6 +722,7 @@ class Customers extends ApiCommand implements ResourceEntity $customernumber = validate($customernumber, 'customer number', '/^[A-Za-z0-9 \-]*$/Di', '', array(), true); $def_language = validate($def_language, 'default language', '', '', array(), true); $custom_notes = validate(str_replace("\r\n", "\n", $custom_notes), 'custom_notes', '/^[^\0]*$/', '', array(), true); + $theme = validate($theme, 'theme', '', '', array(), true); if (Settings::Get('system.mail_quota_enabled') != '1') { $email_quota = - 1; @@ -730,6 +732,10 @@ class Customers extends ApiCommand implements ResourceEntity $tickets = - 1; } + if (empty($theme)) { + $theme = Settings::Get('panel.default_theme'); + } + $diskspace = $diskspace * 1024; $traffic = $traffic * 1024 * 1024; @@ -980,7 +986,8 @@ class Customers extends ApiCommand implements ResourceEntity 'perlenabled' => $perlenabled, 'dnsenabled' => $dnsenabled, 'custom_notes' => $custom_notes, - 'custom_notes_show' => $custom_notes_show + 'custom_notes_show' => $custom_notes_show, + 'theme' => $theme ); $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET @@ -1015,7 +1022,8 @@ class Customers extends ApiCommand implements ResourceEntity `perlenabled` = :perlenabled, `dnsenabled` = :dnsenabled, `custom_notes` = :custom_notes, - `custom_notes_show` = :custom_notes_show + `custom_notes_show` = :custom_notes_show, + `theme` = :theme WHERE `customerid` = :customerid "); Database::pexecute($upd_stmt, $upd_data); From 662f537a0de025a4704dce5394968ad0e7d07296 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Fri, 23 Feb 2018 12:48:17 +0100 Subject: [PATCH 049/746] fixes in Admins.update(); use ApiCommand for theme-, language- and password-change Signed-off-by: Michael Kaufmann (d00p) --- admin_index.php | 46 +++++++++-------------- customer_index.php | 41 +++++++++----------- lib/classes/api/commands/class.Admins.php | 17 ++------- 3 files changed, 38 insertions(+), 66 deletions(-) diff --git a/admin_index.php b/admin_index.php index 959a4632..6efb31ff 100644 --- a/admin_index.php +++ b/admin_index.php @@ -214,15 +214,11 @@ if ($page == 'overview') { } elseif($new_password != $new_password_confirm) { standard_error('newpasswordconfirmerror'); } else { - $chgpwd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_ADMINS . "` - SET `password`= :newpasswd - WHERE `adminid`= :adminid" - ); - Database::pexecute($chgpwd_stmt, array( - 'newpasswd' => makeCryptPassword($new_password), - 'adminid' => (int)$userinfo['adminid'] - )); + try { + Admins::getLocal($userinfo, array('id' => $userinfo['adminid'], 'admin_password' => $new_password))->update(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } $log->logAction(ADM_ACTION, LOG_NOTICE, 'changed password'); redirectTo($filename, Array('s' => $s)); } @@ -238,16 +234,13 @@ if ($page == 'overview') { $def_language = validate($_POST['def_language'], 'default language'); if (isset($languages[$def_language])) { - $lng_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_ADMINS . "` - SET `def_language`= :deflng - WHERE `adminid`= :adminid" - ); - Database::pexecute($lng_stmt, array( - 'deflng' => $def_language, - 'adminid' => (int)$userinfo['adminid'] - )); + try { + Admins::getLocal($userinfo, array('id' => $userinfo['adminid'], 'def_language' => $def_language))->update(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + // also update current session $lng_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_SESSIONS . "` SET `language`= :lng @@ -258,7 +251,6 @@ if ($page == 'overview') { 'hash' => $s )); } - $log->logAction(ADM_ACTION, LOG_NOTICE, "changed his/her default language to '" . $def_language . "'"); redirectTo($filename, array('s' => $s)); @@ -284,17 +276,13 @@ if ($page == 'overview') { && $_POST['send'] == 'send' ) { $theme = validate($_POST['theme'], 'theme'); + try { + Admins::getLocal($userinfo, array('id' => $userinfo['adminid'], 'theme' => $theme))->update(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } - $theme_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_ADMINS . "` - SET `theme`= :theme - WHERE `adminid`= :adminid" - ); - Database::pexecute($theme_stmt, array( - 'theme' => $theme, - 'adminid' => (int)$userinfo['adminid'] - )); - + // also update current session $theme_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_SESSIONS . "` SET `theme`= :theme diff --git a/customer_index.php b/customer_index.php index 71877c71..85d02158 100644 --- a/customer_index.php +++ b/customer_index.php @@ -122,15 +122,11 @@ if ($page == 'overview') { standard_error('newpasswordconfirmerror'); } else { // Update user password - $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` - SET `password` = :newpassword - WHERE `customerid` = :customerid" - ); - $params = array( - "newpassword" => makeCryptPassword($new_password), - "customerid" => $userinfo['customerid'] - ); - Database::pexecute($stmt, $params); + try { + Customers::getLocal($userinfo, array('id' => $userinfo['customerid'], 'new_customer_password' => $new_password))->update(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } $log->logAction(USR_ACTION, LOG_NOTICE, 'changed password'); // Update ftp password @@ -181,21 +177,20 @@ if ($page == 'overview') { if (isset($_POST['send']) && $_POST['send'] == 'send') { $def_language = validate($_POST['def_language'], 'default language'); if (isset($languages[$def_language])) { - $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` - SET `def_language` = :lang - WHERE `customerid` = :customerid" - ); - Database::pexecute($stmt, array("lang" => $def_language, "customerid" => $userinfo['customerid'])); + try { + Customers::getLocal($userinfo, array('id' => $userinfo['customerid'], 'def_language' => $def_language))->update(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + // also update current session $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_SESSIONS . "` SET `language` = :lang WHERE `hash` = :hash" ); Database::pexecute($stmt, array("lang" => $def_language, "hash" => $s)); - - $log->logAction(USR_ACTION, LOG_NOTICE, "changed default language to '" . $def_language . "'"); } - + $log->logAction(USR_ACTION, LOG_NOTICE, "changed default language to '" . $def_language . "'"); redirectTo($filename, array('s' => $s)); } else { $default_lang = Settings::Get('panel.standardlanguage'); @@ -213,13 +208,13 @@ if ($page == 'overview') { } elseif ($page == 'change_theme') { if (isset($_POST['send']) && $_POST['send'] == 'send') { $theme = validate($_POST['theme'], 'theme'); + try { + Customers::getLocal($userinfo, array('id' => $userinfo['customerid'], 'theme' => $theme))->update(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } - $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` - SET `theme` = :theme - WHERE `customerid` = :customerid" - ); - Database::pexecute($stmt, array("theme" => $theme, "customerid" => $userinfo['customerid'])); - + // also update current session $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_SESSIONS . "` SET `theme` = :theme WHERE `hash` = :hash" diff --git a/lib/classes/api/commands/class.Admins.php b/lib/classes/api/commands/class.Admins.php index 5779b68c..4c66592d 100644 --- a/lib/classes/api/commands/class.Admins.php +++ b/lib/classes/api/commands/class.Admins.php @@ -313,14 +313,14 @@ class Admins extends ApiCommand implements ResourceEntity $name = $this->getParam('name', true, $result['name']); $idna_convert = new idna_convert_wrapper(); $email = $this->getParam('email', true, $idna_convert->decode($result['email'])); + $password = $this->getParam('admin_password', true, ''); + $def_language = $this->getParam('def_language', true, $result['def_language']); $custom_notes = $this->getParam('custom_notes', true, $result['custom_notes']); $custom_notes_show = $this->getParam('custom_notes_show', true, $result['custom_notes_show']); $theme = $this->getParam('theme', true, $result['theme']); // you cannot edit some of the details of yourself if ($result['adminid'] == $this->getUserDetail('userid')) { - $password = ''; - $def_language = $result['def_language']; $deactivated = $result['deactivated']; $customers = $result['customers']; $domains = $result['domains']; @@ -341,8 +341,6 @@ class Admins extends ApiCommand implements ResourceEntity $traffic = $result['traffic']; $ipaddress = $result['ip']; } else { - $password = $this->getParam('admin_password', true, ''); - $def_language = $this->getParam('def_language', true, $result['def_language']); $deactivated = $this->getParam('deactivated', true, $result['deactivated']); $dec_places = Settings::Get('panel.decimal_places'); @@ -377,6 +375,7 @@ class Admins extends ApiCommand implements ResourceEntity $def_language = validate($def_language, 'default language', '', '', array(), true); $custom_notes = validate(str_replace("\r\n", "\n", $custom_notes), 'custom_notes', '/^[^\0]*$/', '', array(), true); $theme = validate($theme, 'theme', '', '', array(), true); + $password = validate($password, 'password', '', '', array(), true); if (Settings::Get('system.mail_quota_enabled') != '1') { $email_quota = - 1; @@ -390,16 +389,6 @@ class Admins extends ApiCommand implements ResourceEntity $theme = Settings::Get('panel.default_theme'); } - $password = validate($password, 'password', '', '', array(), true); - // only check if not empty, - // cause empty == generate password automatically - if ($password != '') { - $password = validatePassword($password, true); - } - - $diskspace = $diskspace * 1024; - $traffic = $traffic * 1024 * 1024; - if ($name == '') { standard_error(array( 'stringisempty', From 6409fb2dbedd93c0e0844cf2e0f0397543f235f3 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Fri, 23 Feb 2018 15:17:22 +0100 Subject: [PATCH 050/746] started working on Mysqls-ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- customer_mysql.php | 42 ++-- lib/classes/api/commands/class.Mysqls.php | 283 ++++++++++++++++++++++ 2 files changed, 297 insertions(+), 28 deletions(-) create mode 100644 lib/classes/api/commands/class.Mysqls.php diff --git a/customer_mysql.php b/customer_mysql.php index 8367adec..774daf6b 100644 --- a/customer_mysql.php +++ b/customer_mysql.php @@ -93,12 +93,15 @@ if ($page == 'overview') { eval("echo \"" . getTemplate('mysql/mysqls') . "\";"); } elseif ($action == 'delete' && $id != 0) { - $result_stmt = Database::prepare('SELECT `id`, `databasename`, `description`, `dbserver` FROM `' . TABLE_PANEL_DATABASES . '` - WHERE `customerid`="' . (int)$userinfo['customerid'] . '" - AND `id`="' . (int)$id . '"' - ); - Database::pexecute($result_stmt, array("customerid" => $userinfo['customerid'])); - $result = $result_stmt->fetch(PDO::FETCH_ASSOC); + + try { + $json_result = Mysqls::getLocal($userinfo, array( + 'id' => $id + ))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; if (isset($result['databasename']) && $result['databasename'] != '') { @@ -112,28 +115,11 @@ if ($page == 'overview') { } if (isset($_POST['send']) && $_POST['send'] == 'send') { - // Begin root-session - Database::needRoot(true, $result['dbserver']); - $dbm = new DbManager($log); - $dbm->getManager()->deleteDatabase($result['databasename']); - $log->logAction(USR_ACTION, LOG_INFO, "deleted database '" . $result['databasename'] . "'"); - Database::needRoot(false); - // End root-session - - $stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_DATABASES . "` - WHERE `customerid` = :customerid - AND `id` = :id" - ); - Database::pexecute($stmt, array("customerid" => $userinfo['customerid'], "id" => $id)); - - $resetaccnumber = ($userinfo['mysqls_used'] == '1') ? " , `mysql_lastaccountnumber` = '0' " : ''; - - $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` - SET `mysqls_used` = `mysqls_used` - 1 " . $resetaccnumber . " - WHERE `customerid` = :customerid" - ); - Database::pexecute($stmt, array("customerid" => $userinfo['customerid'])); - + try { + Mysqls::getLocal($userinfo, $_POST)->delete(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } redirectTo($filename, array('page' => $page, 's' => $s)); } else { $dbnamedesc = $result['databasename']; diff --git a/lib/classes/api/commands/class.Mysqls.php b/lib/classes/api/commands/class.Mysqls.php new file mode 100644 index 00000000..fd2ec191 --- /dev/null +++ b/lib/classes/api/commands/class.Mysqls.php @@ -0,0 +1,283 @@ + (2010-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package API + * @since 0.10.0 + * + */ +class Mysqls extends ApiCommand implements ResourceEntity +{ + + public function add() + {} + + /** + * return a mysql database entry by either id or dbname + * + * @param int $id + * optional, the database-id + * @param string $dbname + * optional, the databasename + * @param int $dbserver + * optional, specify database-server, default is none + * + * @throws Exception + * @return array + */ + public function get() + { + $id = $this->getParam('id', true, 0); + $dn_optional = ($id <= 0 ? false : true); + $dbname = $this->getParam('dbname', $dn_optional, ''); + $dbserver = $this->getParam('dbserver', true, - 1); + + if ($id <= 0 && empty($dbname)) { + throw new Exception("Either 'id' or 'dbname' parameter must be given", 406); + } + + if ($this->isAdmin()) { + if ($this->getUserDetail('customers_see_all') != 1) { + // if it's a reseller or an admin who cannot see all customers, we need to check + // whether the database belongs to one of his customers + $json_result = Customers::getLocal($this->getUserData())->list(); + $custom_list_result = json_decode($json_result, true)['data']['list']; + $customer_ids = array(); + foreach ($custom_list_result as $customer) { + $customer_ids[] = $customer['customerid']; + } + if (count($customer_ids) > 0) { + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_DATABASES . "` + WHERE " . ($id > 0 ? "`id` = :iddn" : "`databasename` = :iddn") . ($dbserver >= 0 ? " AND `dbserver` = :dbserver" : "") . " AND `customerid` IN (:customerids) + "); + $params = array( + 'iddn' => ($id <= 0 ? $dbname : $id), + 'customerids' => implode(", ", $customer_ids) + ); + if ($dbserver >= 0) { + $params['dbserver'] = $dbserver; + } + } else { + throw new Exception("You do not have any customers yet", 406); + } + } else { + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_DATABASES . "` + WHERE " . ($id > 0 ? "`id` = :iddn" : "`databasename` = :iddn") . ($dbserver >= 0 ? " AND `dbserver` = :dbserver" : "")); + $params = array( + 'iddn' => ($id <= 0 ? $dbname : $id) + ); + if ($dbserver >= 0) { + $params['dbserver'] = $dbserver; + } + } + } else { + if ($id != $this->getUserDetail('customerid')) { + throw new Exception("You cannot access data of other customers", 401); + } + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_DATABASES . "` + WHERE `customerid`= :customerid AND " . ($id > 0 ? "`id` = :iddn" : "`databasename` = :iddn") . ($dbserver >= 0 ? " AND `dbserver` = :dbserver" : "")); + $params = array( + 'customerid' => $this->getUserDetail('customerid'), + 'iddn' => ($id <= 0 ? $dbname : $id) + ); + if ($dbserver >= 0) { + $params['dbserver'] = $dbserver; + } + } + $result = Database::pexecute_first($result_stmt, $params, true, true); + if ($result) { + Database::needRoot(true, $dbserver); + $mbdata_stmt = Database::prepare(" + SELECT SUM(data_length + index_length) as MB FROM information_schema.TABLES + WHERE table_schema = :table_schema + GROUP BY table_schema + "); + Database::pexecute($mbdata_stmt, array( + "table_schema" => $result['databasename'] + ), true, true); + $mbdata = $mbdata_stmt->fetch(PDO::FETCH_ASSOC); + Database::needRoot(false); + $result['size'] = $mbdata['MB']; + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_NOTICE, "[API] get database '" . $result['databasename'] . "'"); + return $this->response(200, "successfull", $result); + } + $key = ($id > 0 ? "id #" . $id : "dbname '" . $dbname . "'"); + throw new Exception("MySQL database with " . $key . " could not be found", 404); + } + + public function update() + {} + + /** + * list all databases, if called from an admin, list all databases of all customers you are allowed to view, or specify id or loginname for one specific customer + * + * @param int $dbserver + * optional, specify dbserver to select from, else use all available + * @param int $customerid + * optional, admin-only, select dbs of a specific customer by id + * @param string $loginname + * optional, admin-only, select dbs of a specific customer by loginname + * + * @return array count|list + */ + public function list() + { + $result = array(); + $dbserver = $this->getParam('dbserver', true, - 1); + if ($this->isAdmin()) { + // if we're an admin, list all databases of all the admins customers + // or optionally for one specific customer identified by id or loginname + $customerid = $this->getParam('customerid', true, 0); + $loginname = $this->getParam('loginname', true, ''); + + if (! empty($customer_id) || ! empty($loginname)) { + $json_result = Customers::getLocal($this->getUserData(), array( + 'id' => $customerid, + 'loginname' => $loginname + ))->get(); + $custom_list_result = array( + json_decode($json_result, true)['data'] + ); + } else { + $json_result = Customers::getLocal($this->getUserData())->list(); + $custom_list_result = json_decode($json_result, true)['data']['list']; + } + $customer_ids = array(); + foreach ($custom_list_result as $customer) { + $customer_ids[] = $customer['customerid']; + } + } else { + $customer_ids = array( + $this->getUserDetail('customerid') + ); + } + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_DATABASES . "` + WHERE `customerid`= :customerid AND `dbserver` = :dbserver + "); + if ($dbserver < 0) { + // use all dbservers + $dbservers_stmt = Database::query("SELECT DISTINCT `dbserver` FROM `" . TABLE_PANEL_DATABASES . "`"); + $dbservers = $dbservers_stmt->fetchAll(PDO::FETCH_ASSOC); + } else { + // use specific dbserver + $dbservers = array( + array( + 'dbserver' => $dbserver + ) + ); + } + + foreach ($customer_ids as $customer_id) { + foreach ($dbservers as $_dbserver) { + Database::pexecute($result_stmt, array( + 'customerid' => $customer_id, + 'dbserver' => $_dbserver['dbserver'] + ), true, true); + // Begin root-session + Database::needRoot(true, $_dbserver['dbserver']); + while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { + $mbdata_stmt = Database::prepare(" + SELECT SUM(data_length + index_length) as MB FROM information_schema.TABLES + WHERE table_schema = :table_schema + GROUP BY table_schema + "); + Database::pexecute($mbdata_stmt, array( + "table_schema" => $row['databasename'] + ), true, true); + $mbdata = $mbdata_stmt->fetch(PDO::FETCH_ASSOC); + $row['size'] = $mbdata['MB']; + $result[] = $row; + } + Database::needRoot(false); + } + } + return $this->response(200, "successfull", array( + 'count' => count($result), + 'list' => $result + )); + } + + /** + * delete a mysql database by either id or dbname + * + * @param int $id + * optional, the database-id + * @param string $dbname + * optional, the databasename + * @param int $dbserver + * optional, specify database-server, default is none + * + * @throws Exception + * @return array + */ + public function delete() + { + $id = $this->getParam('id', true, 0); + $dn_optional = ($id <= 0 ? false : true); + $dbname = $this->getParam('dbname', $dn_optional, ''); + $dbserver = $this->getParam('dbserver', true, - 1); + + if ($id <= 0 && empty($dbname)) { + throw new Exception("Either 'id' or 'dbname' parameter must be given", 406); + } + + $json_result = Mysqls::getLocal($this->getUserData(), array( + 'id' => $id, + 'dbname' => $dbname, + 'dbserver' => $dbserver + ))->get(); + $result = json_decode($json_result, true)['data']; + $id = $result['id']; + + // Begin root-session + Database::needRoot(true, $result['dbserver']); + $dbm = new DbManager($this->logger()); + $dbm->getManager()->deleteDatabase($result['databasename']); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_WARNING, "[API] deleted database '" . $result['databasename'] . "'"); + Database::needRoot(false); + // End root-session + + // delete from table + $stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_DATABASES . "` WHERE `id` = :id"); + Database::pexecute($stmt, array( + "id" => $id + ), true, true); + + // get needed customer info to reduce the mysql-usage-counter by one + if ($this->isAdmin()) { + $json_result = Customers::getLocal($this->getUserData(), array( + 'id' => $result['customerid'] + ))->get(); + $customer = json_decode($json_result, true)['data']; + $mysql_used = $customer['mysqls_used']; + $customer_id = $customer['customer_id']; + } else { + $mysql_used = $this->getUserDetail('mysqls_used'); + $customer_id = $this->getUserDetail('customer_id'); + } + // reduce mysql-usage-counter + $resetaccnumber = ($mysql_used == '1') ? " , `mysql_lastaccountnumber` = '0' " : ''; + $stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_CUSTOMERS . "` + SET `mysqls_used` = `mysqls_used` - 1 " . $resetaccnumber . " + WHERE `customerid` = :customerid + "); + Database::pexecute($stmt, array( + "customerid" => $customer_id + ), true, true); + return $this->response(200, "successfull", $result); + } +} From 831ee221f6bb8e0459f0a33168b4da17b968d989 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Fri, 23 Feb 2018 18:18:31 +0100 Subject: [PATCH 051/746] make lng, version, dbversion and branding protected variables of ApiCommand to avoid the need of 'global' statement Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/abstract.ApiCommand.php | 36 ++++++++++++- lib/classes/api/commands/class.Admins.php | 22 ++++---- lib/classes/api/commands/class.Customers.php | 6 +-- lib/classes/api/commands/class.FpmDaemons.php | 2 +- lib/classes/api/commands/class.Froxlor.php | 54 ++++++++++--------- .../api/commands/class.PhpSettings.php | 2 +- 6 files changed, 79 insertions(+), 43 deletions(-) diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index 333985d5..ee8f1a90 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -60,6 +60,34 @@ abstract class ApiCommand */ private $cmd_params = null; + /** + * language strings array + * + * @var array + */ + protected $lng = null; + + /** + * froxlor version + * + * @var string + */ + protected $version = null; + + /** + * froxlor dbversion + * + * @var int + */ + protected $dbversion = null; + + /** + * froxlor version-branding + * + * @var string + */ + protected $branding = null; + /** * * @param array $header @@ -73,8 +101,11 @@ abstract class ApiCommand */ public function __construct($header = null, $params = null, $userinfo = null) { - global $lng; - + global $lng, $version, $dbversion, $branding; + + $this->version = $version; + $this->dbversion = $dbversion; + $this->branding = $branding; $this->cmd_params = $params; if (! empty($header)) { $this->readUserData($header); @@ -93,6 +124,7 @@ abstract class ApiCommand } $this->initLang(); + $this->lng = $lng; $this->initMail(); if ($this->debug) { diff --git a/lib/classes/api/commands/class.Admins.php b/lib/classes/api/commands/class.Admins.php index 4c66592d..88ee04e1 100644 --- a/lib/classes/api/commands/class.Admins.php +++ b/lib/classes/api/commands/class.Admins.php @@ -438,37 +438,37 @@ class Admins extends ApiCommand implements ResourceEntity // than actually used by the admin/reseller $res_warning = ""; if ($customers != $result['customers'] && $customers != -1 && $customers < $result['customers_used']) { - $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'customers'); + $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'customers'); } if ($domains != $result['domains'] && $domains != -1 && $domains < $result['domains_used']) { - $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'domains'); + $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'domains'); } if ($diskspace != $result['diskspace'] && ($diskspace / 1024) != -1 && $diskspace < $result['diskspace_used']) { - $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'diskspace'); + $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'diskspace'); } if ($traffic != $result['traffic'] && ($traffic / 1024 / 1024) != -1 && $traffic < $result['traffic_used']) { - $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'traffic'); + $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'traffic'); } if ($emails != $result['emails'] && $emails != -1 && $emails < $result['emails_used']) { - $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'emails'); + $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'emails'); } if ($email_accounts != $result['email_accounts'] && $email_accounts != -1 && $email_accounts < $result['email_accounts_used']) { - $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'email accounts'); + $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'email accounts'); } if ($email_forwarders != $result['email_forwarders'] && $email_forwarders != -1 && $email_forwarders < $result['email_forwarders_used']) { - $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'email forwarders'); + $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'email forwarders'); } if ($email_quota != $result['email_quota'] && $email_quota != -1 && $email_quota < $result['email_quota_used']) { - $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'email quota'); + $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'email quota'); } if ($ftps != $result['ftps'] && $ftps != -1 && $ftps < $result['ftps_used']) { - $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'ftps'); + $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'ftps'); } if ($tickets != $result['tickets'] && $tickets != -1 && $tickets < $result['tickets_used']) { - $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'tickets'); + $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'tickets'); } if ($mysqls != $result['mysqls'] && $mysqls != -1 && $mysqls < $result['mysqls_used']) { - $res_warning .= sprintf($lng['error']['setlessthanalreadyused'], 'mysqls'); + $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'mysqls'); } if (!empty($res_warning)) { diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index 339631b2..26ae4568 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -97,8 +97,6 @@ class Customers extends ApiCommand implements ResourceEntity public function add() { - global $lng; - if ($this->isAdmin()) { if ($this->getUserDetail('customers_used') < $this->getUserDetail('customers') || $this->getUserDetail('customers') == '-1') { @@ -600,7 +598,7 @@ class Customers extends ApiCommand implements ResourceEntity 'adminid' => $this->getUserDetail('adminid'), 'deflang' => $def_language ), true, true); - $mail_subject = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $lng['mails']['createcustomer']['subject']), $replace_arr)); + $mail_subject = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $this->lng['mails']['createcustomer']['subject']), $replace_arr)); $result_stmt = Database::prepare(" SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` @@ -609,7 +607,7 @@ class Customers extends ApiCommand implements ResourceEntity 'adminid' => $this->getUserDetail('adminid'), 'deflang' => $def_language ), true, true); - $mail_body = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $lng['mails']['createcustomer']['mailbody']), $replace_arr)); + $mail_body = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $this->lng['mails']['createcustomer']['mailbody']), $replace_arr)); $_mailerror = false; try { diff --git a/lib/classes/api/commands/class.FpmDaemons.php b/lib/classes/api/commands/class.FpmDaemons.php index 205d6be8..c604e53c 100644 --- a/lib/classes/api/commands/class.FpmDaemons.php +++ b/lib/classes/api/commands/class.FpmDaemons.php @@ -52,7 +52,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity } if (empty($configs)) { - $configs[] = $lng['admin']['phpsettings']['notused']; + $configs[] = $this->lng['admin']['phpsettings']['notused']; } $row['configs'] = $configs; diff --git a/lib/classes/api/commands/class.Froxlor.php b/lib/classes/api/commands/class.Froxlor.php index 8483e5a5..21c0951e 100644 --- a/lib/classes/api/commands/class.Froxlor.php +++ b/lib/classes/api/commands/class.Froxlor.php @@ -26,14 +26,12 @@ class Froxlor extends ApiCommand */ public function checkUpdate() { - global $version, $branding; - if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { // log our actions $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] checking for updates"); // check for new version - define('UPDATE_URI', "https://version.froxlor.org/Froxlor/api/" . $version); + define('UPDATE_URI', "https://version.froxlor.org/Froxlor/api/" . $this->version); $latestversion = HttpClient::urlGet(UPDATE_URI); $latestversion = explode('|', $latestversion); @@ -44,7 +42,7 @@ class Froxlor extends ApiCommand // add the branding so debian guys are not gettings confused // about their version-number - $version_label = $_version . $branding; + $version_label = $_version . $this->branding; $version_link = $_link; $message_addinfo = $_message; @@ -53,7 +51,7 @@ class Froxlor extends ApiCommand // check for customized version to not output // "There is a newer version of froxlor" besides the error-message $isnewerversion = - 1; - } elseif (version_compare2($version, $_version) == - 1) { + } elseif (version_compare2($this->version, $_version) == - 1) { // there is a newer version - yay $isnewerversion = 1; } else { @@ -64,7 +62,7 @@ class Froxlor extends ApiCommand // anzeige über version-status mit ggfls. formular // zum update schritt #1 -> download if ($isnewerversion == 1) { - $text = 'There is a newer version available: "' . $_version . '" (Your current version is: ' . $version . ')'; + $text = 'There is a newer version available: "' . $_version . '" (Your current version is: ' . $this->version . ')'; return $this->response(200, "successfull", array( 'message' => $text, 'link' => $version_link, @@ -82,13 +80,15 @@ class Froxlor extends ApiCommand } /** - * @TODO import settings + * + * @todo import settings */ public function importSettings() {} /** - * @TODO export settings to file + * + * @todo export settings to file */ public function exportSettings() {} @@ -100,21 +100,24 @@ class Froxlor extends ApiCommand */ public function listSettings() { - $sel_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_SETTINGS . "` ORDER BY settinggroup ASC, varname ASC - "); - Database::pexecute($sel_stmt, null, true, true); - $result = array(); - while ($row = $sel_stmt->fetch(PDO::FETCH_ASSOC)) { - $result[] = array( - 'key' => $row['settinggroup'] . '.' . $row['varname'], - 'value' => $row['value'] - ); + if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { + $sel_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_SETTINGS . "` ORDER BY settinggroup ASC, varname ASC + "); + Database::pexecute($sel_stmt, null, true, true); + $result = array(); + while ($row = $sel_stmt->fetch(PDO::FETCH_ASSOC)) { + $result[] = array( + 'key' => $row['settinggroup'] . '.' . $row['varname'], + 'value' => $row['value'] + ); + } + return $this->response(200, "successfull", array( + 'count' => count($result), + 'list' => $result + )); } - return $this->response(200, "successfull", array( - 'count' => count($result), - 'list' => $result - )); + throw new Exception("Not allowed to execute given command.", 403); } /** @@ -122,7 +125,7 @@ class Froxlor extends ApiCommand * * @param string $key * settinggroup.varname couple - * + * * @throws Exception * @return string */ @@ -142,12 +145,15 @@ class Froxlor extends ApiCommand * settinggroup.varname couple * @param string $value * optional the new value, default is '' - * + * * @throws Exception * @return string */ public function updateSetting() { + // currently not implemented as it required validation too so no wrong settings are being stored via API + throw new Exception("Not available yet.", 501); + if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { $setting = $this->getParam('key'); $value = $this->getParam('value', true, ''); diff --git a/lib/classes/api/commands/class.PhpSettings.php b/lib/classes/api/commands/class.PhpSettings.php index 8d0a5dd8..db10ac8d 100644 --- a/lib/classes/api/commands/class.PhpSettings.php +++ b/lib/classes/api/commands/class.PhpSettings.php @@ -81,7 +81,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity } if (empty($domains)) { - $domains[] = $lng['admin']['phpsettings']['notused']; + $domains[] = $this->lng['admin']['phpsettings']['notused']; } // check whether this is our default config From 1c4ecdffbf2cce2f4f2036944bdb9efb38958bdc Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Fri, 23 Feb 2018 18:24:10 +0100 Subject: [PATCH 052/746] use correct dbserver for getting mysql-size info in Mysqls.get() Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Mysqls.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/classes/api/commands/class.Mysqls.php b/lib/classes/api/commands/class.Mysqls.php index fd2ec191..e55c6651 100644 --- a/lib/classes/api/commands/class.Mysqls.php +++ b/lib/classes/api/commands/class.Mysqls.php @@ -98,7 +98,7 @@ class Mysqls extends ApiCommand implements ResourceEntity } $result = Database::pexecute_first($result_stmt, $params, true, true); if ($result) { - Database::needRoot(true, $dbserver); + Database::needRoot(true, $result['dbserver']); $mbdata_stmt = Database::prepare(" SELECT SUM(data_length + index_length) as MB FROM information_schema.TABLES WHERE table_schema = :table_schema From 344ca827e42b5dd1bea6beb75a26509459cd26a0 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Fri, 23 Feb 2018 19:46:57 +0100 Subject: [PATCH 053/746] beautify api-help in webinterface Signed-off-by: Michael Kaufmann (d00p) --- apihelp.php | 43 +- templates/Sparkle/apihelp/index.tpl | 5 - templates/Sparkle/assets/css/main.css | 1368 ++++++++++++++----------- 3 files changed, 811 insertions(+), 605 deletions(-) diff --git a/apihelp.php b/apihelp.php index 9e9be7a5..deee08d9 100644 --- a/apihelp.php +++ b/apihelp.php @@ -73,42 +73,57 @@ foreach ($output_arr as $module => $functions) { // sort by function ksort($functions); + $apihelp .= "

    ".$module."



    "; + // output ALL the functions foreach ($functions as $function => $funcdata) { - $apihelp .= "
    "; - $apihelp .= "

    " . ($funcdata['return_type'] == - 1 ? "no-return-type" : $funcdata['return_type']) . " "; - $apihelp .= "" . $module . "." . $function . "

    "; + $apihelp .= "
    "; + $apihelp .= "

    ".$module." - "; // description - if (strtoupper(substr($funcdata['head'], 0, 5)) == "@TODO") + if (strtoupper(substr($funcdata['head'], 0, 5)) == "@TODO") { $apihelp .= ""; + } $apihelp .= $funcdata['head']; - if (strtoupper(substr($funcdata['head'], 0, 5)) == "@TODO") + if (strtoupper(substr($funcdata['head'], 0, 5)) == "@TODO") { $apihelp .= ""; + } + $apihelp .= "

    "; + $apihelp .= "Command"." "; + $apihelp .= "".$module.".".$function."
    "; + // output ALL the params; if (count($funcdata['params_list']) > 0) { - $parms = "

    Parameters:
      "; + $parms = "
      Parameter
      "; + $parms .= ""; + $parms .= ""; + $parms .= ""; // separate and format them foreach ($funcdata['params_list'] as $index => $param) { - $parms .= "
    • "; + $parms .= "
    • "; + $parms .= ""; + $parms .= ""; } - $apihelp .= "" . $parms; + $parms .= "
      FieldTypeDescription
      ";
       				// check whether the parameter is optional
       				if (! empty($param['desc']) && strtolower(substr(trim($param['desc']), 0, 8)) == "optional") {
      -					$parms .= "optional ";
      +					$parms .= "".$param['name']."";
       					$param['desc'] = substr(trim($param['desc']), 8);
       					if (substr($param['desc'], 0, 1) == ',') {
       						$param['desc'] = substr(trim($param['desc']), 1);
       					}
      +				} else {
      +					$parms .= "".$param['name']."";
       				}
      -				$parms .= "" . (strtolower($param['type']) == 'unknown' ? "unknown" : $param['type']) . " " . $param['name'] . "";
      +				$parms .= "
      " . (strtolower($param['type']) == 'unknown' ? "unknown" : $param['type']).""; if (! empty($param['desc'])) { - $parms .= " " . trim($param['desc']); + $parms .= trim($param['desc']); } - $parms .= "
    • "; + $parms .= "
    • "; + $apihelp .= $parms; } - $apihelp .= "


    "; + $apihelp .= "
    Returns " . ($funcdata['return_type'] == - 1 ? "no-return-type" : $funcdata['return_type']); + $apihelp .= "
    "; } - $apihelp .= "
    "; } eval("echo \"" . getTemplate("apihelp/index", 1) . "\";"); diff --git a/templates/Sparkle/apihelp/index.tpl b/templates/Sparkle/apihelp/index.tpl index 793a57a8..867008dc 100644 --- a/templates/Sparkle/apihelp/index.tpl +++ b/templates/Sparkle/apihelp/index.tpl @@ -1,11 +1,6 @@ $header
    -

    - API help -

    -
    -
    {$apihelp}
    diff --git a/templates/Sparkle/assets/css/main.css b/templates/Sparkle/assets/css/main.css index 3d3f27bc..83a93250 100644 --- a/templates/Sparkle/assets/css/main.css +++ b/templates/Sparkle/assets/css/main.css @@ -1,98 +1,126 @@ @charset "UTF-8"; /* RESET */ -html,body,div,ul,ol,li,dl,dt,dd,h1,h2,h3,h4,h5,h6,pre,form,p,blockquote,fieldset,input { margin:0; padding:0; } -h1,h2,h3,h4,h5,h6,pre,code,address,caption,cite,code,em,strong,th { font-size:1em; font-weight:400; font-style:normal; } -ul,ol { list-style:none; } -fieldset,img { border:none; } -caption,th { text-align:left; } -table { border-collapse:collapse; border-spacing:0; } -article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section { display:block; } +html, body, div, ul, ol, li, dl, dt, dd, h1, h2, h3, h4, h5, h6, pre, + form, p, blockquote, fieldset, input { + margin: 0; + padding: 0; +} + +h1, h2, h3, h4, h5, h6, pre, code, address, caption, cite, code, em, + strong, th { + font-size: 1em; + font-weight: 400; + font-style: normal; +} + +ul, ol { + list-style: none; +} + +fieldset, img { + border: none; +} + +caption, th { + text-align: left; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +article, aside, details, figcaption, figure, footer, header, hgroup, + menu, nav, section { + display: block; +} /* TYPE */ -html,body { - font:12px/18px 'Lucida Grande','Lucida Sans Unicode',Helvetica,Arial,Verdana,sans-serif; +html, body { + font: 12px/18px 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, + Verdana, sans-serif; background-color: #f5f5f5; - color:#444; + color: #444; -webkit-font-smoothing: subpixel-antialiased; } body { - margin:0; - padding:0; + margin: 0; + padding: 0; } strong { - font-weight:600; + font-weight: 600; } .content { - background-color:#f7f8fa; - margin-top:53px; - min-width:100%; - border-bottom:1px solid #b6c0cd; + background-color: #f7f8fa; + margin-top: 53px; + min-width: 100%; + border-bottom: 1px solid #b6c0cd; } /* * main container */ .main { - margin-left:230px; - padding:30px; - background-color:#fff; - border-left:1px solid #b6c0cd; - margin-bottom:0; + margin-left: 230px; + padding: 30px; + background-color: #fff; + border-left: 1px solid #b6c0cd; + margin-bottom: 0; } .dark { - background:#f0f2f4; - border-bottom:1px solid #d1d5d8; + background: #f0f2f4; + border-bottom: 1px solid #d1d5d8; } header img { - padding:10px 0 10px 10px; + padding: 10px 0 10px 10px; } img.small { - height:30px; + height: 30px; } img.responsive { max-width: 100%; - height: auto; + height: auto; } h1 { - display:none; + display: none; font-size: 2em; } -h2,h3 { - margin:0 0 10px; - padding:0; - font-weight:700; +h2, h3 { + margin: 0 0 10px; + padding: 0; + font-weight: 700; } h2 { - font-size:24px; - font-weight:400; + font-size: 24px; + font-weight: 400; } h3 { - font-size:16px; + font-size: 16px; } h4 { - font-size:13px; + font-size: 13px; } img { - border:0; - vertical-align:middle; + border: 0; + vertical-align: middle; text-decoration: none; } td a { - text-decoration:none; + text-decoration: none; } .bradius { @@ -100,390 +128,395 @@ td a { } .topheader { - background:#f0f2f4; - background:rgba(240,242,244,0.85098); - top:0; - width:100%; - padding:2px 0 0 5px; - position:fixed; - z-index:100; + background: #f0f2f4; + background: rgba(240, 242, 244, 0.85098); + top: 0; + width: 100%; + padding: 2px 0 0 5px; + position: fixed; + z-index: 100; border-bottom: 1px solid #ddd; } .topheader_navigation { - float:right; - margin:17px 50px 0 0; + float: right; + margin: 17px 50px 0 0; } /* TOPHEADER NAV */ ul.topheadernav { - list-style-type:none; - font-size:12px; + list-style-type: none; + font-size: 12px; } ul.topheadernav li { - padding:0; - margin-left:50px; - float:left; - position:relative; + padding: 0; + margin-left: 50px; + float: left; + position: relative; } ul.topheadernav li a { - display:block; - text-decoration:none; - color:#0f3e4e; + display: block; + text-decoration: none; + color: #0f3e4e; } ul.topheadernav li a:hover { - color:#111; + color: #111; } ul.topheadernav li ul { - display:none; - background-color:#eee; - padding:5px; - box-shadow:0 1px 3px rgba(0,0,0,0.35); - margin-left:0; - border-radius:3px; + display: none; + background-color: #eee; + padding: 5px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35); + margin-left: 0; + border-radius: 3px; } ul.topheadernav li:hover ul { - display:block; - position:absolute; + display: block; + position: absolute; } ul.topheadernav li ul li { - font-size:11px; - margin-left:0; + font-size: 11px; + margin-left: 0; } ul.topheadernav li ul li a:hover { - color:#111; + color: #111; } .topheadernav img { - padding:0; - margin:-4px 0 0; + padding: 0; + margin: -4px 0 0; } + .topheadernav a.logoutlink { color: #cc0000; } .countbubble { - display:block; - font-size:9px; - color:#fff; - background-color:#d90000; - position:absolute; - padding:3px; - line-height:9px; - border-radius:3px; - right:-6px; - bottom:-4px; + display: block; + font-size: 9px; + color: #fff; + background-color: #d90000; + position: absolute; + padding: 3px; + line-height: 9px; + border-radius: 3px; + right: -6px; + bottom: -4px; } /* FOOTER */ footer { - clear:both; - text-align:center; - color:#888; - font-size:10px!important; - padding:10px 0; - bottom:0; + clear: both; + text-align: center; + color: #888; + font-size: 10px !important; + padding: 10px 0; + bottom: 0; } -footer a,footer a:active,footer a:visited { - color:#888; +footer a, footer a:active, footer a:visited { + color: #888; } footer img { - margin:0 2px 3px 0; - height:13px; + margin: 0 2px 3px 0; + height: 13px; } .login, .errorbox { - background-color:#fff; - margin:9%; - margin-left:auto; - margin-right:auto; - margin-bottom:12px; - width:500px; - box-shadow:rgba(0,0,0,0.34902) 0 1px 3px 0; + background-color: #fff; + margin: 9%; + margin-left: auto; + margin-right: auto; + margin-bottom: 12px; + width: 500px; + box-shadow: rgba(0, 0, 0, 0.34902) 0 1px 3px 0; } -.login div.warningcontainer, .login div.errorcontainer, .login div.successcontainer { - margin:10px!important; +.login div.warningcontainer, .login div.errorcontainer, .login div.successcontainer + { + margin: 10px !important; } .errorbox { - width:800px; + width: 800px; } .installsec { - margin-top:10px; - padding:0; - text-align:left; + margin-top: 10px; + padding: 0; + text-align: left; } .loginsec, .errorsec { - margin-top:10px; - padding:10px 0; - text-align:center; + margin-top: 10px; + padding: 10px 0; + text-align: center; } .errorsec { - padding:10px; + padding: 10px; } .loginsec form { - width:400px; - margin:0 auto; - text-align:left; + width: 400px; + margin: 0 auto; + text-align: left; } .loginsec fieldset { - border:0; - float:left; - clear:left; - width:100%; - margin:0 0 10px; - padding:0; + border: 0; + float: left; + clear: left; + width: 100%; + margin: 0 0 10px; + padding: 0; } .loginsec legend { - display:none; + display: none; } .loginsec label { - float:left; - width:10em; - margin-right:1em; - margin-top:6px; - text-align:right; + float: left; + width: 10em; + margin-right: 1em; + margin-top: 6px; + text-align: right; } .loginsec input[type="text"], .loginsec input[type="password"] { - width:183px; + width: 183px; } .loginsec select { - width:213px; + width: 213px; } p.submit { - text-align:right; - padding-right:46px; + text-align: right; + padding-right: 46px; } -.loginsec aside,.errorsec aside { - border-top:1px solid #d1d5d8; - clear:both; - float:none; - width:auto; - text-align:left; - padding:10px 10px 0; +.loginsec aside, .errorsec aside { + border-top: 1px solid #d1d5d8; + clear: both; + float: none; + width: auto; + text-align: left; + padding: 10px 10px 0; } aside.right { - text-align:right; + text-align: right; } .messagewrapper { - width:650px; - margin:0 auto; - padding:120px 0 0; - overflow:hidden; + width: 650px; + margin: 0 auto; + padding: 120px 0 0; + overflow: hidden; } .messagewrapperfull { - width:100%; - margin:0 auto; - padding:0; - overflow:hidden; + width: 100%; + margin: 0 auto; + padding: 0; + overflow: hidden; } .overviewsearch { - float:right; - font-size:80%; - text-align:right; + float: right; + font-size: 80%; + text-align: right; } .overviewsearch input[type="text"] { - width:150px; + width: 150px; } -.overviewsearch + table { - clear:right; +.overviewsearch+table { + clear: right; } .overviewadd { - padding:10px; - font-weight:700; + padding: 10px; + font-weight: 700; } /* * error message display */ .errorcontainer { - background:url(../img/icons/error_big.png) 15px 15px no-repeat rgb(242, 222, 222); - border:1px solid #ffc2ca; + background: url(../img/icons/error_big.png) 15px 15px no-repeat + rgb(242, 222, 222); + border: 1px solid #ffc2ca; padding: 15px 15px 15px 60px; - margin:10px 0; - overflow:hidden; - box-shadow:0 0 0 #000; + margin: 10px 0; + overflow: hidden; + box-shadow: 0 0 0 #000; border-radius: 4px; - color:rgb(169, 68, 66); + color: rgb(169, 68, 66); } .errortitle { - font-weight:700; + font-weight: 700; } .error { - font-weight:400!important; + font-weight: 400 !important; } /* * warning message display */ -.warningcontainer,.ui-dialog { - background:url(../img/icons/warning_big.png) 10px center no-repeat #fffecc; - border:1px solid #f3c37e; - padding:10px 10px 10px 68px!important; - margin:10px 0!important; - text-align:left!important; - overflow:hidden; - box-shadow:0 0 0 #000; +.warningcontainer, .ui-dialog { + background: url(../img/icons/warning_big.png) 10px center no-repeat + #fffecc; + border: 1px solid #f3c37e; + padding: 10px 10px 10px 68px !important; + margin: 10px 0 !important; + text-align: left !important; + overflow: hidden; + box-shadow: 0 0 0 #000; } .ui-dialog { - padding:10px!important; + padding: 10px !important; } -.warningtitle,.ui-dialog-titlebar { - font-weight:700; - color:#D57D00; +.warningtitle, .ui-dialog-titlebar { + font-weight: 700; + color: #D57D00; } -.warning,.ui-dialog-content { - color:#D57D00!important; +.warning, .ui-dialog-content { + color: #D57D00 !important; } /* * success message display */ .successcontainer { - background:url(../img/icons/ok_big.png) 10px center no-repeat #E2F9E3; - border:1px solid #9C9; - padding:10px 10px 10px 68px!important; - margin:10px 0!important; - text-align:left!important; - overflow:hidden; - box-shadow:0 0 0 #000; + background: url(../img/icons/ok_big.png) 10px center no-repeat #E2F9E3; + border: 1px solid #9C9; + padding: 10px 10px 10px 68px !important; + margin: 10px 0 !important; + text-align: left !important; + overflow: hidden; + box-shadow: 0 0 0 #000; } .successtitle { - font-weight:700; - color:#060!important; + font-weight: 700; + color: #060 !important; } .success { - font-weight:400!important; + font-weight: 400 !important; } /* * neutral/info message display */ .neutralcontainer { - background:url(../img/icons/info_big.png) 10px center no-repeat #d2eaf6; - border:1px solid #b7d8ed; - padding:10px 10px 10px 68px!important; - margin:10px 0!important; - text-align:left!important; - overflow:hidden; - box-shadow:0 0 0 #000; + background: url(../img/icons/info_big.png) 10px center no-repeat #d2eaf6; + border: 1px solid #b7d8ed; + padding: 10px 10px 10px 68px !important; + margin: 10px 0 !important; + text-align: left !important; + overflow: hidden; + box-shadow: 0 0 0 #000; } .neutraltitle { - font-weight:700; - color:#3188c1!important; + font-weight: 700; + color: #3188c1 !important; } .neutral { - font-weight:400!important; - color:#3188c1!important; + font-weight: 400 !important; + color: #3188c1 !important; } /* std hyperlink */ -a,a:active,a:visited { - color:#176fa1; - text-decoration:none; +a, a:active, a:visited { + color: #176fa1; + text-decoration: none; } a:hover { - text-decoration:underline; + text-decoration: underline; } a.active { - font-weight:700; + font-weight: 700; } /* navigation */ nav { - float:left; - width:230px; - background-color:#f7f8fa; - min-height:500px; + float: left; + width: 230px; + background-color: #f7f8fa; + min-height: 500px; padding-top: 10px; } nav div:first-child { - display:none; + display: none; } nav div:nth-child(2) { - border-top:0!important; + border-top: 0 !important; } .menuelement { - margin:0 15px; - padding:15px 0 15px 5px; - border-bottom:1px solid #e1e7f0; - border-top:1px solid #fff; + margin: 0 15px; + padding: 15px 0 15px 5px; + border-bottom: 1px solid #e1e7f0; + border-top: 1px solid #fff; } nav div:last-child { - border-bottom:0!important; + border-bottom: 0 !important; } .menuelement h4 { - background:transparent url(../img/icons/tag_blue.png) no-repeat center left; - font-weight:700; - margin:0; - padding:0 0 0 20px; - color:#626976; + background: transparent url(../img/icons/tag_blue.png) no-repeat center + left; + font-weight: 700; + margin: 0; + padding: 0 0 0 20px; + color: #626976; } .menuelement h4 a { - color:#626976; + color: #626976; } .menuelement ul { - list-style:none; - margin:3px 0 0; - padding:0; + list-style: none; + margin: 3px 0 0; + padding: 0; } .menuelement ul li { - margin:2px 0 2px 20px; - padding:0; + margin: 2px 0 2px 20px; + padding: 0; } .noborder { - width:100%; - border-spacing:0; - border-collapse:separate; - border:0; + width: 100%; + border-spacing: 0; + border-collapse: separate; + border: 0; } /* TABLES */ @@ -502,13 +535,14 @@ table thead th, table th { font-weight: 700; } -table tr.section:not(:first-child) th { +table tr.section:not (:first-child ) th { border-top: 1px solid #d1d5d8; } table tbody tr td { - border-bottom:1px solid #f1f2f3; + border-bottom: 1px solid #f1f2f3; } + table tbody tr:last-child td { border-bottom: none; } @@ -518,16 +552,16 @@ table.hl tbody tr:hover { } table tfoot tr td { - height:25px; - border-top:1px solid #d1d5d8; - background-color:#f2f8fa; + height: 25px; + border-top: 1px solid #d1d5d8; + background-color: #f2f8fa; padding-right: 0px; } table td { padding: 5px 10px; height: 25px; - min-height: 25px; + min-height: 25px; } table th.right, table td.right { @@ -537,45 +571,50 @@ table th.right, table td.right { table.tiny { width: 400px; } + table.middle { width: 600px; } + table.full { width: 100%; } + table.center { margin: 0 auto; } + table tr.top { vertical-align: top; } + tr.disabled td, tr.disabled td a { - color:#cfcfcf; + color: #cfcfcf; } /* ADMIN/CUSTOMER BARS */ .overviewcustomerextras { - line-height:15px; - font-size:10px; - width:250px; - padding-top:3px; - padding-bottom:3px; + line-height: 15px; + font-size: 10px; + width: 250px; + padding-top: 3px; + padding-bottom: 3px; } .overviewcustomerextras span { - width:60px; - float:left; + width: 60px; + float: left; } /* INPUT ELEMENTS */ input { - background:#fff url(../img/icons/text_align_left.png) no-repeat 5px 4px; + background: #fff url(../img/icons/text_align_left.png) no-repeat 5px 4px; color: #333; - padding:1px 4px 2px 24px; - height:23px; - border:1px solid #d9d9d9; - margin-bottom:5px; - border-radius:3px; + padding: 1px 4px 2px 24px; + height: 23px; + border: 1px solid #d9d9d9; + margin-bottom: 5px; + border-radius: 3px; } input[disabled], input[readonly] { @@ -584,274 +623,277 @@ input[disabled], input[readonly] { } textarea { - background:#fff url(../img/icons/text_align_left.png) no-repeat 5px 4px; + background: #fff url(../img/icons/text_align_left.png) no-repeat 5px 4px; color: #333; - padding:4px 4px 2px 24px; - border:1px solid #d9d9d9; + padding: 4px 4px 2px 24px; + border: 1px solid #d9d9d9; margin: 5px 0 5px 0; - border-radius:3px; + border-radius: 3px; } -input[type="text"],input[type="password"],input[type="text"] { - width:400px; +input[type="text"], input[type="password"], input[type="text"] { + width: 400px; margin-top: 5px; } input[type="password"] { - background:#fff url(../img/icons/lock.png) no-repeat 5px 4px; + background: #fff url(../img/icons/lock.png) no-repeat 5px 4px; } input[class="small"] { - width:auto; + width: auto; margin-top: 5px; } - /* * BUTTONS */ -input[type="button"],input[type="submit"],input[type="reset"],input[type="file"] { - margin:0 5px; - padding:5px 14px; - outline:0; - border:0; - background-color:#eee; - min-width:80px; - height:28px; - background-image:none; - border-width:0; +input[type="button"], input[type="submit"], input[type="reset"], input[type="file"] + { + margin: 0 5px; + padding: 5px 14px; + outline: 0; + border: 0; + background-color: #eee; + min-width: 80px; + height: 28px; + background-image: none; + border-width: 0; } -.loginsec input[type="button"],.loginsec input[type="submit"],.loginsec input[type="reset"] { - margin:0 1px; +.loginsec input[type="button"], .loginsec input[type="submit"], + .loginsec input[type="reset"] { + margin: 0 1px; } -input[type="button"]:hover,input[type="submit"]:hover,input[type="reset"]:hover { - color:#333; - background-color:#dcdcdc; +input[type="button"]:hover, input[type="submit"]:hover, input[type="reset"]:hover + { + color: #333; + background-color: #dcdcdc; } -input[type="button"]:active,input[type="submit"]:active,input[type="reset"]:active { - -webkit-box-shadow:inset 0 1px 8px rgba(0,0,0,0.25); - -moz-box-shadow:inset 0 1px 8px rgba(0,0,0,0.25); - box-shadow:inset 0 1px 8px rgba(0,0,0,0.25); - color:#fff!important; +input[type="button"]:active, input[type="submit"]:active, input[type="reset"]:active + { + -webkit-box-shadow: inset 0 1px 8px rgba(0, 0, 0, 0.25); + -moz-box-shadow: inset 0 1px 8px rgba(0, 0, 0, 0.25); + box-shadow: inset 0 1px 8px rgba(0, 0, 0, 0.25); + color: #fff !important; } -input[type="submit"],input[class="yesbutton"] { - color:#fff; - background-color:#35aa47; +input[type="submit"], input[class="yesbutton"] { + color: #fff; + background-color: #35aa47; } -input[type="submit"]:hover,input[class="yesbutton"]:hover { - color:#fff; - background-color:#1d943b; +input[type="submit"]:hover, input[class="yesbutton"]:hover { + color: #fff; + background-color: #1d943b; } -input[class="submit"]:active,input[class="yesbutton"]:active { - background-color:#35aa47; +input[class="submit"]:active, input[class="yesbutton"]:active { + background-color: #35aa47; } -input[class="nobutton"],input[type="reset"] { - color:#fff; - background-color:#d84a38; +input[class="nobutton"], input[type="reset"] { + color: #fff; + background-color: #d84a38; } input[type="file"] { - background-color:#FFFFFF; - padding-left:0px; + background-color: #FFFFFF; + padding-left: 0px; } -input[class="nobutton"]:hover,input[type="reset"]:hover { - color:#fff; - background-color:#c53727; +input[class="nobutton"]:hover, input[type="reset"]:hover { + color: #fff; + background-color: #c53727; } -input[class="nobutton"]:active,input[type="reset"]:active { - background-color:#dd4b39; +input[class="nobutton"]:active, input[type="reset"]:active { + background-color: #dd4b39; } input[type="checkbox"] { - background:#dae7ee; - padding:0; - margin:0 5px 0 0; - vertical-align:middle; + background: #dae7ee; + padding: 0; + margin: 0 5px 0 0; + vertical-align: middle; /* Fix Safari-Bug */ height: auto; } select { - padding:6px 4px 7px 24px; + padding: 6px 4px 7px 24px; color: #333; - border:1px solid #d9d9d9; - margin:5px 5px 5px 0; - border-radius:3px; + border: 1px solid #d9d9d9; + margin: 5px 5px 5px 0; + border-radius: 3px; background: url(../img/icons/down.png) no-repeat 9px; - -webkit-appearance:none; - -moz-appearance:none; - appearance:none; - min-width:170px; - text-indent:0.01px; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + min-width: 170px; + text-indent: 0.01px; text-overflow: ''; } select[multiple="multiple"] { - height:auto; - background-image:none; - padding:4px; + height: auto; + background-image: none; + padding: 4px; } .customer_add { - margin-top:15px; + margin-top: 15px; } .dboarditem { margin-bottom: 20px; - border:1px solid #d1d5d8; + border: 1px solid #d1d5d8; border-radius: 3px; width: 100%; } .dboarditemfull { - position:relative; - overflow:hidden; - width:100%; - margin-top:10px; - margin-bottom:10px; - padding:0; - border:1px solid #d1d5d8; + position: relative; + overflow: hidden; + width: 100%; + margin-top: 10px; + margin-bottom: 10px; + padding: 0; + border: 1px solid #d1d5d8; } -.dboarditem table,.dboarditemfull table { - width:100%; - border:0; +.dboarditem table, .dboarditemfull table { + width: 100%; + border: 0; } -.dboarditem th,.dboarditemfull th { - border-bottom:1px solid #d1d5d8; - height:25px!important; - padding:5px 0 5px 8px; - font-weight:700; +.dboarditem th, .dboarditemfull th { + border-bottom: 1px solid #d1d5d8; + height: 25px !important; + padding: 5px 0 5px 8px; + font-weight: 700; } -.dboarditem td,.dboarditemfull td { - border-right:0; - border-bottom:1px solid #f1f2f3; - padding:4px 0 4px 8px; +.dboarditem td, .dboarditemfull td { + border-right: 0; + border-bottom: 1px solid #f1f2f3; + padding: 4px 0 4px 8px; } .cronjobtask li { - background-image:url(../img/icons/clock.png); - background-repeat:no-repeat; - background-position:0 1px; - padding-left:18px; + background-image: url(../img/icons/clock.png); + background-repeat: no-repeat; + background-position: 0 1px; + padding-left: 18px; } .overviewheading { - vertical-align:top; - line-height:36px; - height:36px; + vertical-align: top; + line-height: 36px; + height: 36px; } .overviewheading h3 { - display:inline; + display: inline; } /* PROGRESS BAR */ .progress { - height:15px; - width:150px; - margin:2px 0 2px 10px; - overflow:hidden; - background-color:#f7f7f7; - -webkit-border-radius:3px; - -moz-border-radius:3px; - border-radius:3px; - -webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1); - -moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1); - box-shadow:inset 0 1px 2px rgba(0,0,0,0.1); - text-align:center; - color:#999; + height: 15px; + width: 150px; + margin: 2px 0 2px 10px; + overflow: hidden; + background-color: #f7f7f7; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + text-align: center; + color: #999; } .progress .bar { - width:1px; - height:18px; - font-size:12px; - color:#fff; - text-align:center; - text-shadow:0 -1px 0 rgba(0,0,0,0.25); - background-color:#0e90d2; - -webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15); - -moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15); - box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15); - -webkit-transition:width 6s ease; - -moz-transition:width 6s ease; - -ms-transition:width 6s ease; - -o-transition:width 6s ease; - transition:width 6s ease; + width: 1px; + height: 18px; + font-size: 12px; + color: #fff; + text-align: center; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #0e90d2; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + -webkit-transition: width 6s ease; + -moz-transition: width 6s ease; + -ms-transition: width 6s ease; + -o-transition: width 6s ease; + transition: width 6s ease; } .progress-danger .bar { - background-color:#dd514c; + background-color: #dd514c; } .progress-warn .bar { - background-color:#e6b64e; + background-color: #e6b64e; } .maintable { - width:90%; + width: 90%; } .update_progess { - padding:2em; - text-align:left; + padding: 2em; + text-align: left; } .preconfig { - text-align:left; - margin-top:20px; - margin-bottom:5px; - margin-right:15px; - margin-left:15px; + text-align: left; + margin-top: 20px; + margin-bottom: 5px; + margin-right: 15px; + margin-left: 15px; } .preconfigitem { - padding:.15em; - border-bottom:1px solid #ccc; + padding: .15em; + border-bottom: 1px solid #ccc; } .preconfdesc { - display:block; - margin-bottom:.5em; - font-size:120%; + display: block; + margin-bottom: .5em; + font-size: 120%; } .strikethrough { - text-decoration:line-through; + text-decoration: line-through; } label.nobr { - display:inline; - padding:0; - margin:0 10px 0 0; + display: inline; + padding: 0; + margin: 0 10px 0 0; } .scrollup { - width:40px; - height:40px; - opacity:.3; - position:fixed; - bottom:50px; - left:95px; - display:none; - text-indent:-9999px; - background:url(../img/top.png) no-repeat; + width: 40px; + height: 40px; + opacity: .3; + position: fixed; + bottom: 50px; + left: 95px; + display: none; + text-indent: -9999px; + background: url(../img/top.png) no-repeat; } .nowrap { - white-space:nowrap; + white-space: nowrap; } .trafficchart { @@ -862,26 +904,26 @@ label.nobr { /* CANVAS STUFF */ .canvasitems { - position:relative; - overflow:hidden; - width:100%; - margin-top:0; - margin-bottom:10px; - padding:0 0 0 10px; + position: relative; + overflow: hidden; + width: 100%; + margin-top: 0; + margin-bottom: 10px; + padding: 0 0 0 10px; } .canvasbox { - width:130px; - margin:10px 20px 10px 0; - text-align:center; - float:left; - height:150px; - line-height:normal; + width: 130px; + margin: 10px 20px 10px 0; + text-align: center; + float: left; + height: 150px; + line-height: normal; } .canvasbox canvas { - width:120px; - margin-bottom:5px; + width: 120px; + margin-bottom: 5px; } /* NEWSFEED @@ -921,15 +963,15 @@ label.nobr { color:gray; }*/ .newsfeed { - margin: 0; - padding: 0; - list-style: none; + margin: 0; + padding: 0; + list-style: none; } .newsfeed li { - margin-bottom: 10px; - padding-bottom: 5px; - border-bottom: 1px dotted #999; + margin-bottom: 10px; + padding-bottom: 5px; + border-bottom: 1px dotted #999; } .newsfeed li:last-child { @@ -939,118 +981,121 @@ label.nobr { } .newsfeed li.left .newsfeed-body { - margin-left: 60px; + margin-left: 60px; } .newsfeed li.right .newsfeed-body { - margin-right: 60px; + margin-right: 60px; } .newsfeed li .newsfeed-body p { - margin: 0; + margin: 0; } -.panel .slidedown .glyphicon, -.newsfeed .glyphicon { - margin-right: 5px; +.panel .slidedown .glyphicon, .newsfeed .glyphicon { + margin-right: 5px; } .newsfeed-panel .panel-body { - height: 350px; - overflow-y: scroll; + height: 350px; + overflow-y: scroll; } /* TIPPER */ .tipper-positioner { - left:-99999px; - position:absolute; + left: -99999px; + position: absolute; pointer-events: none; - top:-99999px; + top: -99999px; } .tipper-positioner .tipper-wrapper { - position:relative; + position: relative; } .tipper-positioner .tipper-content { - background:rgba(0,0,0,0.85); - border-radius:3px; - color:#fff; - display:block; - font-family:sans-serif; - font-size:11px; - margin:0; - padding:4px 8px; - white-space:nowrap; + background: rgba(0, 0, 0, 0.85); + border-radius: 3px; + color: #fff; + display: block; + font-family: sans-serif; + font-size: 11px; + margin: 0; + padding: 4px 8px; + white-space: nowrap; } .tipper-positioner .tipper-caret { - background:url(../img/tipper.png) no-repeat; - display:block; - height:11px; - margin:0; - overflow:hidden; - position:absolute; - width:5px; + background: url(../img/tipper.png) no-repeat; + display: block; + height: 11px; + margin: 0; + overflow: hidden; + position: absolute; + width: 5px; } .tipper-positioner.right { - box-shadow:1px 0 3px rgba(0,0,0,0.25); + box-shadow: 1px 0 3px rgba(0, 0, 0, 0.25); } .tipper-positioner.right .tipper-caret { - background-position:left center; - left:-5px; - top:0; + background-position: left center; + left: -5px; + top: 0; } .tipper-positioner.left { - box-shadow:-1px 0 3px rgba(0,0,0,0.25); + box-shadow: -1px 0 3px rgba(0, 0, 0, 0.25); } .tipper-positioner.left .tipper-caret { - background-position:right center; - right:-5px; - top:0; + background-position: right center; + right: -5px; + top: 0; } -.tipper-positioner.top .tipper-caret,.tipper-positioner.bottom .tipper-caret { - display:block; - float:none; - height:5px; - margin:0 auto; - width:11px; +.tipper-positioner.top .tipper-caret, .tipper-positioner.bottom .tipper-caret + { + display: block; + float: none; + height: 5px; + margin: 0 auto; + width: 11px; } .tipper-positioner.top { - box-shadow:0 -1px 3px rgba(0,0,0,0.25); + box-shadow: 0 -1px 3px rgba(0, 0, 0, 0.25); } .tipper-positioner.top .tipper-caret { - background-position:center bottom; - bottom:-5px; - left:0; + background-position: center bottom; + bottom: -5px; + left: 0; } .tipper-positioner.bottom { - box-shadow:0 1px 3px rgba(0,0,0,0.25); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25); } .tipper-positioner.bottom .tipper-caret { - background-position:center top; - top:-5px; - left:0; + background-position: center top; + top: -5px; + left: 0; } .red { color: #ff0000; } + .green { color: green; } + .orange { color: orange; } + .blue { color: blue; } @@ -1058,19 +1103,24 @@ label.nobr { .phpinfo, .overflow { overflow: scroll; } + .phperror { margin-top: 50px; margin-bottom: -55px; } + .clear { clear: both; } + .hidden { display: none; } + div.left { float: left; } + div.right { float: right; } @@ -1090,16 +1140,18 @@ div.right { .grid-u { display: inline-block; - zoom: 1; *display: inline; + zoom: 1; + *display: inline; letter-spacing: normal; word-spacing: normal; vertical-align: top; text-rendering: auto; } -.grid-u-1,.grid-u-1-2 { +.grid-u-1, .grid-u-1-2 { display: inline-block; - zoom: 1; *display: inline; + zoom: 1; + *display: inline; letter-spacing: normal; word-spacing: normal; vertical-align: top; @@ -1107,7 +1159,7 @@ div.right { } .grid-u-1 { - display:block; + display: block; } .grid-u-1-2 { @@ -1122,347 +1174,466 @@ div.right { .tablesorter-header-inner { margin-left: 15px; } + table thead th.tablesorter-headerUnSorted { - background-image: url(data:image/gif;base64,R0lGODlhFQAJAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAkAAAIXjI+AywnaYnhUMoqt3gZXPmVg94yJVQAAOw==); + background-image: + url(data:image/gif;base64,R0lGODlhFQAJAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAkAAAIXjI+AywnaYnhUMoqt3gZXPmVg94yJVQAAOw==); background-repeat: no-repeat; background-position: left center; } + table thead th.tablesorter-headerAsc { - background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjI8Bya2wnINUMopZAQA7); + background-image: + url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjI8Bya2wnINUMopZAQA7); background-repeat: no-repeat; background-position: left center; } + table thead th.tablesorter-headerDesc { - background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjB+gC+jP2ptn0WskLQA7); + background-image: + url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjB+gC+jP2ptn0WskLQA7); background-repeat: no-repeat; background-position: left center; } /* PROGRESS */ .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="0"] { - width: 0%; + width: 0%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="1"] { - width: 1%; + width: 1%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="2"] { - width: 2%; + width: 2%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="3"] { - width: 3%; + width: 3%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="4"] { - width: 4%; + width: 4%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="5"] { - width: 5%; + width: 5%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="6"] { - width: 6%; + width: 6%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="7"] { - width: 7%; + width: 7%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="8"] { - width: 8%; + width: 8%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="9"] { - width: 9%; + width: 9%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="10"] { - width: 10%; + width: 10%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="11"] { - width: 11%; + width: 11%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="12"] { - width: 12%; + width: 12%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="13"] { - width: 13%; + width: 13%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="14"] { - width: 14%; + width: 14%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="15"] { - width: 15%; + width: 15%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="16"] { - width: 16%; + width: 16%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="17"] { - width: 17%; + width: 17%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="18"] { - width: 18%; + width: 18%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="19"] { - width: 19%; + width: 19%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="20"] { - width: 20%; + width: 20%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="21"] { - width: 21%; + width: 21%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="22"] { - width: 22%; + width: 22%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="23"] { - width: 23%; + width: 23%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="24"] { - width: 24%; + width: 24%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="25"] { - width: 25%; + width: 25%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="26"] { - width: 26%; + width: 26%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="27"] { - width: 27%; + width: 27%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="28"] { - width: 28%; + width: 28%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="29"] { - width: 29%; + width: 29%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="30"] { - width: 30%; + width: 30%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="31"] { - width: 31%; + width: 31%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="32"] { - width: 32%; + width: 32%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="33"] { - width: 33%; + width: 33%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="34"] { - width: 34%; + width: 34%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="35"] { - width: 35%; + width: 35%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="36"] { - width: 36%; + width: 36%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="37"] { - width: 37%; + width: 37%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="38"] { - width: 38%; + width: 38%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="39"] { - width: 39%; + width: 39%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="40"] { - width: 40%; + width: 40%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="41"] { - width: 41%; + width: 41%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="42"] { - width: 42%; + width: 42%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="43"] { - width: 43%; + width: 43%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="44"] { - width: 44%; + width: 44%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="45"] { - width: 45%; + width: 45%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="46"] { - width: 46%; + width: 46%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="47"] { - width: 47%; + width: 47%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="48"] { - width: 48%; + width: 48%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="49"] { - width: 49%; + width: 49%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="50"] { - width: 50%; + width: 50%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="51"] { - width: 51%; + width: 51%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="52"] { - width: 52%; + width: 52%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="53"] { - width: 53%; + width: 53%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="54"] { - width: 54%; + width: 54%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="55"] { - width: 55%; + width: 55%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="56"] { - width: 56%; + width: 56%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="57"] { - width: 57%; + width: 57%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="58"] { - width: 58%; + width: 58%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="59"] { - width: 59%; + width: 59%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="60"] { - width: 60%; + width: 60%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="61"] { - width: 61%; + width: 61%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="62"] { - width: 62%; + width: 62%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="63"] { - width: 63%; + width: 63%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="64"] { - width: 64%; + width: 64%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="65"] { - width: 65%; + width: 65%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="66"] { - width: 66%; + width: 66%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="67"] { - width: 67%; + width: 67%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="68"] { - width: 68%; + width: 68%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="69"] { - width: 69%; + width: 69%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="70"] { - width: 70%; + width: 70%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="71"] { - width: 71%; + width: 71%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="72"] { - width: 72%; + width: 72%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="73"] { - width: 73%; + width: 73%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="74"] { - width: 74%; + width: 74%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="75"] { - width: 75%; + width: 75%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="76"] { - width: 76%; + width: 76%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="77"] { - width: 77%; + width: 77%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="78"] { - width: 78%; + width: 78%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="79"] { - width: 79%; + width: 79%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="80"] { - width: 80%; + width: 80%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="81"] { - width: 81%; + width: 81%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="82"] { - width: 82%; + width: 82%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="83"] { - width: 83%; + width: 83%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="84"] { - width: 84%; + width: 84%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="85"] { - width: 85%; + width: 85%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="86"] { - width: 86%; + width: 86%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="87"] { - width: 87%; + width: 87%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="88"] { - width: 88%; + width: 88%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="89"] { - width: 89%; + width: 89%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="90"] { - width: 90%; + width: 90%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="91"] { - width: 91%; + width: 91%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="92"] { - width: 92%; + width: 92%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="93"] { - width: 93%; + width: 93%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="94"] { - width: 94%; + width: 94%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="95"] { - width: 95%; + width: 95%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="96"] { - width: 96%; + width: 96%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="97"] { - width: 97%; + width: 97%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="98"] { - width: 98%; + width: 98%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="99"] { - width: 99%; + width: 99%; } + .bar[aria-valuemax="100"][aria-valuemin="0"][aria-valuenow="100"] { - width: 100%; + width: 100%; } .update-step { - margin-left: 5em; - font-weight: bold; + margin-left: 5em; + font-weight: bold; } -.update-step-ok { color: #1dcd00; } -.update-step-warn { color: #db7100; } -.update-step-err { color: #ff0000; } -.update-step-unknown { color: #000000; } +.update-step-ok { + color: #1dcd00; +} -.align-top { vertical-align:top; } +.update-step-warn { + color: #db7100; +} + +.update-step-err { + color: #ff0000; +} + +.update-step-unknown { + color: #000000; +} + +.align-top { + vertical-align: top; +} .code-block { - width: 500px; - border: 1px solid #ccc; - padding: 4px; + width: 500px; + border: 1px solid #ccc; + padding: 4px; } .notes_block { - display: none; + display: none; } .nolbr { @@ -1477,9 +1648,10 @@ table thead th.tablesorter-headerDesc { } .shell, .filecontent { - font-family: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace; + font-family: Consolas, Monaco, Lucida Console, Liberation Mono, + DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace; border: 1px solid #d1d5d8; - border-radius: 3px; + border-radius: 3px; padding: 10px; background-image: none; -webkit-box-sizing: border-box; @@ -1494,7 +1666,7 @@ table thead th.tablesorter-headerDesc { } fieldset.file { - border:1px solid #d1d5d8; + border: 1px solid #d1d5d8; border-radius: 3px; padding: 5px; margin-bottom: 10px; @@ -1535,3 +1707,27 @@ td.size-20 { td.size-50 { width: 50%; } + +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); +} + +.label { + border-radius: 0; + text-shadow: none; + font-size: 14px; + font-weight: normal; + padding: 3px 5px 3px; + background-color: #abbac3 !important +} + +.label[class*="span"][class*="arrow"] { + min-height: 0 +} From 81d6a856d9e3d38ab0b2474aad855a37a04847f4 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Fri, 23 Feb 2018 19:51:06 +0100 Subject: [PATCH 054/746] forgot to hit save :P Signed-off-by: Michael Kaufmann (d00p) --- templates/Sparkle/assets/css/main.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/templates/Sparkle/assets/css/main.css b/templates/Sparkle/assets/css/main.css index 83a93250..5e61b727 100644 --- a/templates/Sparkle/assets/css/main.css +++ b/templates/Sparkle/assets/css/main.css @@ -1723,9 +1723,10 @@ td.size-50 { border-radius: 0; text-shadow: none; font-size: 14px; - font-weight: normal; + font-weight: bold; padding: 3px 5px 3px; - background-color: #abbac3 !important + background-color: #abbac3 !important; + color: #fff !important; } .label[class*="span"][class*="arrow"] { From 9a61a56732466368762dbc18d34e3441aead5d04 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sat, 24 Feb 2018 10:57:52 +0100 Subject: [PATCH 055/746] enhance phpdoc and add @access to specify which usergroup can use the ApiCommands; add --import-settings parameter to config-services.php CLI script to gain even more automatism when setting up Signed-off-by: Michael Kaufmann (d00p) --- apihelp.php | 27 +++-- install/scripts/config-services.php | 24 ++++ lib/classes/api/commands/class.Admins.php | 30 ++++- lib/classes/api/commands/class.Customers.php | 105 ++++++++++-------- lib/classes/api/commands/class.FpmDaemons.php | 21 ++++ lib/classes/api/commands/class.Froxlor.php | 24 +++- .../api/commands/class.IpsAndPorts.php | 24 +++- lib/classes/api/commands/class.Mysqls.php | 3 + .../api/commands/class.PhpSettings.php | 21 ++++ 9 files changed, 217 insertions(+), 62 deletions(-) diff --git a/apihelp.php b/apihelp.php index deee08d9..bcc3169b 100644 --- a/apihelp.php +++ b/apihelp.php @@ -1,7 +1,7 @@ listFunctions(); } catch (Exception $e) { @@ -48,7 +48,8 @@ foreach ($m_arr as $module) { $output_arr[$module['module']][$module['function']] = array( 'return_type' => (isset($module['return']['type']) && $module['return']['type'] != "" ? $module['return']['type'] : - 1), 'params_list' => array(), - 'head' => $module['head'] + 'head' => $module['head'], + 'access' => isset($module['access']) ? $module['access'] : null ); if (isset($module['params']) && is_array($module['params'])) { @@ -73,12 +74,12 @@ foreach ($output_arr as $module => $functions) { // sort by function ksort($functions); - $apihelp .= "

    ".$module."



    "; + $apihelp .= "

    " . $module . "



    "; // output ALL the functions foreach ($functions as $function => $funcdata) { $apihelp .= "
    "; - $apihelp .= "

    ".$module." - "; + $apihelp .= "

    " . $module . " - "; // description if (strtoupper(substr($funcdata['head'], 0, 5)) == "@TODO") { $apihelp .= ""; @@ -88,8 +89,12 @@ foreach ($output_arr as $module => $functions) { $apihelp .= ""; } $apihelp .= "

    "; - $apihelp .= "Command"." "; - $apihelp .= "".$module.".".$function."
    "; + $apihelp .= "Command" . " "; + $apihelp .= "" . $module . "." . $function . "
    "; + if (isset($funcdata['access']['groups']) && ! empty($funcdata['access']['groups'])) { + $apihelp .= "
    Access: "; + $apihelp .= $funcdata['access']['groups'] . "
    "; + } // output ALL the params; if (count($funcdata['params_list']) > 0) { @@ -102,15 +107,15 @@ foreach ($output_arr as $module => $functions) { $parms .= "
    ";
     				// check whether the parameter is optional
     				if (! empty($param['desc']) && strtolower(substr(trim($param['desc']), 0, 8)) == "optional") {
    -					$parms .= "".$param['name']."";
    +					$parms .= "" . $param['name'] . "";
     					$param['desc'] = substr(trim($param['desc']), 8);
     					if (substr($param['desc'], 0, 1) == ',') {
     						$param['desc'] = substr(trim($param['desc']), 1);
     					}
     				} else {
    -					$parms .= "".$param['name']."";
    +					$parms .= "" . $param['name'] . "";
     				}
    -				$parms .= "
    " . (strtolower($param['type']) == 'unknown' ? "unknown" : $param['type']).""; + $parms .= "" . (strtolower($param['type']) == 'unknown' ? "unknown" : $param['type']) . ""; $parms .= ""; if (! empty($param['desc'])) { $parms .= trim($param['desc']); diff --git a/install/scripts/config-services.php b/install/scripts/config-services.php index 69d1b654..1799ec4e 100755 --- a/install/scripts/config-services.php +++ b/install/scripts/config-services.php @@ -43,6 +43,7 @@ class ConfigServicesCmd extends CmdLineHandler public static $params = array( 'create', 'apply', + 'import-settings', 'daemon', 'list-daemons', 'froxlor-dir', @@ -68,6 +69,10 @@ class ConfigServicesCmd extends CmdLineHandler self::println("--daemon\t\tWhen running --apply you can specify a daemon. This will be the only service that gets configured"); self::println("\t\t\tExample: --apply=/path/to/my-config.json --daemon=apache24"); self::println(""); + self::println(""); + self::println("--import-settings\tImport settings from another froxlor installation. This should be done prior to running --apply or alternatively in the same command together."); + self::println("\t\t\tExample: --import-settings=/path/to/Froxlor_settings-[version]-[dbversion]-[date].json"); + self::println(""); self::println("--froxlor-dir\t\tpath to froxlor installation"); self::println("\t\t\tExample: --froxlor-dir=/var/www/froxlor/"); self::println(""); @@ -115,10 +120,15 @@ class Action require FROXLOR_INSTALL_DIR . '/lib/tables.inc.php'; require FROXLOR_INSTALL_DIR . '/lib/functions.php'; require FROXLOR_INSTALL_DIR . '/lib/classes/settings/class.Settings.php'; + require FROXLOR_INSTALL_DIR . '/lib/classes/settings/class.SImExporter.php'; require FROXLOR_INSTALL_DIR . '/lib/classes/config/class.ConfigParser.php'; require FROXLOR_INSTALL_DIR . '/lib/classes/config/class.ConfigService.php'; require FROXLOR_INSTALL_DIR . '/lib/classes/config/class.ConfigDaemon.php'; + if (array_key_exists("import-settings", $this->_args)) { + $this->_importSettings(); + } + if (array_key_exists("create", $this->_args)) { $this->_createConfig(); } elseif (array_key_exists("apply", $this->_args)) { @@ -128,6 +138,20 @@ class Action } } + private function _importSettings() + { + if (! is_file($this->_args["import-settings"])) { + throw new Exception("Given settings file is not a file"); + } elseif (! file_exists($this->_args["import-settings"])) { + throw new Exception("Given settings file cannot be found ('" . $this->_args["import-settings"] . "')"); + } elseif (! is_readable($this->_args["import-settings"])) { + throw new Exception("Given settings file cannot be read ('" . $this->_args["import-settings"] . "')"); + } + $imp_content = file_get_contents($this->_args["import-settings"]); + SImExporter::import($imp_content); + CmdLineHandler::printsucc("Successfully imported settings from '" . $this->_args["import-settings"] . "'"); + } + private function _createConfig() { $_daemons_config = array( diff --git a/lib/classes/api/commands/class.Admins.php b/lib/classes/api/commands/class.Admins.php index 88ee04e1..a37a894c 100644 --- a/lib/classes/api/commands/class.Admins.php +++ b/lib/classes/api/commands/class.Admins.php @@ -21,6 +21,8 @@ class Admins extends ApiCommand implements ResourceEntity /** * lists all admin entries * + * @access admin + * @throws Exception * @return array count|list */ public function list() @@ -52,7 +54,8 @@ class Admins extends ApiCommand implements ResourceEntity * optional, the admin-id * @param string $loginname * optional, the loginname - * + * + * @access admin * @throws Exception * @return array */ @@ -84,6 +87,13 @@ class Admins extends ApiCommand implements ResourceEntity throw new Exception("Not allowed to execute given command.", 403); } + /** + * create a new admin user + * + * @access admin + * @throws Exception + * @return array + */ public function add() { if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { @@ -290,6 +300,18 @@ class Admins extends ApiCommand implements ResourceEntity throw new Exception("Not allowed to execute given command.", 403); } + /** + * update an admin user by given id or loginname + * + * @param int $id + * optional, the admin-id + * @param string $loginname + * optional, the loginname + * + * @access admin + * @throws Exception + * @return array + */ public function update() { if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { @@ -551,7 +573,8 @@ class Admins extends ApiCommand implements ResourceEntity * optional, the admin-id * @param string $loginname * optional, the loginname - * + * + * @access admin * @throws Exception * @return array */ @@ -619,7 +642,8 @@ class Admins extends ApiCommand implements ResourceEntity * optional, the admin-id * @param string $loginname * optional, the loginname - * + * + * @access admin * @throws Exception * @return array */ diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index 26ae4568..424bdf92 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -21,6 +21,7 @@ class Customers extends ApiCommand implements ResourceEntity /** * lists all customer entries * + * @access admin * @return array count|list */ public function list() @@ -61,20 +62,21 @@ class Customers extends ApiCommand implements ResourceEntity * @param string $loginname * optional, the loginname * + * @access admin, customer * @throws Exception * @return array */ public function get() { + $id = $this->getParam('id', true, 0); + $ln_optional = ($id <= 0 ? false : true); + $loginname = $this->getParam('loginname', $ln_optional, ''); + + if ($id <= 0 && empty($loginname)) { + throw new Exception("Either 'id' or 'loginname' parameter must be given", 406); + } + if ($this->isAdmin()) { - $id = $this->getParam('id', true, 0); - $ln_optional = ($id <= 0 ? false : true); - $loginname = $this->getParam('loginname', $ln_optional, ''); - - if ($id <= 0 && empty($loginname)) { - throw new Exception("Either 'id' or 'loginname' parameter must be given", 406); - } - $result_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE " . ($id > 0 ? "`customerid` = :idln" : "`loginname` = :idln") . ($this->getUserDetail('customers_see_all') ? '' : " AND `adminid` = :adminid")); @@ -84,17 +86,32 @@ class Customers extends ApiCommand implements ResourceEntity if ($this->getUserDetail('customers_see_all') == '0') { $params['adminid'] = $this->getUserDetail('adminid'); } - $result = Database::pexecute_first($result_stmt, $params, true, true); - if ($result) { - $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] get customer '" . $result['loginname'] . "'"); - return $this->response(200, "successfull", $result); + } else { + if (($id > 0 && $id != $this->getUserDetail('customerid')) || ! empty($loginname) && $loginname != $this->getUserDetail('loginname')) { + throw new Exception("You cannot access data of other customers", 401); } - $key = ($id > 0 ? "id #" . $id : "loginname '" . $loginname . "'"); - throw new Exception("Customer with " . $key . " could not be found", 404); + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_CUSTOMERS . "` + WHERE " . ($id > 0 ? "`customerid` = :idln" : "`loginname` = :idln")); + $params = array( + 'idln' => ($id <= 0 ? $loginname : $id) + ); } - throw new Exception("Not allowed to execute given command.", 403); + $result = Database::pexecute_first($result_stmt, $params, true, true); + if ($result) { + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_NOTICE, "[API] get customer '" . $result['loginname'] . "'"); + return $this->response(200, "successfull", $result); + } + $key = ($id > 0 ? "id #" . $id : "loginname '" . $loginname . "'"); + throw new Exception("Customer with " . $key . " could not be found", 404); } + /** + * create a new customer with default ftp-user and standard-subdomain (if wanted) + * + * @access admin + * @return array + */ public function add() { if ($this->isAdmin()) { @@ -236,7 +253,9 @@ class Customers extends ApiCommand implements ResourceEntity ))->get(); $loginname_check = json_decode($dup_check_result, true)['data']; } catch (Exception $e) { - $loginname_check = array('loginname' => ''); + $loginname_check = array( + 'loginname' => '' + ); } // Check if an admin with the loginname already exists @@ -246,7 +265,9 @@ class Customers extends ApiCommand implements ResourceEntity ))->get(); $loginname_check_admin = json_decode($dup_check_result, true)['data']; } catch (Exception $e) { - $loginname_check_admin = array('loginname' => ''); + $loginname_check_admin = array( + 'loginname' => '' + ); } if (strtolower($loginname_check['loginname']) == strtolower($loginname) || strtolower($loginname_check_admin['loginname']) == strtolower($loginname)) { @@ -646,17 +667,29 @@ class Customers extends ApiCommand implements ResourceEntity throw new Exception("Not allowed to execute given command.", 403); } + /** + * update customer entry by either id or loginname + * + * @param int $id + * optional, the customer-id + * @param string $loginname + * optional, the loginname + * + * @access admin, customer + * @throws Exception + * @return array + */ public function update() { if ($this->isAdmin()) { $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); $loginname = $this->getParam('loginname', $ln_optional, ''); - + if ($id <= 0 && empty($loginname)) { throw new Exception("Either 'id' or 'loginname' parameter must be given", 406); } - + $json_result = Customers::getLocal($this->getUserData(), array( 'id' => $id, 'loginname' => $loginname @@ -729,36 +762,18 @@ class Customers extends ApiCommand implements ResourceEntity if (Settings::Get('ticket.enabled') != '1') { $tickets = - 1; } - + if (empty($theme)) { $theme = Settings::Get('panel.default_theme'); } - + $diskspace = $diskspace * 1024; $traffic = $traffic * 1024 * 1024; - - if (((($this->getUserDetail('diskspace_used') + $diskspace - $result['diskspace']) > $this->getUserDetail('diskspace')) && ($this->getUserDetail('diskspace') / 1024) != '-1') - || ((($this->getUserDetail('mysqls_used') + $mysqls - $result['mysqls']) > $this->getUserDetail('mysqls')) && $this->getUserDetail('mysqls') != '-1') - || ((($this->getUserDetail('emails_used') + $emails - $result['emails']) > $this->getUserDetail('emails')) && $this->getUserDetail('emails') != '-1') - || ((($this->getUserDetail('email_accounts_used') + $email_accounts - $result['email_accounts']) > $this->getUserDetail('email_accounts')) && $this->getUserDetail('email_accounts') != '-1') - || ((($this->getUserDetail('email_forwarders_used') + $email_forwarders - $result['email_forwarders']) > $this->getUserDetail('email_forwarders')) && $this->getUserDetail('email_forwarders') != '-1') - || ((($this->getUserDetail('email_quota_used') + $email_quota - $result['email_quota']) > $this->getUserDetail('email_quota')) && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') - || ((($this->getUserDetail('ftps_used') + $ftps - $result['ftps']) > $this->getUserDetail('ftps')) && $this->getUserDetail('ftps') != '-1') - || ((($this->getUserDetail('tickets_used') + $tickets - $result['tickets']) > $this->getUserDetail('tickets')) && $this->getUserDetail('tickets') != '-1') - || ((($this->getUserDetail('subdomains_used') + $subdomains - $result['subdomains']) > $this->getUserDetail('subdomains')) && $this->getUserDetail('subdomains') != '-1') - || (($diskspace / 1024) == '-1' && ($this->getUserDetail('diskspace') / 1024) != '-1') - || ($mysqls == '-1' && $this->getUserDetail('mysqls') != '-1') - || ($emails == '-1' && $this->getUserDetail('emails') != '-1') - || ($email_accounts == '-1' && $this->getUserDetail('email_accounts') != '-1') - || ($email_forwarders == '-1' && $this->getUserDetail('email_forwarders') != '-1') - || ($email_quota == '-1' && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') - || ($ftps == '-1' && $this->getUserDetail('ftps') != '-1') - || ($tickets == '-1' && $this->getUserDetail('tickets') != '-1') - || ($subdomains == '-1' && $this->getUserDetail('subdomains') != '-1') - ) { + + if (((($this->getUserDetail('diskspace_used') + $diskspace - $result['diskspace']) > $this->getUserDetail('diskspace')) && ($this->getUserDetail('diskspace') / 1024) != '-1') || ((($this->getUserDetail('mysqls_used') + $mysqls - $result['mysqls']) > $this->getUserDetail('mysqls')) && $this->getUserDetail('mysqls') != '-1') || ((($this->getUserDetail('emails_used') + $emails - $result['emails']) > $this->getUserDetail('emails')) && $this->getUserDetail('emails') != '-1') || ((($this->getUserDetail('email_accounts_used') + $email_accounts - $result['email_accounts']) > $this->getUserDetail('email_accounts')) && $this->getUserDetail('email_accounts') != '-1') || ((($this->getUserDetail('email_forwarders_used') + $email_forwarders - $result['email_forwarders']) > $this->getUserDetail('email_forwarders')) && $this->getUserDetail('email_forwarders') != '-1') || ((($this->getUserDetail('email_quota_used') + $email_quota - $result['email_quota']) > $this->getUserDetail('email_quota')) && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') || ((($this->getUserDetail('ftps_used') + $ftps - $result['ftps']) > $this->getUserDetail('ftps')) && $this->getUserDetail('ftps') != '-1') || ((($this->getUserDetail('tickets_used') + $tickets - $result['tickets']) > $this->getUserDetail('tickets')) && $this->getUserDetail('tickets') != '-1') || ((($this->getUserDetail('subdomains_used') + $subdomains - $result['subdomains']) > $this->getUserDetail('subdomains')) && $this->getUserDetail('subdomains') != '-1') || (($diskspace / 1024) == '-1' && ($this->getUserDetail('diskspace') / 1024) != '-1') || ($mysqls == '-1' && $this->getUserDetail('mysqls') != '-1') || ($emails == '-1' && $this->getUserDetail('emails') != '-1') || ($email_accounts == '-1' && $this->getUserDetail('email_accounts') != '-1') || ($email_forwarders == '-1' && $this->getUserDetail('email_forwarders') != '-1') || ($email_quota == '-1' && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') || ($ftps == '-1' && $this->getUserDetail('ftps') != '-1') || ($tickets == '-1' && $this->getUserDetail('tickets') != '-1') || ($subdomains == '-1' && $this->getUserDetail('subdomains') != '-1')) { standard_error('youcantallocatemorethanyouhave', '', true); } - + // Either $name and $firstname or the $company must be inserted if ($name == '' && $company == '') { standard_error(array( @@ -1160,6 +1175,7 @@ class Customers extends ApiCommand implements ResourceEntity * @param bool $delete_userfiles * optional, default false * + * @access admin * @throws Exception * @return array */ @@ -1405,6 +1421,7 @@ class Customers extends ApiCommand implements ResourceEntity * @param string $loginname * optional, the loginname * + * @access admin * @throws Exception * @return array */ @@ -1414,11 +1431,11 @@ class Customers extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); $loginname = $this->getParam('loginname', $ln_optional, ''); - + if ($id <= 0 && empty($loginname)) { throw new Exception("Either 'id' or 'loginname' parameter must be given", 406); } - + $json_result = Customers::getLocal($this->getUserData(), array( 'id' => $id, 'loginname' => $loginname diff --git a/lib/classes/api/commands/class.FpmDaemons.php b/lib/classes/api/commands/class.FpmDaemons.php index c604e53c..fd7b30fa 100644 --- a/lib/classes/api/commands/class.FpmDaemons.php +++ b/lib/classes/api/commands/class.FpmDaemons.php @@ -21,6 +21,8 @@ class FpmDaemons extends ApiCommand implements ResourceEntity /** * lists all fpm-daemon entries * + * @access admin + * @throws Exception * @return array count|list */ public function list() @@ -72,6 +74,8 @@ class FpmDaemons extends ApiCommand implements ResourceEntity * * @param int $id fpm-daemon-id * + * @access admin + * @throws Exception * @return array */ public function get() @@ -93,6 +97,13 @@ class FpmDaemons extends ApiCommand implements ResourceEntity throw new Exception("Not allowed to execute given command.", 403); } + /** + * create a new fpm-daemon entry + * + * @access admin + * @throws Exception + * @return array + */ public function add() { if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { @@ -166,6 +177,15 @@ class FpmDaemons extends ApiCommand implements ResourceEntity throw new Exception("Not allowed to execute given command.", 403); } + /** + * update a fpm-daemon entry by given id + * + * @param int $id + * + * @access admin + * @throws Exception + * @return array + */ public function update() { if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { @@ -251,6 +271,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity * * @param int $id fpm-daemon-id * + * @access admin * @throws Exception * @return array */ diff --git a/lib/classes/api/commands/class.Froxlor.php b/lib/classes/api/commands/class.Froxlor.php index 21c0951e..2f61e20c 100644 --- a/lib/classes/api/commands/class.Froxlor.php +++ b/lib/classes/api/commands/class.Froxlor.php @@ -21,6 +21,7 @@ class Froxlor extends ApiCommand /** * checks whether there is a newer version of froxlor available * + * @access admin * @throws Exception * @return string */ @@ -82,6 +83,8 @@ class Froxlor extends ApiCommand /** * * @todo import settings + * + * @access admin */ public function importSettings() {} @@ -89,6 +92,8 @@ class Froxlor extends ApiCommand /** * * @todo export settings to file + * + * @access admin */ public function exportSettings() {} @@ -96,6 +101,8 @@ class Froxlor extends ApiCommand /** * return a list of all settings * + * @access admin + * @throws Exception * @return array count|list */ public function listSettings() @@ -126,6 +133,7 @@ class Froxlor extends ApiCommand * @param string $key * settinggroup.varname couple * + * @access admin * @throws Exception * @return string */ @@ -146,6 +154,7 @@ class Froxlor extends ApiCommand * @param string $value * optional the new value, default is '' * + * @access admin * @throws Exception * @return string */ @@ -153,7 +162,7 @@ class Froxlor extends ApiCommand { // currently not implemented as it required validation too so no wrong settings are being stored via API throw new Exception("Not available yet.", 501); - + if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { $setting = $this->getParam('key'); $value = $this->getParam('value', true, ''); @@ -173,6 +182,7 @@ class Froxlor extends ApiCommand * @param string $module * optional, return list of functions for a specific module * + * @access admin, customer * @throws Exception * @return array */ @@ -260,7 +270,7 @@ class Froxlor extends ApiCommand 'head' => 'There is no comment-block for "' . $module . '.' . $function . '"' ); } - + $clines = explode("\n", $comment); $result = array(); $result['params'] = array(); @@ -277,6 +287,15 @@ class Froxlor extends ApiCommand 'desc' => (isset($r[3]) ? trim($r['3']) : '') ); $param_desc = true; + } // check access-section + elseif (strpos($c, '@access')) { + preg_match('/^\*\s\@access\s(.*)/', $c, $r); + if (! isset($r[0]) || empty($r[0])) { + $r[1] = 'This function has no restrictions'; + } + $result['access'] = array( + 'groups' => (isset($r[1]) ? trim($r[1]) : '') + ); } // check return-section elseif (strpos($c, '@return')) { preg_match('/^\*\s\@return\s(\w+)(\s.*)?/', $c, $r); @@ -312,6 +331,7 @@ class Froxlor extends ApiCommand } } } + $result['head'] =trim($result['head']); return $result; } catch (\ReflectionException $e) { return array(); diff --git a/lib/classes/api/commands/class.IpsAndPorts.php b/lib/classes/api/commands/class.IpsAndPorts.php index fe0affa0..32ab79a3 100644 --- a/lib/classes/api/commands/class.IpsAndPorts.php +++ b/lib/classes/api/commands/class.IpsAndPorts.php @@ -21,6 +21,8 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity /** * lists all ip/port entries * + * @access admin + * @throws Exception * @return array count|list */ public function list() @@ -48,7 +50,8 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity * * @param int $id * ip-port-id - * + * + * @access admin * @throws Exception * @return array */ @@ -71,6 +74,13 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity throw new Exception("Not allowed to execute given command.", 403); } + /** + * create a new ip/port entry + * + * @access admin + * @throws Exception + * @return array + */ public function add() { if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { @@ -199,6 +209,15 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity throw new Exception("Not allowed to execute given command.", 403); } + /** + * update ip/port entry by given id + * + * @param int $id + * + * @access admin + * @throws ErrorException + * @return array + */ public function update() { if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { @@ -349,7 +368,8 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity * * @param int $id * ip-port-id - * + * + * @access admin * @throws Exception * @return array */ diff --git a/lib/classes/api/commands/class.Mysqls.php b/lib/classes/api/commands/class.Mysqls.php index e55c6651..b7faee12 100644 --- a/lib/classes/api/commands/class.Mysqls.php +++ b/lib/classes/api/commands/class.Mysqls.php @@ -31,6 +31,7 @@ class Mysqls extends ApiCommand implements ResourceEntity * @param int $dbserver * optional, specify database-server, default is none * + * @access admin, customer * @throws Exception * @return array */ @@ -130,6 +131,7 @@ class Mysqls extends ApiCommand implements ResourceEntity * @param string $loginname * optional, admin-only, select dbs of a specific customer by loginname * + * @access admin, customer * @return array count|list */ public function list() @@ -220,6 +222,7 @@ class Mysqls extends ApiCommand implements ResourceEntity * @param int $dbserver * optional, specify database-server, default is none * + * @access admin, customer * @throws Exception * @return array */ diff --git a/lib/classes/api/commands/class.PhpSettings.php b/lib/classes/api/commands/class.PhpSettings.php index db10ac8d..6d26a560 100644 --- a/lib/classes/api/commands/class.PhpSettings.php +++ b/lib/classes/api/commands/class.PhpSettings.php @@ -21,6 +21,8 @@ class PhpSettings extends ApiCommand implements ResourceEntity /** * lists all php-setting entries * + * @access admin + * @throws Exception * @return array count|list */ public function list() @@ -106,6 +108,8 @@ class PhpSettings extends ApiCommand implements ResourceEntity * * @param int $id php-settings-id * + * @access admin + * @throws Exception * @return array */ public function get() @@ -127,6 +131,13 @@ class PhpSettings extends ApiCommand implements ResourceEntity throw new Exception("Not allowed to execute given command.", 403); } + /** + * add new php-settings entry + * + * @access admin + * @throws Exception + * @return array + */ public function add() { if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { @@ -226,6 +237,15 @@ class PhpSettings extends ApiCommand implements ResourceEntity throw new Exception("Not allowed to execute given command.", 403); } + /** + * update a php-setting entry by given id + * + * @param int $id + * + * @access admin + * @throws Exception:: + * @return array + */ public function update() { if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { @@ -333,6 +353,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity * * @param int $id php-settings-id * + * @access admin * @throws Exception * @return array */ From b5ebe48715758db9c4a918837fb951db296ac296 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sat, 24 Feb 2018 11:01:29 +0100 Subject: [PATCH 056/746] forgot to save this one Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Domains.php | 27 ++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index 92866870..b363c19e 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -21,6 +21,8 @@ class Domains extends ApiCommand implements ResourceEntity /** * lists all domain entries * + * @access admin + * @throws Exception * @return array count|list */ public function list() @@ -61,7 +63,8 @@ class Domains extends ApiCommand implements ResourceEntity * optional, the domainname * @param boolean $no_std_subdomain * optional, default false - * + * + * @access admin * @throws Exception * @return array */ @@ -106,6 +109,13 @@ class Domains extends ApiCommand implements ResourceEntity throw new Exception("Not allowed to execute given command.", 403); } + /** + * add new domain entry + * + * @access admin + * @throws Exception + * @return array + */ public function add() { if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { @@ -755,6 +765,18 @@ class Domains extends ApiCommand implements ResourceEntity throw new Exception("Not allowed to execute given command.", 403); } + /** + * update domain entry by either id or domainname + * + * @param int $id + * optional, the domain-id + * @param string $domainname + * optional, the domainname + * + * @access admin + * @throws Exception + * @return array + */ public function update() { if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { @@ -1574,7 +1596,8 @@ class Domains extends ApiCommand implements ResourceEntity * optional, remove also domains that are subdomains of this domain but added as main domains; default false * @param bool $is_stdsubdomain * optional, default false, specify whether it's a std-subdomain you are deleting as it does not count as subdomain-resource - * + * + * @access admin * @throws Exception * @return array */ From d2aaf84effdab2de53d18d4af303bbefc7c34993 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sat, 24 Feb 2018 12:13:47 +0100 Subject: [PATCH 057/746] added Mysqls.add Signed-off-by: Michael Kaufmann (d00p) --- apihelp.php | 3 +- lib/classes/api/commands/class.Mysqls.php | 210 +++++++++++++++++++++- 2 files changed, 211 insertions(+), 2 deletions(-) diff --git a/apihelp.php b/apihelp.php index bcc3169b..4fa6eee4 100644 --- a/apihelp.php +++ b/apihelp.php @@ -47,6 +47,7 @@ foreach ($m_arr as $module) { // set necessary data $output_arr[$module['module']][$module['function']] = array( 'return_type' => (isset($module['return']['type']) && $module['return']['type'] != "" ? $module['return']['type'] : - 1), + 'return_desc' => (isset($module['return']['desc']) && $module['return']['desc'] != "" ? $module['return']['desc'] : - 1), 'params_list' => array(), 'head' => $module['head'], 'access' => isset($module['access']) ? $module['access'] : null @@ -126,7 +127,7 @@ foreach ($output_arr as $module => $functions) { $parms .= ""; $apihelp .= $parms; } - $apihelp .= "
    Returns " . ($funcdata['return_type'] == - 1 ? "no-return-type" : $funcdata['return_type']); + $apihelp .= "
    Returns " . ($funcdata['return_type'] == - 1 ? "no-return-type" : $funcdata['return_type']) . ($funcdata['return_desc'] == - 1 ? "" : " ".$funcdata['return_desc']); $apihelp .= "

    "; } } diff --git a/lib/classes/api/commands/class.Mysqls.php b/lib/classes/api/commands/class.Mysqls.php index b7faee12..eed6b999 100644 --- a/lib/classes/api/commands/class.Mysqls.php +++ b/lib/classes/api/commands/class.Mysqls.php @@ -18,8 +18,207 @@ class Mysqls extends ApiCommand implements ResourceEntity { + /** + * add a new mysql-database + * + * @param string $mysql_password + * password for the created database and database-user + * @param int $mysql_server + * optional, default is 0 + * @param string $description + * optional, description for database + * @param bool $sendinfomail + * optional, send created resource-information to customer, default: false + * @param int $customer_id + * required when called as admin, not needed when called as customer + * + * @access admin, customer + * @throws Exception + * @return array + */ public function add() - {} + { + if ($this->getUserDetail('mysqls_used') < $this->getUserDetail('mysqls') || $this->getUserDetail('mysqls') == '-1') { + + // required paramters + $password = $this->getParam('mysql_password'); + + // parameters + $dbserver = $this->getParam('mysql_server', true, 0); + $databasedescription = $this->getParam('description', true, ''); + $sendinfomail = $this->getParam('sendinfomail', true, 0); + + // validation + $password = validate($password, 'password', '', '', array(), true); + $password = validatePassword($password, true); + $databasedescription = validate(trim($databasedescription), 'description', '', '', array(), true); + + // validate whether the dbserver exists + $dbserver = validate($dbserver, html_entity_decode($this->lng['mysql']['mysql_server']), '', '', 0, true); + Database::needRoot(true, $dbserver); + Database::needSqlData(); + $sql_root = Database::getSqlData(); + Database::needRoot(false); + if (! isset($sql_root) || ! is_array($sql_root)) { + throw new ErrorException("Database server with index #" . $dbserver . " is unknown", 404); + } + + if ($password == '') { + standard_error(array( + 'stringisempty', + 'mysql_password' + ), '', true); + } + + if ($sendinfomail != 1) { + $sendinfomail = 0; + } + + // get needed customer info to reduce the mysql-usage-counter by one + if ($this->isAdmin()) { + // get customer id + $customer_id = $this->getParam('customer_id'); + $json_result = Customers::getLocal($this->getUserData(), array( + 'id' => $result['customerid'] + ))->get(); + $customer = json_decode($json_result, true)['data']; + // check whether the customer has enough resources to get the database added + if ($customer['mysqls_used'] >= $customer['mysqls'] && $customer['mysqls'] != '-1') { + throw new Exception("Customer has no more resources available", 406); + } + } else { + $customer_id = $this->getUserDetail('customer_id'); + } + + $newdb_params = array( + 'loginname' => ($this->isAdmin() ? $customer['loginname'] : $this->getUserDetail('loginname')), + 'mysql_lastaccountnumber' => ($this->isAdmin() ? $customer['mysql_lastaccountnumber'] : $this->getUserDetail('mysql_lastaccountnumber')) + ); + // create database, user, set permissions, etc.pp. + $dbm = new DbManager($this->logger()); + $username = $dbm->createDatabase($newdb_params['loginname'], $password, $newdb_params['mysql_lastaccountnumber']); + + // we've checked against the password in dbm->createDatabase + if ($username == false) { + standard_error('passwordshouldnotbeusername', '', true); + } + + // add database info to froxlor + $stmt = Database::prepare(" + INSERT INTO `" . TABLE_PANEL_DATABASES . "` + SET + `customerid` = :customerid, + `databasename` = :databasename, + `description` = :description, + `dbserver` = :dbserver + "); + $params = array( + "customerid" => ($this->isAdmin() ? $customer['customerid'] : $this->getUserDetail('customerid')), + "databasename" => $username, + "description" => $databasedescription, + "dbserver" => $dbserver + ); + Database::pexecute($stmt, $params, true, true); + $databaseid = Database::lastInsertId(); + $params['id'] = $databaseid; + + // update customer usage + $stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_CUSTOMERS . "` + SET `mysqls_used` = `mysqls_used` + 1, `mysql_lastaccountnumber` = `mysql_lastaccountnumber` + 1 + WHERE `customerid` = :customerid + "); + Database::pexecute($stmt, array( + "customerid" => ($this->isAdmin() ? $customer['customerid'] : $this->getUserDetail('customerid')) + ), true, true); + + // update admin usage + $stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_ADMINS . "` + SET `mysqls_used` = `mysqls_used` + 1 + WHERE `adminid` = :adminid + "); + Database::pexecute($stmt, array( + "adminid" => $this->getUserDetail('adminid') + ), true, true); + + // send info-mail? + if ($sendinfomail == 1) { + $pma = $this->lng['admin']['notgiven']; + if (Settings::Get('panel.phpmyadmin_url') != '') { + $pma = Settings::Get('panel.phpmyadmin_url'); + } + + Database::needRoot(true, $dbserver); + Database::needSqlData(); + $sql_root = Database::getSqlData(); + Database::needRoot(false); + $userinfo = ($this->isAdmin() ? $customer : $this->getUserData()); + + $replace_arr = array( + 'SALUTATION' => getCorrectUserSalutation($userinfo), + 'CUST_NAME' => getCorrectUserSalutation($userinfo), // < keep this for compatibility + 'DB_NAME' => $username, + 'DB_PASS' => $password, + 'DB_DESC' => $databasedescription, + 'DB_SRV' => $sql_root['host'], + 'PMA_URI' => $pma + ); + + $def_language = $userinfo['def_language']; + $result_stmt = Database::prepare(" + SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` + WHERE `adminid` = :adminid + AND `language` = :lang + AND `templategroup`='mails' + AND `varname`='new_database_by_customer_subject' + "); + $result = Database::pexecute_first($result_stmt, array( + "adminid" => $userinfo['adminid'], + "lang" => $def_language + ), true, true); + $mail_subject = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $this->lng['mails']['new_database_by_customer']['subject']), $replace_arr)); + + $result_stmt = Database::prepare(" + SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` + WHERE `adminid`= :adminid + AND `language`= :lang + AND `templategroup` = 'mails' + AND `varname` = 'new_database_by_customer_mailbody' + "); + $result = Database::pexecute_first($result_stmt, array( + "adminid" => $userinfo['adminid'], + "lang" => $def_language + )); + $mail_body = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $this->lng['mails']['new_database_by_customer']['mailbody']), $replace_arr)); + + $_mailerror = false; + try { + $this->mail->Subject = $mail_subject; + $this->mail->AltBody = $mail_body; + $this->mail->MsgHTML(str_replace("\n", "
    ", $mail_body)); + $this->mail->AddAddress($userinfo['email'], getCorrectUserSalutation($userinfo)); + $this->mail->Send(); + } catch (phpmailerException $e) { + $mailerr_msg = $e->errorMessage(); + $_mailerror = true; + } catch (Exception $e) { + $mailerr_msg = $e->getMessage(); + $_mailerror = true; + } + + if ($_mailerror) { + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_ERR, "[API] Error sending mail: " . $mailerr_msg); + standard_error('errorsendingmail', $userinfo['email'], true); + } + + $this->mail->ClearAddresses(); + } + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_WARNING, "[API] added mysql-database '" . $username . "'"); + return $this->response(200, "successfull", $params); + } + throw new Exception("No more resources available", 406); + } /** * return a mysql database entry by either id or dbname @@ -85,6 +284,8 @@ class Mysqls extends ApiCommand implements ResourceEntity } else { if ($id != $this->getUserDetail('customerid')) { throw new Exception("You cannot access data of other customers", 401); + } elseif (Settings::IsInList('panel.customer_hide_options', 'mysql')) { + throw new Exception("You cannot access this resource", 405); } $result_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_DATABASES . "` @@ -161,6 +362,9 @@ class Mysqls extends ApiCommand implements ResourceEntity $customer_ids[] = $customer['customerid']; } } else { + if (Settings::IsInList('panel.customer_hide_options', 'mysql')) { + throw new Exception("You cannot access this resource", 405); + } $customer_ids = array( $this->getUserDetail('customerid') ); @@ -237,6 +441,10 @@ class Mysqls extends ApiCommand implements ResourceEntity throw new Exception("Either 'id' or 'dbname' parameter must be given", 406); } + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'mysql')) { + throw new Exception("You cannot access this resource", 405); + } + $json_result = Mysqls::getLocal($this->getUserData(), array( 'id' => $id, 'dbname' => $dbname, From 0bf430e0c13e3559e16db976ff021fdfb45d63b0 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sat, 24 Feb 2018 15:02:49 +0100 Subject: [PATCH 058/746] let customers edit password, def-language and theme in Customers.edit Signed-off-by: Michael Kaufmann (d00p) --- customer_mysql.php | 146 +--- lib/classes/api/commands/class.Customers.php | 673 ++++++++++--------- 2 files changed, 376 insertions(+), 443 deletions(-) diff --git a/customer_mysql.php b/customer_mysql.php index 774daf6b..9e1b0b8e 100644 --- a/customer_mysql.php +++ b/customer_mysql.php @@ -132,134 +132,12 @@ if ($page == 'overview') { } elseif ($action == 'add') { if ($userinfo['mysqls_used'] < $userinfo['mysqls'] || $userinfo['mysqls'] == '-1') { if (isset($_POST['send']) && $_POST['send'] == 'send') { - $password = validate($_POST['mysql_password'], 'password'); - $password = validatePassword($password); - - $sendinfomail = isset($_POST['sendinfomail']) ? 1 : 0; - if ($sendinfomail != 1) { - $sendinfomail = 0; - } - - if ($password == '') { - standard_error(array('stringisempty', 'mypassword')); - } else { - $dbserver = 0; - $dbservers_stmt = Database::query("SELECT COUNT(DISTINCT `dbserver`) as numservers FROM `".TABLE_PANEL_DATABASES."`"); - $_dbserver = $dbservers_stmt->fetch(PDO::FETCH_ASSOC); - $count_mysqlservers = $_dbserver['numservers']; - if ($count_mysqlservers > 1) { - $dbserver = validate($_POST['mysql_server'], html_entity_decode($lng['mysql']['mysql_server']), '', '', 0); - Database::needRoot(true, $dbserver); - Database::needSqlData(); - $sql_root = Database::getSqlData(); - Database::needRoot(false); - if (!isset($sql_root) || !is_array($sql_root)) { - $dbserver = 0; - } - } - - // validate description before actual adding the database, #1052 - $databasedescription = validate(trim($_POST['description']), 'description'); - - // create database, user, set permissions, etc.pp. - $dbm = new DbManager($log); - $username = $dbm->createDatabase( - $userinfo['loginname'], - $password, - $userinfo['mysql_lastaccountnumber'] - ); - - // we've checked against the password in dbm->createDatabase - if ($username == false) { - standard_error('passwordshouldnotbeusername'); - } - - // Statement modified for Database description -- PH 2004-11-29 - $stmt = Database::prepare('INSERT INTO `' . TABLE_PANEL_DATABASES . '` - (`customerid`, `databasename`, `description`, `dbserver`) - VALUES (:customerid, :databasename, :description, :dbserver)' - ); - $params = array( - "customerid" => $userinfo['customerid'], - "databasename" => $username, - "description" => $databasedescription, - "dbserver" => $dbserver - ); - Database::pexecute($stmt, $params); - - $stmt = Database::prepare('UPDATE `' . TABLE_PANEL_CUSTOMERS . '` - SET `mysqls_used` = `mysqls_used` + 1, `mysql_lastaccountnumber` = `mysql_lastaccountnumber` + 1 - WHERE `customerid` = :customerid' - ); - Database::pexecute($stmt, array("customerid" => $userinfo['customerid'])); - - if ($sendinfomail == 1) { - $pma = $lng['admin']['notgiven']; - if (Settings::Get('panel.phpmyadmin_url') != '') { - $pma = Settings::Get('panel.phpmyadmin_url'); - } - - Database::needRoot(true, $dbserver); - Database::needSqlData(); - $sql_root = Database::getSqlData(); - Database::needRoot(false); - - $replace_arr = array( - 'SALUTATION' => getCorrectUserSalutation($userinfo), - 'CUST_NAME' => getCorrectUserSalutation($userinfo), // < keep this for compatibility - 'DB_NAME' => $username, - 'DB_PASS' => $password, - 'DB_DESC' => $databasedescription, - 'DB_SRV' => $sql_root['host'], - 'PMA_URI' => $pma - ); - - $def_language = $userinfo['def_language']; - $result_stmt = Database::prepare("SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` - WHERE `adminid` = :adminid - AND `language` = :lang - AND `templategroup`='mails' - AND `varname`='new_database_by_customer_subject'" - ); - Database::pexecute($result_stmt, array("adminid" => $userinfo['adminid'], "lang" => $def_language)); - $result = $result_stmt->fetch(PDO::FETCH_ASSOC); - $mail_subject = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $lng['mails']['new_database_by_customer']['subject']), $replace_arr)); - - $result_stmt = Database::prepare("SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` - WHERE `adminid`= :adminid - AND `language`= :lang - AND `templategroup` = 'mails' - AND `varname` = 'new_database_by_customer_mailbody'" - ); - Database::pexecute($result_stmt, array("adminid" => $userinfo['adminid'], "lang" => $def_language)); - $result = $result_stmt->fetch(PDO::FETCH_ASSOC); - $mail_body = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $lng['mails']['new_database_by_customer']['mailbody']), $replace_arr)); - - $_mailerror = false; - try { - $mail->Subject = $mail_subject; - $mail->AltBody = $mail_body; - $mail->MsgHTML(str_replace("\n", "
    ", $mail_body)); - $mail->AddAddress($userinfo['email'], getCorrectUserSalutation($userinfo)); - $mail->Send(); - } catch(phpmailerException $e) { - $mailerr_msg = $e->errorMessage(); - $_mailerror = true; - } catch (Exception $e) { - $mailerr_msg = $e->getMessage(); - $_mailerror = true; - } - - if ($_mailerror) { - $log->logAction(USR_ACTION, LOG_ERR, "Error sending mail: " . $mailerr_msg); - standard_error('errorsendingmail', $userinfo['email']); - } - - $mail->ClearAddresses(); - } - - redirectTo($filename, array('page' => $page, 's' => $s)); + try { + Mysqls::getLocal($userinfo, $_POST)->add(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } + redirectTo($filename, array('page' => $page, 's' => $s)); } else { $dbservers_stmt = Database::query("SELECT DISTINCT `dbserver` FROM `".TABLE_PANEL_DATABASES."`"); @@ -284,12 +162,14 @@ if ($page == 'overview') { } } } elseif ($action == 'edit' && $id != 0) { - $result_stmt = Database::prepare("SELECT `id`, `databasename`, `description`, `dbserver` FROM `" . TABLE_PANEL_DATABASES . "` - WHERE `customerid` = :customerid - AND `id` = :id" - ); - Database::pexecute($result_stmt, array("customerid" => $userinfo['customerid'], "id" => $id)); - $result = $result_stmt->fetch(PDO::FETCH_ASSOC); + try { + $json_result = Mysqls::getLocal($userinfo, array( + 'id' => $id + ))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; if (isset($result['databasename']) && $result['databasename'] != '') { if (!isset($sql_root[$result['dbserver']]) || !is_array($sql_root[$result['dbserver']])) { diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index 424bdf92..4bea264a 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -71,11 +71,11 @@ class Customers extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); $loginname = $this->getParam('loginname', $ln_optional, ''); - + if ($id <= 0 && empty($loginname)) { throw new Exception("Either 'id' or 'loginname' parameter must be given", 406); } - + if ($this->isAdmin()) { $result_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_CUSTOMERS . "` @@ -257,7 +257,7 @@ class Customers extends ApiCommand implements ResourceEntity 'loginname' => '' ); } - + // Check if an admin with the loginname already exists try { $dup_check_result = Admins::getLocal($this->getUserData(), array( @@ -269,7 +269,7 @@ class Customers extends ApiCommand implements ResourceEntity 'loginname' => '' ); } - + if (strtolower($loginname_check['loginname']) == strtolower($loginname) || strtolower($loginname_check_admin['loginname']) == strtolower($loginname)) { standard_error('loginnameexists', $loginname, true); } elseif (! validateUsername($loginname, Settings::Get('panel.unix_names'), 14 - strlen(Settings::Get('customer.mysqlprefix')))) { @@ -674,7 +674,7 @@ class Customers extends ApiCommand implements ResourceEntity * optional, the customer-id * @param string $loginname * optional, the loginname - * + * * @access admin, customer * @throws Exception * @return array @@ -697,47 +697,94 @@ class Customers extends ApiCommand implements ResourceEntity $result = json_decode($json_result, true)['data']; $id = $result['customerid']; - // parameters - $move_to_admin = intval_ressource($this->getParam('move_to_admin', true, 0)); - - $idna_convert = new idna_convert_wrapper(); - $email = $this->getParam('email', true, $idna_convert->decode($result['email'])); - $name = $this->getParam('name', true, $result['name']); - $firstname = $this->getParam('firstname', true, $result['firstname']); - $company = $this->getParam('company', true, $result['company']); - $street = $this->getParam('street', true, $result['street']); - $zipcode = $this->getParam('zipcode', true, $result['zipcode']); - $city = $this->getParam('city', true, $result['city']); - $phone = $this->getParam('phone', true, $result['phone']); - $fax = $this->getParam('fax', true, $result['fax']); - $customernumber = $this->getParam('customernumber', true, $result['customernumber']); - $def_language = $this->getParam('def_language', true, $result['def_language']); - $gender = intval_ressource($this->getParam('gender', true, $result['gender'])); - $custom_notes = $this->getParam('custom_notes', true, $result['custom_notes']); - $custom_notes_show = $this->getParam('custom_notes_show', true, $result['custom_notes_show']); - - $dec_places = Settings::Get('panel.decimal_places'); - $diskspace = $this->getUlParam('diskspace', 'diskspace_ul', true, round($result['diskspace'] / 1024, $dec_places)); - $traffic = $this->getUlParam('traffic', 'traffic_ul', true, round($result['traffic'] / (1024 * 1024), $dec_places)); - $subdomains = $this->getUlParam('subdomains', 'subdomains_ul', true, $result['subdomains']); - $emails = $this->getUlParam('emails', 'emails_ul', true, $result['emails']); - $email_accounts = $this->getUlParam('email_accounts', 'email_accounts_ul', true, $result['email_accounts']); - $email_forwarders = $this->getUlParam('email_forwarders', 'email_forwarders_ul', true, $result['email_forwarders']); - $email_quota = $this->getUlParam('email_quota', 'email_quota_ul', true, $result['email_quota']); - $email_imap = $this->getParam('email_imap', true, $result['imap']); - $email_pop3 = $this->getParam('email_pop3', true, $result['pop3']); - $ftps = $this->getUlParam('ftps', 'ftps_ul', true, $result['ftps']); - $tickets = $this->getUlParam('tickets', 'tickets_ul', true, $result['tickets']); - $mysqls = $this->getUlParam('mysqls', 'mysqls_ul', true, $result['mysqls']); - $createstdsubdomain = $this->getParam('createstdsubdomain', true, 0); - $password = $this->getParam('new_customer_password', true, ''); - $sendpassword = $this->getParam('sendpassword', true, 0); - $phpenabled = $this->getParam('phpenabled', true, $result['phpenabled']); - $allowed_phpconfigs = $this->getParam('allowed_phpconfigs', true, json_decode($result['allowed_phpconfigs'], true)); - $perlenabled = $this->getParam('perlenabled', true, $result['perlenabled']); - $dnsenabled = $this->getParam('dnsenabled', true, $result['dnsenabled']); - $deactivated = $this->getParam('deactivated', true, $result['deactivated']); - $theme = $this->getParam('theme', true, $result['theme']); + if ($this->isAdmin()) { + // parameters + $move_to_admin = intval_ressource($this->getParam('move_to_admin', true, 0)); + + $idna_convert = new idna_convert_wrapper(); + $email = $this->getParam('email', true, $idna_convert->decode($result['email'])); + $name = $this->getParam('name', true, $result['name']); + $firstname = $this->getParam('firstname', true, $result['firstname']); + $company = $this->getParam('company', true, $result['company']); + $street = $this->getParam('street', true, $result['street']); + $zipcode = $this->getParam('zipcode', true, $result['zipcode']); + $city = $this->getParam('city', true, $result['city']); + $phone = $this->getParam('phone', true, $result['phone']); + $fax = $this->getParam('fax', true, $result['fax']); + $customernumber = $this->getParam('customernumber', true, $result['customernumber']); + $def_language = $this->getParam('def_language', true, $result['def_language']); + $gender = intval_ressource($this->getParam('gender', true, $result['gender'])); + $custom_notes = $this->getParam('custom_notes', true, $result['custom_notes']); + $custom_notes_show = $this->getParam('custom_notes_show', true, $result['custom_notes_show']); + + $dec_places = Settings::Get('panel.decimal_places'); + $diskspace = $this->getUlParam('diskspace', 'diskspace_ul', true, round($result['diskspace'] / 1024, $dec_places)); + $traffic = $this->getUlParam('traffic', 'traffic_ul', true, round($result['traffic'] / (1024 * 1024), $dec_places)); + $subdomains = $this->getUlParam('subdomains', 'subdomains_ul', true, $result['subdomains']); + $emails = $this->getUlParam('emails', 'emails_ul', true, $result['emails']); + $email_accounts = $this->getUlParam('email_accounts', 'email_accounts_ul', true, $result['email_accounts']); + $email_forwarders = $this->getUlParam('email_forwarders', 'email_forwarders_ul', true, $result['email_forwarders']); + $email_quota = $this->getUlParam('email_quota', 'email_quota_ul', true, $result['email_quota']); + $email_imap = $this->getParam('email_imap', true, $result['imap']); + $email_pop3 = $this->getParam('email_pop3', true, $result['pop3']); + $ftps = $this->getUlParam('ftps', 'ftps_ul', true, $result['ftps']); + $tickets = $this->getUlParam('tickets', 'tickets_ul', true, $result['tickets']); + $mysqls = $this->getUlParam('mysqls', 'mysqls_ul', true, $result['mysqls']); + $createstdsubdomain = $this->getParam('createstdsubdomain', true, 0); + $password = $this->getParam('new_customer_password', true, ''); + $sendpassword = $this->getParam('sendpassword', true, 0); + $phpenabled = $this->getParam('phpenabled', true, $result['phpenabled']); + $allowed_phpconfigs = $this->getParam('allowed_phpconfigs', true, json_decode($result['allowed_phpconfigs'], true)); + $perlenabled = $this->getParam('perlenabled', true, $result['perlenabled']); + $dnsenabled = $this->getParam('dnsenabled', true, $result['dnsenabled']); + $deactivated = $this->getParam('deactivated', true, $result['deactivated']); + $theme = $this->getParam('theme', true, $result['theme']); + } else { + // allowed parameters + $def_language = $this->getParam('def_language', true, $result['def_language']); + $password = $this->getParam('new_customer_password', true, ''); + $theme = $this->getParam('theme', true, $result['theme']); + + // unchangeable parameters for customers + $move_to_admin = 0; + $idna_convert = new idna_convert_wrapper(); + $email = $idna_convert->decode($result['email']); + $name = $result['name']; + $firstname = $result['firstname']; + $company = $result['company']; + $street = $result['street']; + $zipcode = $result['zipcode']; + $city = $result['city']; + $phone = $result['phone']; + $fax = $result['fax']; + $customernumber = $result['customernumber']; + $gender = $result['gender']; + $custom_notes = $result['custom_notes']; + $custom_notes_show = $result['custom_notes_show']; + + $dec_places = Settings::Get('panel.decimal_places'); + $diskspace = round($result['diskspace'] / 1024, $dec_places); + $traffic = round($result['traffic'] / (1024 * 1024), $dec_places); + $subdomains = $result['subdomains']; + $emails = $result['emails']; + $email_accounts = $result['email_accounts']; + $email_forwarders = $result['email_forwarders']; + $email_quota = $result['email_quota']; + $email_imap = $result['imap']; + $email_pop3 = $result['pop3']; + $ftps = $result['ftps']; + $tickets = $result['tickets']; + $mysqls = $result['mysqls']; + // if we got one, it's true so none will be generated (it exists already) + // if not, none will be generated + $createstdsubdomain = ($result['standardsubdomain'] > 0 ? 1 : 0); + $sendpassword = 0; + $phpenabled = $result['phpenabled']; + $allowed_phpconfigs = json_decode($result['allowed_phpconfigs'], true); + $perlenabled = $result['perlenabled']; + $dnsenabled = $result['dnsenabled']; + $deactivated = $result['deactivated']; + } // validation $idna_convert = new idna_convert_wrapper(); @@ -770,277 +817,282 @@ class Customers extends ApiCommand implements ResourceEntity $diskspace = $diskspace * 1024; $traffic = $traffic * 1024 * 1024; - if (((($this->getUserDetail('diskspace_used') + $diskspace - $result['diskspace']) > $this->getUserDetail('diskspace')) && ($this->getUserDetail('diskspace') / 1024) != '-1') || ((($this->getUserDetail('mysqls_used') + $mysqls - $result['mysqls']) > $this->getUserDetail('mysqls')) && $this->getUserDetail('mysqls') != '-1') || ((($this->getUserDetail('emails_used') + $emails - $result['emails']) > $this->getUserDetail('emails')) && $this->getUserDetail('emails') != '-1') || ((($this->getUserDetail('email_accounts_used') + $email_accounts - $result['email_accounts']) > $this->getUserDetail('email_accounts')) && $this->getUserDetail('email_accounts') != '-1') || ((($this->getUserDetail('email_forwarders_used') + $email_forwarders - $result['email_forwarders']) > $this->getUserDetail('email_forwarders')) && $this->getUserDetail('email_forwarders') != '-1') || ((($this->getUserDetail('email_quota_used') + $email_quota - $result['email_quota']) > $this->getUserDetail('email_quota')) && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') || ((($this->getUserDetail('ftps_used') + $ftps - $result['ftps']) > $this->getUserDetail('ftps')) && $this->getUserDetail('ftps') != '-1') || ((($this->getUserDetail('tickets_used') + $tickets - $result['tickets']) > $this->getUserDetail('tickets')) && $this->getUserDetail('tickets') != '-1') || ((($this->getUserDetail('subdomains_used') + $subdomains - $result['subdomains']) > $this->getUserDetail('subdomains')) && $this->getUserDetail('subdomains') != '-1') || (($diskspace / 1024) == '-1' && ($this->getUserDetail('diskspace') / 1024) != '-1') || ($mysqls == '-1' && $this->getUserDetail('mysqls') != '-1') || ($emails == '-1' && $this->getUserDetail('emails') != '-1') || ($email_accounts == '-1' && $this->getUserDetail('email_accounts') != '-1') || ($email_forwarders == '-1' && $this->getUserDetail('email_forwarders') != '-1') || ($email_quota == '-1' && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') || ($ftps == '-1' && $this->getUserDetail('ftps') != '-1') || ($tickets == '-1' && $this->getUserDetail('tickets') != '-1') || ($subdomains == '-1' && $this->getUserDetail('subdomains') != '-1')) { - standard_error('youcantallocatemorethanyouhave', '', true); + if ($this->isAdmin()) { + if (((($this->getUserDetail('diskspace_used') + $diskspace - $result['diskspace']) > $this->getUserDetail('diskspace')) && ($this->getUserDetail('diskspace') / 1024) != '-1') || ((($this->getUserDetail('mysqls_used') + $mysqls - $result['mysqls']) > $this->getUserDetail('mysqls')) && $this->getUserDetail('mysqls') != '-1') || ((($this->getUserDetail('emails_used') + $emails - $result['emails']) > $this->getUserDetail('emails')) && $this->getUserDetail('emails') != '-1') || ((($this->getUserDetail('email_accounts_used') + $email_accounts - $result['email_accounts']) > $this->getUserDetail('email_accounts')) && $this->getUserDetail('email_accounts') != '-1') || ((($this->getUserDetail('email_forwarders_used') + $email_forwarders - $result['email_forwarders']) > $this->getUserDetail('email_forwarders')) && $this->getUserDetail('email_forwarders') != '-1') || ((($this->getUserDetail('email_quota_used') + $email_quota - $result['email_quota']) > $this->getUserDetail('email_quota')) && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') || ((($this->getUserDetail('ftps_used') + $ftps - $result['ftps']) > $this->getUserDetail('ftps')) && $this->getUserDetail('ftps') != '-1') || ((($this->getUserDetail('tickets_used') + $tickets - $result['tickets']) > $this->getUserDetail('tickets')) && $this->getUserDetail('tickets') != '-1') || ((($this->getUserDetail('subdomains_used') + $subdomains - $result['subdomains']) > $this->getUserDetail('subdomains')) && $this->getUserDetail('subdomains') != '-1') || (($diskspace / 1024) == '-1' && ($this->getUserDetail('diskspace') / 1024) != '-1') || ($mysqls == '-1' && $this->getUserDetail('mysqls') != '-1') || ($emails == '-1' && $this->getUserDetail('emails') != '-1') || ($email_accounts == '-1' && $this->getUserDetail('email_accounts') != '-1') || ($email_forwarders == '-1' && $this->getUserDetail('email_forwarders') != '-1') || ($email_quota == '-1' && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') || ($ftps == '-1' && $this->getUserDetail('ftps') != '-1') || ($tickets == '-1' && $this->getUserDetail('tickets') != '-1') || ($subdomains == '-1' && $this->getUserDetail('subdomains') != '-1')) { + standard_error('youcantallocatemorethanyouhave', '', true); + } + + // Either $name and $firstname or the $company must be inserted + if ($name == '' && $company == '') { + standard_error(array( + 'stringisempty', + 'myname' + ), '', true); + } elseif ($firstname == '' && $company == '') { + standard_error(array( + 'stringisempty', + 'myfirstname' + ), '', true); + } elseif ($email == '') { + standard_error(array( + 'stringisempty', + 'emailadd' + ), '', true); + } elseif (! validateEmail($email)) { + standard_error('emailiswrong', $email, true); + } } - // Either $name and $firstname or the $company must be inserted - if ($name == '' && $company == '') { - standard_error(array( - 'stringisempty', - 'myname' - ), '', true); - } elseif ($firstname == '' && $company == '') { - standard_error(array( - 'stringisempty', - 'myfirstname' - ), '', true); - } elseif ($email == '') { - standard_error(array( - 'stringisempty', - 'emailadd' - ), '', true); - } elseif (! validateEmail($email)) { - standard_error('emailiswrong', $email, true); + if ($password != '') { + $password = validatePassword($password, true); + $password = makeCryptPassword($password); } else { + $password = $result['password']; + } + + if ($createstdsubdomain != '1') { + $createstdsubdomain = '0'; + } + + if ($createstdsubdomain == '1' && $result['standardsubdomain'] == '0') { - if ($password != '') { - $password = validatePassword($password, true); - $password = makeCryptPassword($password); + if (Settings::Get('system.stdsubdomain') !== null && Settings::Get('system.stdsubdomain') != '') { + $_stdsubdomain = $result['loginname'] . '.' . Settings::Get('system.stdsubdomain'); } else { - $password = $result['password']; + $_stdsubdomain = $result['loginname'] . '.' . Settings::Get('system.hostname'); } - if ($createstdsubdomain != '1') { - $createstdsubdomain = '0'; + $ins_data = array( + 'domain' => $_stdsubdomain, + 'customerid' => $result['customerid'], + 'adminid' => $this->getUserDetail('adminid'), + 'docroot' => $result['documentroot'], + 'phpenabled' => $phpenabled, + 'openbasedir' => '1' + ); + $domainid = - 1; + try { + $std_domain = Domains::getLocal($this->getUserData(), $ins_data)->add(); + $domainid = json_decode($std_domain, true)['data']['id']; + } catch (Exception $e) { + $this->logger()->logAction(ADM_ACTION, LOG_ERR, "[API] Unable to add standard-subdomain: " . $e->getMessage()); } - if ($createstdsubdomain == '1' && $result['standardsubdomain'] == '0') { - - if (Settings::Get('system.stdsubdomain') !== null && Settings::Get('system.stdsubdomain') != '') { - $_stdsubdomain = $result['loginname'] . '.' . Settings::Get('system.stdsubdomain'); - } else { - $_stdsubdomain = $result['loginname'] . '.' . Settings::Get('system.hostname'); - } - - $ins_data = array( - 'domain' => $_stdsubdomain, - 'customerid' => $result['customerid'], - 'adminid' => $this->getUserDetail('adminid'), - 'docroot' => $result['documentroot'], - 'phpenabled' => $phpenabled, - 'openbasedir' => '1' - ); - $domainid = - 1; - try { - $std_domain = Domains::getLocal($this->getUserData(), $ins_data)->add(); - $domainid = json_decode($std_domain, true)['data']['id']; - } catch (Exception $e) { - $this->logger()->logAction(ADM_ACTION, LOG_ERR, "[API] Unable to add standard-subdomain: " . $e->getMessage()); - } - - if ($domainid > 0) { - $upd_stmt = Database::prepare(" + if ($domainid > 0) { + $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `standardsubdomain` = :domainid WHERE `customerid` = :customerid "); - Database::pexecute($upd_stmt, array( - 'domainid' => $domainid, - 'customerid' => $result['customerid'] - ), true, true); - $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] automatically added standardsubdomain for user '" . $result['loginname'] . "'"); - inserttask('1'); - } - } - - if ($createstdsubdomain == '0' && $result['standardsubdomain'] != '0') { - - try { - $std_domain = Domains::getLocal($this->getUserData(), array( - 'id' => $result['standardsubdomain'], - 'is_stdsubdomain' => 1 - ))->delete(); - } catch (Exception $e) { - $this->logger()->logAction(ADM_ACTION, LOG_ERR, "[API] Unable to delete standard-subdomain: " . $e->getMessage()); - } - $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] automatically deleted standardsubdomain for user '" . $result['loginname'] . "'"); + Database::pexecute($upd_stmt, array( + 'domainid' => $domainid, + 'customerid' => $result['customerid'] + ), true, true); + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] automatically added standardsubdomain for user '" . $result['loginname'] . "'"); inserttask('1'); } + } + + if ($createstdsubdomain == '0' && $result['standardsubdomain'] != '0') { - if ($deactivated != '1') { - $deactivated = '0'; + try { + $std_domain = Domains::getLocal($this->getUserData(), array( + 'id' => $result['standardsubdomain'], + 'is_stdsubdomain' => 1 + ))->delete(); + } catch (Exception $e) { + $this->logger()->logAction(ADM_ACTION, LOG_ERR, "[API] Unable to delete standard-subdomain: " . $e->getMessage()); } + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] automatically deleted standardsubdomain for user '" . $result['loginname'] . "'"); + inserttask('1'); + } + + if ($deactivated != '1') { + $deactivated = '0'; + } + + if ($phpenabled != '0') { + $phpenabled = '1'; + } + + if ($perlenabled != '0') { + $perlenabled = '1'; + } + + if ($dnsenabled != '0') { + $dnsenabled = '1'; + } + + if ($phpenabled != $result['phpenabled'] || $perlenabled != $result['perlenabled']) { + inserttask('1'); + } + + // activate/deactivate customer services + if ($deactivated != $result['deactivated']) { - if ($phpenabled != '0') { - $phpenabled = '1'; - } + $yesno = (($deactivated) ? 'N' : 'Y'); + $pop3 = (($deactivated) ? '0' : (int) $result['pop3']); + $imap = (($deactivated) ? '0' : (int) $result['imap']); - if ($perlenabled != '0') { - $perlenabled = '1'; - } - - if ($dnsenabled != '0') { - $dnsenabled = '1'; - } - - if ($phpenabled != $result['phpenabled'] || $perlenabled != $result['perlenabled']) { - inserttask('1'); - } - - // activate/deactivate customer services - if ($deactivated != $result['deactivated']) { - - $yesno = (($deactivated) ? 'N' : 'Y'); - $pop3 = (($deactivated) ? '0' : (int) $result['pop3']); - $imap = (($deactivated) ? '0' : (int) $result['imap']); - - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_MAIL_USERS . "` SET `postfix`= :yesno, `pop3` = :pop3, `imap` = :imap WHERE `customerid` = :customerid"); - Database::pexecute($upd_stmt, array( - 'yesno' => $yesno, - 'pop3' => $pop3, - 'imap' => $imap, - 'customerid' => $id - )); - - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_FTP_USERS . "` SET `login_enabled` = :yesno WHERE `customerid` = :customerid"); - Database::pexecute($upd_stmt, array( - 'yesno' => $yesno, - 'customerid' => $id - )); - - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `deactivated`= :deactivated WHERE `customerid` = :customerid"); - Database::pexecute($upd_stmt, array( - 'deactivated' => $deactivated, - 'customerid' => $id - )); - - // Retrieve customer's databases - $databases_stmt = Database::prepare("SELECT * FROM " . TABLE_PANEL_DATABASES . " WHERE customerid = :customerid ORDER BY `dbserver`"); - Database::pexecute($databases_stmt, array( - 'customerid' => $id - )); - - Database::needRoot(true); - $last_dbserver = 0; - - $dbm = new DbManager($this->logger()); - - // For each of them - while ($row_database = $databases_stmt->fetch(PDO::FETCH_ASSOC)) { - - if ($last_dbserver != $row_database['dbserver']) { - $dbm->getManager()->flushPrivileges(); - Database::needRoot(true, $row_database['dbserver']); - $last_dbserver = $row_database['dbserver']; - } - - foreach (array_unique(explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) { - $mysql_access_host = trim($mysql_access_host); - - // Prevent access, if deactivated - if ($deactivated) { - // failsafe if user has been deleted manually (requires MySQL 4.1.2+) - $dbm->getManager()->disableUser($row_database['databasename'], $mysql_access_host); - } else { - // Otherwise grant access - $dbm->getManager()->enableUser($row_database['databasename'], $mysql_access_host); - } - } - } - - // At last flush the new privileges - $dbm->getManager()->flushPrivileges(); - Database::needRoot(false); - - $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] deactivated user '" . $result['loginname'] . "'"); - inserttask('1'); - } - - // Disable or enable POP3 Login for customers Mail Accounts - if ($email_pop3 != $result['pop3']) { - $upd_stmt = Database::prepare("UPDATE `" . TABLE_MAIL_USERS . "` SET `pop3` = :pop3 WHERE `customerid` = :customerid"); - Database::pexecute($upd_stmt, array( - 'pop3' => $email_pop3, - 'customerid' => $id - )); - } - - // Disable or enable IMAP Login for customers Mail Accounts - if ($email_imap != $result['imap']) { - $upd_stmt = Database::prepare("UPDATE `" . TABLE_MAIL_USERS . "` SET `imap` = :imap WHERE `customerid` = :customerid"); - Database::pexecute($upd_stmt, array( - 'imap' => $email_imap, - 'customerid' => $id - )); - } - - $upd_data = array( - 'customerid' => $id, - 'passwd' => $password, - 'name' => $name, - 'firstname' => $firstname, - 'gender' => $gender, - 'company' => $company, - 'street' => $street, - 'zipcode' => $zipcode, - 'city' => $city, - 'phone' => $phone, - 'fax' => $fax, - 'email' => $email, - 'customerno' => $customernumber, - 'lang' => $def_language, - 'diskspace' => $diskspace, - 'traffic' => $traffic, - 'subdomains' => $subdomains, - 'emails' => $emails, - 'email_accounts' => $email_accounts, - 'email_forwarders' => $email_forwarders, - 'email_quota' => $email_quota, - 'ftps' => $ftps, - 'tickets' => $tickets, - 'mysqls' => $mysqls, - 'deactivated' => $deactivated, - 'phpenabled' => $phpenabled, - 'allowed_phpconfigs' => empty($allowed_phpconfigs) ? "" : json_encode($allowed_phpconfigs), - 'imap' => $email_imap, - 'pop3' => $email_pop3, - 'perlenabled' => $perlenabled, - 'dnsenabled' => $dnsenabled, - 'custom_notes' => $custom_notes, - 'custom_notes_show' => $custom_notes_show, - 'theme' => $theme - ); $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET - `name` = :name, - `firstname` = :firstname, - `gender` = :gender, - `company` = :company, - `street` = :street, - `zipcode` = :zipcode, - `city` = :city, - `phone` = :phone, - `fax` = :fax, - `email` = :email, - `customernumber` = :customerno, - `def_language` = :lang, - `password` = :passwd, - `diskspace` = :diskspace, - `traffic` = :traffic, - `subdomains` = :subdomains, - `emails` = :emails, - `email_accounts` = :email_accounts, - `email_forwarders` = :email_forwarders, - `ftps` = :ftps, - `tickets` = :tickets, - `mysqls` = :mysqls, - `deactivated` = :deactivated, - `phpenabled` = :phpenabled, - `allowed_phpconfigs` = :allowed_phpconfigs, - `email_quota` = :email_quota, - `imap` = :imap, - `pop3` = :pop3, - `perlenabled` = :perlenabled, - `dnsenabled` = :dnsenabled, - `custom_notes` = :custom_notes, - `custom_notes_show` = :custom_notes_show, - `theme` = :theme - WHERE `customerid` = :customerid + UPDATE `" . TABLE_MAIL_USERS . "` SET `postfix`= :yesno, `pop3` = :pop3, `imap` = :imap WHERE `customerid` = :customerid "); - Database::pexecute($upd_stmt, $upd_data); + Database::pexecute($upd_stmt, array( + 'yesno' => $yesno, + 'pop3' => $pop3, + 'imap' => $imap, + 'customerid' => $id + )); + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_FTP_USERS . "` SET `login_enabled` = :yesno WHERE `customerid` = :customerid + "); + Database::pexecute($upd_stmt, array( + 'yesno' => $yesno, + 'customerid' => $id + )); + + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `deactivated`= :deactivated WHERE `customerid` = :customerid"); + Database::pexecute($upd_stmt, array( + 'deactivated' => $deactivated, + 'customerid' => $id + )); + + // Retrieve customer's databases + $databases_stmt = Database::prepare("SELECT * FROM " . TABLE_PANEL_DATABASES . " WHERE customerid = :customerid ORDER BY `dbserver`"); + Database::pexecute($databases_stmt, array( + 'customerid' => $id + )); + + Database::needRoot(true); + $last_dbserver = 0; + + $dbm = new DbManager($this->logger()); + + // For each of them + while ($row_database = $databases_stmt->fetch(PDO::FETCH_ASSOC)) { + + if ($last_dbserver != $row_database['dbserver']) { + $dbm->getManager()->flushPrivileges(); + Database::needRoot(true, $row_database['dbserver']); + $last_dbserver = $row_database['dbserver']; + } + + foreach (array_unique(explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) { + $mysql_access_host = trim($mysql_access_host); + + // Prevent access, if deactivated + if ($deactivated) { + // failsafe if user has been deleted manually (requires MySQL 4.1.2+) + $dbm->getManager()->disableUser($row_database['databasename'], $mysql_access_host); + } else { + // Otherwise grant access + $dbm->getManager()->enableUser($row_database['databasename'], $mysql_access_host); + } + } + } + + // At last flush the new privileges + $dbm->getManager()->flushPrivileges(); + Database::needRoot(false); + + $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] deactivated user '" . $result['loginname'] . "'"); + inserttask('1'); + } + + // Disable or enable POP3 Login for customers Mail Accounts + if ($email_pop3 != $result['pop3']) { + $upd_stmt = Database::prepare("UPDATE `" . TABLE_MAIL_USERS . "` SET `pop3` = :pop3 WHERE `customerid` = :customerid"); + Database::pexecute($upd_stmt, array( + 'pop3' => $email_pop3, + 'customerid' => $id + )); + } + + // Disable or enable IMAP Login for customers Mail Accounts + if ($email_imap != $result['imap']) { + $upd_stmt = Database::prepare("UPDATE `" . TABLE_MAIL_USERS . "` SET `imap` = :imap WHERE `customerid` = :customerid"); + Database::pexecute($upd_stmt, array( + 'imap' => $email_imap, + 'customerid' => $id + )); + } + + $upd_data = array( + 'customerid' => $id, + 'passwd' => $password, + 'name' => $name, + 'firstname' => $firstname, + 'gender' => $gender, + 'company' => $company, + 'street' => $street, + 'zipcode' => $zipcode, + 'city' => $city, + 'phone' => $phone, + 'fax' => $fax, + 'email' => $email, + 'customerno' => $customernumber, + 'lang' => $def_language, + 'diskspace' => $diskspace, + 'traffic' => $traffic, + 'subdomains' => $subdomains, + 'emails' => $emails, + 'email_accounts' => $email_accounts, + 'email_forwarders' => $email_forwarders, + 'email_quota' => $email_quota, + 'ftps' => $ftps, + 'tickets' => $tickets, + 'mysqls' => $mysqls, + 'deactivated' => $deactivated, + 'phpenabled' => $phpenabled, + 'allowed_phpconfigs' => empty($allowed_phpconfigs) ? "" : json_encode($allowed_phpconfigs), + 'imap' => $email_imap, + 'pop3' => $email_pop3, + 'perlenabled' => $perlenabled, + 'dnsenabled' => $dnsenabled, + 'custom_notes' => $custom_notes, + 'custom_notes_show' => $custom_notes_show, + 'theme' => $theme + ); + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET + `name` = :name, + `firstname` = :firstname, + `gender` = :gender, + `company` = :company, + `street` = :street, + `zipcode` = :zipcode, + `city` = :city, + `phone` = :phone, + `fax` = :fax, + `email` = :email, + `customernumber` = :customerno, + `def_language` = :lang, + `password` = :passwd, + `diskspace` = :diskspace, + `traffic` = :traffic, + `subdomains` = :subdomains, + `emails` = :emails, + `email_accounts` = :email_accounts, + `email_forwarders` = :email_forwarders, + `ftps` = :ftps, + `tickets` = :tickets, + `mysqls` = :mysqls, + `deactivated` = :deactivated, + `phpenabled` = :phpenabled, + `allowed_phpconfigs` = :allowed_phpconfigs, + `email_quota` = :email_quota, + `imap` = :imap, + `pop3` = :pop3, + `perlenabled` = :perlenabled, + `dnsenabled` = :dnsenabled, + `custom_notes` = :custom_notes, + `custom_notes_show` = :custom_notes_show, + `theme` = :theme + WHERE `customerid` = :customerid + "); + Database::pexecute($upd_stmt, $upd_data); + + if ($this->isAdmin()) { // Using filesystem - quota, insert a task which cleans the filesystem - quota inserttask('10'); @@ -1147,20 +1199,21 @@ class Customers extends ApiCommand implements ResourceEntity $admin_update_query .= " WHERE `adminid` = '" . (int) $result['adminid'] . "'"; Database::query($admin_update_query); - $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] edited user '" . $result['loginname'] . "'"); - - /* - * move customer to another admin/reseller; #1166 - */ - if ($move_to_admin > 0 && $move_to_admin != $result['adminid']) { - $move_result = moveCustomerToAdmin($id, $move_to_admin); - if ($move_result != true) { - standard_error('moveofcustomerfailed', $move_result, true); - } - } - - return $this->response(200, "successfull", $upd_data); } + + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] edited user '" . $result['loginname'] . "'"); + + /* + * move customer to another admin/reseller; #1166 + */ + if ($move_to_admin > 0 && $move_to_admin != $result['adminid']) { + $move_result = moveCustomerToAdmin($id, $move_to_admin); + if ($move_result != true) { + standard_error('moveofcustomerfailed', $move_result, true); + } + } + + return $this->response(200, "successfull", $upd_data); } throw new Exception("Not allowed to execute given command.", 403); } From 87912a9e072b360f9df2066753b9572ac01398c4 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sat, 24 Feb 2018 16:06:53 +0100 Subject: [PATCH 059/746] refactored moveCustomerToAdmin() function to Customers.move ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Customers.php | 80 ++++++++++++++++++- lib/classes/api/commands/class.Domains.php | 2 +- .../froxlor/function.moveCustomerToAdmin.php | 62 -------------- 3 files changed, 79 insertions(+), 65 deletions(-) delete mode 100644 lib/functions/froxlor/function.moveCustomerToAdmin.php diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index 4bea264a..d3a762e4 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -1207,12 +1207,16 @@ class Customers extends ApiCommand implements ResourceEntity * move customer to another admin/reseller; #1166 */ if ($move_to_admin > 0 && $move_to_admin != $result['adminid']) { - $move_result = moveCustomerToAdmin($id, $move_to_admin); + $json_result = Customers::getLocal($this->getUserData(), array( + 'id' => $result['customerid'], + 'adminid' => $move_to_admin + ))->move(); + $move_result = json_decode($json_result, true)['data']; if ($move_result != true) { standard_error('moveofcustomerfailed', $move_result, true); } } - + return $this->response(200, "successfull", $upd_data); } throw new Exception("Not allowed to execute given command.", 403); @@ -1510,4 +1514,76 @@ class Customers extends ApiCommand implements ResourceEntity } throw new Exception("Not allowed to execute given command.", 403); } + + /** + * Function to move a given customer to a given admin/reseller + * and update all its references accordingly + * + * @param int $id + * customer-id + * @param int $adminid + * target-admin-id + * + * @access admin + * @throws Exception + * @return bool true on success, error-message on failure + */ + public function move() + { + if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { + $id = $this->getParam('id'); + $adminid = $this->getParam('adminid'); + + // get customer + $json_result = Admins::getLocal($this->getUserData(), array( + 'id' => $id + ))->get(); + $c_result = json_decode($json_result, true)['data']; + + // check if target-admin is the current admin + if ($adminid == $c_result['adminid']) { + throw new Exception("Cannot move customer to the same admin/reseller as he currently is assigned to", 406); + } + + // get target admin + $json_result = Customers::getLocal($this->getUserData(), array( + 'id' => $adminid + ))->get(); + $a_result = json_decode($json_result, true)['data']; + + // Update customer entry + $updCustomer_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `adminid` = :adminid WHERE `customerid` = :cid + "); + Database::pexecute($updCustomer_stmt, array( + 'adminid' => $adminid, + 'cid' => $id + ), true, true); + + // Update customer-domains + $updDomains_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `adminid` = :adminid WHERE `customerid` = :cid + "); + Database::pexecute($updDomains_stmt, array( + 'adminid' => $adminid, + 'cid' => $id + ), true, true); + + // Update customer-tickets + $updTickets_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_TICKETS . "` SET `adminid` = :adminid WHERE `customerid` = :cid + "); + Database::pexecute($updTickets_stmt, array( + 'adminid' => $adminid, + 'cid' => $id + ), true, true); + + // now, recalculate the resource-usage for the old and the new admin + updateCounters(false); + + $log->logAction(ADM_ACTION, LOG_INFO, "[API] moved user '" . $c_result['loginname'] . "' from admin/reseller '" . $c_result['adminname'] . " to admin/reseller '" . $a_result['loginname'] . "'"); + return $this->response(200, "successfull", true); + } + throw new Exception("Not allowed to execute given command.", 403); + } } diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index b363c19e..1e135c2c 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -61,7 +61,7 @@ class Domains extends ApiCommand implements ResourceEntity * optional, the domain-id * @param string $domainname * optional, the domainname - * @param boolean $no_std_subdomain + * @param bool $no_std_subdomain * optional, default false * * @access admin diff --git a/lib/functions/froxlor/function.moveCustomerToAdmin.php b/lib/functions/froxlor/function.moveCustomerToAdmin.php deleted file mode 100644 index 0c2018bc..00000000 --- a/lib/functions/froxlor/function.moveCustomerToAdmin.php +++ /dev/null @@ -1,62 +0,0 @@ - $id - ) ); - - $log->logAction(ADM_ACTION, LOG_INFO, "moved user #" . $id . " from admin/reseller #".$cAdmin['adminid']." to admin/reseller #".$adminid); - - // Update customer entry - $updCustomer_stmt = Database::prepare ( " - UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `adminid` = :adminid WHERE `customerid` = :cid - " ); - Database::pexecute ( $updCustomer_stmt, array ( - 'adminid' => $adminid, - 'cid' => $id - ) ); - - // Update customer-domains - $updDomains_stmt = Database::prepare ( " - UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `adminid` = :adminid WHERE `customerid` = :cid - " ); - Database::pexecute ( $updDomains_stmt, array ( - 'adminid' => $adminid, - 'cid' => $id - ) ); - - // Update customer-tickets - $updTickets_stmt = Database::prepare ( " - UPDATE `" . TABLE_PANEL_TICKETS . "` SET `adminid` = :adminid WHERE `customerid` = :cid - " ); - Database::pexecute ( $updTickets_stmt, array ( - 'adminid' => $adminid, - 'cid' => $id - ) ); - - // now, recalculate the resource-usage for the old and the new admin - updateCounters ( false ); - - return true; -} From bda652f9477b419eca47003e7449405dc5b31030 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sat, 24 Feb 2018 16:26:47 +0100 Subject: [PATCH 060/746] tiny fixes in Domains.add and Domains.update Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Domains.php | 33 ++++++++-------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index 1e135c2c..ad879bb6 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -181,18 +181,12 @@ class Domains extends ApiCommand implements ResourceEntity 'mydomain' ), '', true); } - - $customer_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_CUSTOMERS . "` - WHERE `customerid` = :customerid " . ($this->getUserDetail('customers_see_all') ? '' : " AND `adminid` = :adminid")); - $params = array( - 'customerid' => $customerid - ); - if ($this->getUserDetail('customers_see_all') == '0') { - $params['adminid'] = $this->getUserDetail('adminid'); - } - $customer = Database::pexecute_first($customer_stmt, $params, true, true); - + + $json_result = Customers::getLocal($this->getUserData(), array( + 'id' => $customerid + ))->get(); + $customer = json_decode($json_result, true)['data']; + if (empty($customer) || $customer['customerid'] != $customerid) { standard_error('customerdoesntexist', '', true); } @@ -859,14 +853,9 @@ class Domains extends ApiCommand implements ResourceEntity if ($this->getUserDetail('customers_see_all') == '0') { $params['adminid'] = $this->getUserDetail('adminid'); } - - // get domains customer - $json_result = Customers::getLocal($this->getUserData(), array( - 'id' => $result['customerid'] - ))->get(); - $customer = json_decode($json_result, true)['data']; - - if (empty($customer) || $customer['customerid'] != $customerid) { + + $result = Database::pexecute_first($customer_stmt, $params, true, true); + if (empty($result) || $result['customerid'] != $customerid) { standard_error('customerdoesntexist', '', true); } } else { @@ -943,9 +932,9 @@ class Domains extends ApiCommand implements ResourceEntity // If path is empty and 'Use domain name as default value for DocumentRoot path' is enabled in settings, // set default path to subdomain or domain name if (Settings::Get('system.documentroot_use_default_value') == 1) { - $documentroot = makeCorrectDir($customer['documentroot'] . '/' . $result['domain']); + $documentroot = makeCorrectDir($result['documentroot'] . '/' . $result['domain']); } else { - $documentroot = $customer['documentroot']; + $documentroot = $result['documentroot']; } } From 20aac1ccd4b8f2cc86e6cd294f004de84c9cc7db Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sat, 24 Feb 2018 16:46:56 +0100 Subject: [PATCH 061/746] finish Mysqls.update, untested Signed-off-by: Michael Kaufmann (d00p) --- admin_admins.php | 1 - customer_mysql.php | 44 +----- lib/classes/api/commands/class.Mysqls.php | 164 +++++++++++++++++++--- 3 files changed, 145 insertions(+), 64 deletions(-) diff --git a/admin_admins.php b/admin_admins.php index 1404aa6d..49784818 100644 --- a/admin_admins.php +++ b/admin_admins.php @@ -229,7 +229,6 @@ if ($page == 'admins' } elseif($action == 'edit' && $id != 0 ) { - try { $json_result = Admins::getLocal($userinfo, array( 'id' => $id diff --git a/customer_mysql.php b/customer_mysql.php index 9e1b0b8e..5407b172 100644 --- a/customer_mysql.php +++ b/customer_mysql.php @@ -172,48 +172,12 @@ if ($page == 'overview') { $result = json_decode($json_result, true)['data']; if (isset($result['databasename']) && $result['databasename'] != '') { - if (!isset($sql_root[$result['dbserver']]) || !is_array($sql_root[$result['dbserver']])) { - $result['dbserver'] = 0; - } - if (isset($_POST['send']) && $_POST['send'] == 'send') { - // Only change Password if it is set, do nothing if it is empty! -- PH 2004-11-29 - $password = validate($_POST['mysql_password'], 'password'); - if ($password != '') { - // validate password - $password = validatePassword($password); - - if ($password == $result['databasename']) { - standard_error('passwordshouldnotbeusername'); - } - - // Begin root-session - Database::needRoot(true); - foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) { - $stmt = Database::prepare("SET PASSWORD FOR :dbname@:host = PASSWORD(:password)"); - $params = array( - "dbname" => $result['databasename'], - "host" => $mysql_access_host, - "password" => $password - ); - Database::pexecute($stmt, $params); - } - - $stmt = Database::prepare("FLUSH PRIVILEGES"); - Database::pexecute($stmt); - Database::needRoot(false); - // End root-session + try { + $json_result = Mysqls::getLocal($userinfo, $_POST)->update(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - - // Update the Database description -- PH 2004-11-29 - $log->logAction(USR_ACTION, LOG_INFO, "edited database '" . $result['databasename'] . "'"); - $databasedescription = validate($_POST['description'], 'description'); - $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_DATABASES . "` - SET `description` = :desc - WHERE `customerid` = :customerid - AND `id` = :id" - ); - Database::pexecute($stmt, array("desc" => $databasedescription, "customerid" => $userinfo['customerid'], "id" => $id)); redirectTo($filename, array('page' => $page, 's' => $s)); } else { diff --git a/lib/classes/api/commands/class.Mysqls.php b/lib/classes/api/commands/class.Mysqls.php index eed6b999..71558a1e 100644 --- a/lib/classes/api/commands/class.Mysqls.php +++ b/lib/classes/api/commands/class.Mysqls.php @@ -31,7 +31,7 @@ class Mysqls extends ApiCommand implements ResourceEntity * optional, send created resource-information to customer, default: false * @param int $customer_id * required when called as admin, not needed when called as customer - * + * * @access admin, customer * @throws Exception * @return array @@ -39,20 +39,20 @@ class Mysqls extends ApiCommand implements ResourceEntity public function add() { if ($this->getUserDetail('mysqls_used') < $this->getUserDetail('mysqls') || $this->getUserDetail('mysqls') == '-1') { - + // required paramters $password = $this->getParam('mysql_password'); - + // parameters $dbserver = $this->getParam('mysql_server', true, 0); $databasedescription = $this->getParam('description', true, ''); $sendinfomail = $this->getParam('sendinfomail', true, 0); - + // validation $password = validate($password, 'password', '', '', array(), true); $password = validatePassword($password, true); $databasedescription = validate(trim($databasedescription), 'description', '', '', array(), true); - + // validate whether the dbserver exists $dbserver = validate($dbserver, html_entity_decode($this->lng['mysql']['mysql_server']), '', '', 0, true); Database::needRoot(true, $dbserver); @@ -62,18 +62,18 @@ class Mysqls extends ApiCommand implements ResourceEntity if (! isset($sql_root) || ! is_array($sql_root)) { throw new ErrorException("Database server with index #" . $dbserver . " is unknown", 404); } - + if ($password == '') { standard_error(array( 'stringisempty', 'mysql_password' ), '', true); } - + if ($sendinfomail != 1) { $sendinfomail = 0; } - + // get needed customer info to reduce the mysql-usage-counter by one if ($this->isAdmin()) { // get customer id @@ -89,7 +89,7 @@ class Mysqls extends ApiCommand implements ResourceEntity } else { $customer_id = $this->getUserDetail('customer_id'); } - + $newdb_params = array( 'loginname' => ($this->isAdmin() ? $customer['loginname'] : $this->getUserDetail('loginname')), 'mysql_lastaccountnumber' => ($this->isAdmin() ? $customer['mysql_lastaccountnumber'] : $this->getUserDetail('mysql_lastaccountnumber')) @@ -97,12 +97,12 @@ class Mysqls extends ApiCommand implements ResourceEntity // create database, user, set permissions, etc.pp. $dbm = new DbManager($this->logger()); $username = $dbm->createDatabase($newdb_params['loginname'], $password, $newdb_params['mysql_lastaccountnumber']); - + // we've checked against the password in dbm->createDatabase if ($username == false) { standard_error('passwordshouldnotbeusername', '', true); } - + // add database info to froxlor $stmt = Database::prepare(" INSERT INTO `" . TABLE_PANEL_DATABASES . "` @@ -121,7 +121,7 @@ class Mysqls extends ApiCommand implements ResourceEntity Database::pexecute($stmt, $params, true, true); $databaseid = Database::lastInsertId(); $params['id'] = $databaseid; - + // update customer usage $stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_CUSTOMERS . "` @@ -131,7 +131,7 @@ class Mysqls extends ApiCommand implements ResourceEntity Database::pexecute($stmt, array( "customerid" => ($this->isAdmin() ? $customer['customerid'] : $this->getUserDetail('customerid')) ), true, true); - + // update admin usage $stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_ADMINS . "` @@ -141,20 +141,20 @@ class Mysqls extends ApiCommand implements ResourceEntity Database::pexecute($stmt, array( "adminid" => $this->getUserDetail('adminid') ), true, true); - + // send info-mail? if ($sendinfomail == 1) { $pma = $this->lng['admin']['notgiven']; if (Settings::Get('panel.phpmyadmin_url') != '') { $pma = Settings::Get('panel.phpmyadmin_url'); } - + Database::needRoot(true, $dbserver); Database::needSqlData(); $sql_root = Database::getSqlData(); Database::needRoot(false); $userinfo = ($this->isAdmin() ? $customer : $this->getUserData()); - + $replace_arr = array( 'SALUTATION' => getCorrectUserSalutation($userinfo), 'CUST_NAME' => getCorrectUserSalutation($userinfo), // < keep this for compatibility @@ -164,7 +164,7 @@ class Mysqls extends ApiCommand implements ResourceEntity 'DB_SRV' => $sql_root['host'], 'PMA_URI' => $pma ); - + $def_language = $userinfo['def_language']; $result_stmt = Database::prepare(" SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` @@ -178,7 +178,7 @@ class Mysqls extends ApiCommand implements ResourceEntity "lang" => $def_language ), true, true); $mail_subject = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $this->lng['mails']['new_database_by_customer']['subject']), $replace_arr)); - + $result_stmt = Database::prepare(" SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` WHERE `adminid`= :adminid @@ -191,7 +191,7 @@ class Mysqls extends ApiCommand implements ResourceEntity "lang" => $def_language )); $mail_body = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $this->lng['mails']['new_database_by_customer']['mailbody']), $replace_arr)); - + $_mailerror = false; try { $this->mail->Subject = $mail_subject; @@ -206,12 +206,12 @@ class Mysqls extends ApiCommand implements ResourceEntity $mailerr_msg = $e->getMessage(); $_mailerror = true; } - + if ($_mailerror) { $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_ERR, "[API] Error sending mail: " . $mailerr_msg); standard_error('errorsendingmail', $userinfo['email'], true); } - + $this->mail->ClearAddresses(); } $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_WARNING, "[API] added mysql-database '" . $username . "'"); @@ -319,8 +319,126 @@ class Mysqls extends ApiCommand implements ResourceEntity throw new Exception("MySQL database with " . $key . " could not be found", 404); } + /** + * update a mysql database entry by either id or dbname + * + * @param int $id + * optional, the database-id + * @param string $dbname + * optional, the databasename + * @param int $dbserver + * optional, specify database-server, default is none + * @param string $mysql_password + * optional, update password for the database + * @param string $description + * optional, description for database + * + * @access admin, customer + * @throws Exception + * @return array + */ public function update() - {} + { + $id = $this->getParam('id', true, 0); + $dn_optional = ($id <= 0 ? false : true); + $dbname = $this->getParam('dbname', $dn_optional, ''); + $dbserver = $this->getParam('dbserver', true, - 1); + + if ($id <= 0 && empty($dbname)) { + throw new Exception("Either 'id' or 'dbname' parameter must be given", 406); + } + + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'mysql')) { + throw new Exception("You cannot access this resource", 405); + } + + $json_result = Mysqls::getLocal($this->getUserData(), array( + 'id' => $id, + 'dbname' => $dbname, + 'dbserver' => $dbserver + ))->get(); + $result = json_decode($json_result, true)['data']; + $id = $result['id']; + + // paramters + $password = $this->getParam('mysql_password', true, ''); + $databasedescription = $this->getParam('description', true, ''); + + // validation + $password = validate($password, 'password', '', '', array(), true); + $password = validatePassword($password, true); + $databasedescription = validate(trim($databasedescription), 'description', '', '', array(), true); + + // validate whether the dbserver exists + $dbserver = validate($dbserver, html_entity_decode($this->lng['mysql']['mysql_server']), '', '', 0, true); + Database::needRoot(true, $dbserver); + Database::needSqlData(); + $sql_root = Database::getSqlData(); + Database::needRoot(false); + if (! isset($sql_root) || ! is_array($sql_root)) { + throw new ErrorException("Database server with index #" . $dbserver . " is unknown", 404); + } + + if ($sendinfomail != 1) { + $sendinfomail = 0; + } + + // get needed customer info to reduce the mysql-usage-counter by one + if ($this->isAdmin()) { + // get customer id + $customer_id = $this->getParam('customer_id'); + $json_result = Customers::getLocal($this->getUserData(), array( + 'id' => $result['customerid'] + ))->get(); + $customer = json_decode($json_result, true)['data']; + // check whether the customer has enough resources to get the database added + if ($customer['mysqls_used'] >= $customer['mysqls'] && $customer['mysqls'] != '-1') { + throw new Exception("Customer has no more resources available", 406); + } + } else { + $customer_id = $this->getUserDetail('customer_id'); + } + + if ($password != '') { + // validate password + $password = validatePassword($password, true); + + if ($password == $result['databasename']) { + standard_error('passwordshouldnotbeusername', '', true); + } + + // Begin root-session + Database::needRoot(true, $result['dbserver']); + foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) { + $stmt = Database::prepare("SET PASSWORD FOR :dbname@:host = PASSWORD(:password)"); + $params = array( + "dbname" => $result['databasename'], + "host" => $mysql_access_host, + "password" => $password + ); + Database::pexecute($stmt, $params, true, true); + } + + $stmt = Database::prepare("FLUSH PRIVILEGES"); + Database::pexecute($stmt, null, true, true); + Database::needRoot(false); + // End root-session + } + $stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_DATABASES . "` + SET `description` = :desc + WHERE `customerid` = :customerid + AND `id` = :id + "); + $params = array( + "desc" => $databasedescription, + "customerid" => ($this->isAdmin() ? $customer['customerid'] : $this->getUserDetail('customerid')), + "id" => $id + ); + Database::pexecute($stmt, $params, true, true); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_WARNING, "[API] updated mysql-database '" . $result['databasename'] . "'"); + return $this->response(200, "successfull", $params); + } /** * list all databases, if called from an admin, list all databases of all customers you are allowed to view, or specify id or loginname for one specific customer @@ -444,7 +562,7 @@ class Mysqls extends ApiCommand implements ResourceEntity if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'mysql')) { throw new Exception("You cannot access this resource", 405); } - + $json_result = Mysqls::getLocal($this->getUserData(), array( 'id' => $id, 'dbname' => $dbname, From e5a1b504d7675c060e2fd283d14acdfbf0b2f073 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sat, 24 Feb 2018 16:52:14 +0100 Subject: [PATCH 062/746] consistency replace dbserver with mysql_server everywhere Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Mysqls.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/classes/api/commands/class.Mysqls.php b/lib/classes/api/commands/class.Mysqls.php index 71558a1e..77d8d28f 100644 --- a/lib/classes/api/commands/class.Mysqls.php +++ b/lib/classes/api/commands/class.Mysqls.php @@ -227,7 +227,7 @@ class Mysqls extends ApiCommand implements ResourceEntity * optional, the database-id * @param string $dbname * optional, the databasename - * @param int $dbserver + * @param int $mysql_server * optional, specify database-server, default is none * * @access admin, customer @@ -239,7 +239,7 @@ class Mysqls extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $dn_optional = ($id <= 0 ? false : true); $dbname = $this->getParam('dbname', $dn_optional, ''); - $dbserver = $this->getParam('dbserver', true, - 1); + $dbserver = $this->getParam('mysql_server', true, - 1); if ($id <= 0 && empty($dbname)) { throw new Exception("Either 'id' or 'dbname' parameter must be given", 406); @@ -326,7 +326,7 @@ class Mysqls extends ApiCommand implements ResourceEntity * optional, the database-id * @param string $dbname * optional, the databasename - * @param int $dbserver + * @param int $mysql_server * optional, specify database-server, default is none * @param string $mysql_password * optional, update password for the database @@ -342,7 +342,7 @@ class Mysqls extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $dn_optional = ($id <= 0 ? false : true); $dbname = $this->getParam('dbname', $dn_optional, ''); - $dbserver = $this->getParam('dbserver', true, - 1); + $dbserver = $this->getParam('mysql_server', true, - 1); if ($id <= 0 && empty($dbname)) { throw new Exception("Either 'id' or 'dbname' parameter must be given", 406); @@ -355,7 +355,7 @@ class Mysqls extends ApiCommand implements ResourceEntity $json_result = Mysqls::getLocal($this->getUserData(), array( 'id' => $id, 'dbname' => $dbname, - 'dbserver' => $dbserver + 'mysql_server' => $dbserver ))->get(); $result = json_decode($json_result, true)['data']; $id = $result['id']; @@ -443,7 +443,7 @@ class Mysqls extends ApiCommand implements ResourceEntity /** * list all databases, if called from an admin, list all databases of all customers you are allowed to view, or specify id or loginname for one specific customer * - * @param int $dbserver + * @param int $mysql_server * optional, specify dbserver to select from, else use all available * @param int $customerid * optional, admin-only, select dbs of a specific customer by id @@ -456,7 +456,7 @@ class Mysqls extends ApiCommand implements ResourceEntity public function list() { $result = array(); - $dbserver = $this->getParam('dbserver', true, - 1); + $dbserver = $this->getParam('mysql_server', true, - 1); if ($this->isAdmin()) { // if we're an admin, list all databases of all the admins customers // or optionally for one specific customer identified by id or loginname @@ -541,7 +541,7 @@ class Mysqls extends ApiCommand implements ResourceEntity * optional, the database-id * @param string $dbname * optional, the databasename - * @param int $dbserver + * @param int $mysql_server * optional, specify database-server, default is none * * @access admin, customer @@ -553,7 +553,7 @@ class Mysqls extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $dn_optional = ($id <= 0 ? false : true); $dbname = $this->getParam('dbname', $dn_optional, ''); - $dbserver = $this->getParam('dbserver', true, - 1); + $dbserver = $this->getParam('mysql_server', true, - 1); if ($id <= 0 && empty($dbname)) { throw new Exception("Either 'id' or 'dbname' parameter must be given", 406); @@ -566,7 +566,7 @@ class Mysqls extends ApiCommand implements ResourceEntity $json_result = Mysqls::getLocal($this->getUserData(), array( 'id' => $id, 'dbname' => $dbname, - 'dbserver' => $dbserver + 'mysql_server' => $dbserver ))->get(); $result = json_decode($json_result, true)['data']; $id = $result['id']; From ca07621de77389b62adbe04b894df2aa99d0d99f Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sat, 24 Feb 2018 20:12:57 +0100 Subject: [PATCH 063/746] reduce mysql-usage counter for admins too when deleting a mysql-database Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Mysqls.php | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/classes/api/commands/class.Mysqls.php b/lib/classes/api/commands/class.Mysqls.php index 77d8d28f..7b72a65c 100644 --- a/lib/classes/api/commands/class.Mysqls.php +++ b/lib/classes/api/commands/class.Mysqls.php @@ -378,11 +378,7 @@ class Mysqls extends ApiCommand implements ResourceEntity if (! isset($sql_root) || ! is_array($sql_root)) { throw new ErrorException("Database server with index #" . $dbserver . " is unknown", 404); } - - if ($sendinfomail != 1) { - $sendinfomail = 0; - } - + // get needed customer info to reduce the mysql-usage-counter by one if ($this->isAdmin()) { // get customer id @@ -436,6 +432,7 @@ class Mysqls extends ApiCommand implements ResourceEntity "id" => $id ); Database::pexecute($stmt, $params, true, true); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_WARNING, "[API] updated mysql-database '" . $result['databasename'] . "'"); return $this->response(200, "successfull", $params); } @@ -607,6 +604,16 @@ class Mysqls extends ApiCommand implements ResourceEntity Database::pexecute($stmt, array( "customerid" => $customer_id ), true, true); + // update admin usage + $stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_ADMINS . "` + SET `mysqls_used` = `mysqls_used` - 1 + WHERE `adminid` = :adminid + "); + Database::pexecute($stmt, array( + "adminid" => ($this->isAdmin() ? $customer['adminid'] : $this->getUserDetail('adminid')), + ), true, true); + return $this->response(200, "successfull", $result); } } From 2f30d85d325486cb04825c56ee94af6c63e6db37 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sat, 24 Feb 2018 20:52:21 +0100 Subject: [PATCH 064/746] minor changes in ApiCommand; added Ftps.get ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- api.php | 2 +- customer_ftp.php | 28 ++++--- lib/classes/api/abstract.ApiCommand.php | 9 ++- lib/classes/api/commands/class.Ftps.php | 98 +++++++++++++++++++++++++ 4 files changed, 121 insertions(+), 16 deletions(-) create mode 100644 lib/classes/api/commands/class.Ftps.php diff --git a/api.php b/api.php index a5dd6fe3..6dc25ff3 100644 --- a/api.php +++ b/api.php @@ -56,7 +56,7 @@ function json_response($status, $status_message = '', $data = null) { $resheader = $_SERVER["SERVER_PROTOCOL"] . " " . $status; if (! empty($status_message)) { - $resheader .= ' ' . $status_message; + $resheader .= ' ' . str_replace("\n", " ", $status_message); } header($resheader); diff --git a/customer_ftp.php b/customer_ftp.php index 46cd71f4..e40886d5 100644 --- a/customer_ftp.php +++ b/customer_ftp.php @@ -79,12 +79,14 @@ if ($page == 'overview') { eval("echo \"" . getTemplate('ftp/accounts') . "\";"); } elseif ($action == 'delete' && $id != 0) { - $result_stmt = Database::prepare("SELECT `id`, `username`, `homedir`, `up_count`, `up_bytes`, `down_count`, `down_bytes` FROM `" . TABLE_FTP_USERS . "` - WHERE `customerid` = :customerid - AND `id` = :id" - ); - Database::pexecute($result_stmt, array("customerid" => $userinfo['customerid'], "id" => $id)); - $result = $result_stmt->fetch(PDO::FETCH_ASSOC); + try { + $json_result = Ftps::getLocal($userinfo, array( + 'id' => $id + ))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; if (isset($result['username']) && $result['username'] != $userinfo['loginname']) { if (isset($_POST['send']) && $_POST['send'] == 'send') { @@ -369,12 +371,14 @@ if ($page == 'overview') { } } } elseif ($action == 'edit' && $id != 0) { - $result_stmt = Database::prepare("SELECT `id`, `username`, `description`, `homedir`, `uid`, `gid`, `shell` FROM `" . TABLE_FTP_USERS . "` - WHERE `customerid` = :customerid - AND `id` = :id" - ); - Database::pexecute($result_stmt, array("customerid" => $userinfo['customerid'], "id" => $id)); - $result = $result_stmt->fetch(PDO::FETCH_ASSOC); + try { + $json_result = Ftps::getLocal($userinfo, array( + 'id' => $id + ))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; if (isset($result['username']) && $result['username'] != '') { if (isset($_POST['send']) && $_POST['send'] == 'send') { diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index ee8f1a90..8ce98e8d 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -402,7 +402,7 @@ abstract class ApiCommand { $resheader = $_SERVER["SERVER_PROTOCOL"] . " " . $status; if (! empty($status_message)) { - $resheader .= ' ' . $status_message; + $resheader .= ' ' . str_replace("\n", " ", $status_message); } header($resheader); @@ -432,14 +432,17 @@ abstract class ApiCommand ), true, true); if ($result) { // admin or customer? - if ($result['customerid'] == 0) { + if ($result['customerid'] == 0 && $result['adminid'] > 0) { $this->is_admin = true; $table = 'panel_admins'; $key = "adminid"; - } else { + } elseif ($result['customerid'] > 0 && $result['adminid'] > 0) { $this->is_admin = false; $table = 'panel_customers'; $key = "customerid"; + } else { + // neither adminid is > 0 nor customerid is > 0 - sorry man, no way + throw new Exception("Invalid API credentials", 400); } $sel_stmt = Database::prepare("SELECT * FROM `" . $table . "` WHERE `" . $key . "` = :id"); $this->user_data = Database::pexecute_first($sel_stmt, array( diff --git a/lib/classes/api/commands/class.Ftps.php b/lib/classes/api/commands/class.Ftps.php new file mode 100644 index 00000000..6b27c3fb --- /dev/null +++ b/lib/classes/api/commands/class.Ftps.php @@ -0,0 +1,98 @@ + (2010-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package API + * @since 0.10.0 + * + */ +class Ftps extends ApiCommand implements ResourceEntity +{ + + public function add() + {} + + /** + * return a ftp-user entry by either id or username + * + * @param int $id + * optional, the customer-id + * @param string $username + * optional, the username + * + * @access admin, customer + * @throws Exception + * @return array + */ + public function get() + { + $id = $this->getParam('id', true, 0); + $un_optional = ($id <= 0 ? false : true); + $username = $this->getParam('username', $un_optional, ''); + + if ($id <= 0 && empty($username)) { + throw new Exception("Either 'id' or 'username' parameter must be given", 406); + } + + $params = array(); + if ($this->isAdmin()) { + if ($this->getUserDetail('customers_see_all') != 1) { + // if it's a reseller or an admin who cannot see all customers, we need to check + // whether the database belongs to one of his customers + $json_result = Customers::getLocal($this->getUserData())->list(); + $custom_list_result = json_decode($json_result, true)['data']['list']; + $customer_ids = array(); + foreach ($custom_list_result as $customer) { + $customer_ids[] = $customer['customerid']; + } + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_FTP_USERS . "` + WHERE `customerid` IN (:customerid) + AND (`id` = :idun OR `username` = :idun) + "); + $params['customerid'] = implode(", ", $customer_ids); + } else { + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_FTP_USERS . "` + WHERE (`id` = :idun OR `username` = :idun) + "); + } + } else { + if (Settings::IsInList('panel.customer_hide_options', 'ftp')) { + throw new Exception("You cannot access this resource", 405); + } + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_FTP_USERS . "` + WHERE `customerid` = :customerid + AND (`id` = :idun OR `username` = :idun) + "); + $params['customerid'] = $this->getUserDetail('customerid'); + } + $params['idun'] = ($id <= 0 ? $username : $id); + $result = Database::pexecute_first($result_stmt, $params, true, true); + if ($result) { + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_NOTICE, "[API] get ftp-user '" . $result['username'] . "'"); + return $this->response(200, "successfull", $result); + } + $key = ($id > 0 ? "id #" . $id : "username '" . $username . "'"); + throw new Exception("FTP user with " . $key . " could not be found", 404); + } + + public function update() + {} + + public function list() + {} + + public function delete() + {} +} From 243b68cc371a8d4fa83dff58e0724336f40795ff Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sun, 25 Feb 2018 11:47:28 +0100 Subject: [PATCH 065/746] minor changes for testing Signed-off-by: Michael Kaufmann (d00p) --- api.php | 10 ++++++---- lib/classes/api/abstract.ApiCommand.php | 16 +++++++++------- lib/classes/api/commands/class.Ftps.php | 2 +- lib/functions.php | 5 +++++ 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/api.php b/api.php index 6dc25ff3..db419d30 100644 --- a/api.php +++ b/api.php @@ -54,11 +54,13 @@ exit(); */ function json_response($status, $status_message = '', $data = null) { - $resheader = $_SERVER["SERVER_PROTOCOL"] . " " . $status; - if (! empty($status_message)) { - $resheader .= ' ' . str_replace("\n", " ", $status_message); + if (isset($_SERVER["SERVER_PROTOCOL"]) && ! empty($_SERVER["SERVER_PROTOCOL"])) { + $resheader = $_SERVER["SERVER_PROTOCOL"] . " " . $status; + if (! empty($status_message)) { + $resheader .= ' ' . str_replace("\n", " ", $status_message); + } + header($resheader); } - header($resheader); $response['status'] = $status; $response['status_message'] = $status_message; diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index 8ce98e8d..82fd38c8 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -116,7 +116,7 @@ abstract class ApiCommand throw new Exception("Invalid user data", 500); } $this->logger = FroxlorLogger::getInstanceOf($this->user_data); - + // check whether the user is deactivated if ($this->getUserDetail('deactivated') == 1) { $this->logger()->logAction(LOG_ERROR, LOG_INFO, "[API] User '" . $this->getUserDetail('loginnname') . "' tried to use API but is deactivated"); @@ -161,13 +161,13 @@ abstract class ApiCommand // include every english language file we can get foreach ($langs['English'] as $key => $value) { - include_once makeSecurePath($value['file']); + include_once makeSecurePath(FROXLOR_INSTALL_DIR . '/' . $value['file']); } // now include the selected language if its not english if ($language != 'English') { foreach ($langs[$language] as $key => $value) { - include_once makeSecurePath($value['file']); + include_once makeSecurePath(FROXLOR_INSTALL_DIR . '/' . $value['file']); } } @@ -400,11 +400,13 @@ abstract class ApiCommand */ protected function response($status, $status_message, $data = null) { - $resheader = $_SERVER["SERVER_PROTOCOL"] . " " . $status; - if (! empty($status_message)) { - $resheader .= ' ' . str_replace("\n", " ", $status_message); + if (isset($_SERVER["SERVER_PROTOCOL"]) && ! empty($_SERVER["SERVER_PROTOCOL"])) { + $resheader = $_SERVER["SERVER_PROTOCOL"] . " " . $status; + if (! empty($status_message)) { + $resheader .= ' ' . str_replace("\n", " ", $status_message); + } + header($resheader); } - header($resheader); $response['status'] = $status; $response['status_message'] = $status_message; diff --git a/lib/classes/api/commands/class.Ftps.php b/lib/classes/api/commands/class.Ftps.php index 6b27c3fb..352302e3 100644 --- a/lib/classes/api/commands/class.Ftps.php +++ b/lib/classes/api/commands/class.Ftps.php @@ -45,7 +45,7 @@ class Ftps extends ApiCommand implements ResourceEntity $params = array(); if ($this->isAdmin()) { - if ($this->getUserDetail('customers_see_all') != 1) { + if ($this->getUserDetail('customers_see_all') == false) { // if it's a reseller or an admin who cannot see all customers, we need to check // whether the database belongs to one of his customers $json_result = Customers::getLocal($this->getUserData())->list(); diff --git a/lib/functions.php b/lib/functions.php index ebfd5bb6..5a15cac8 100644 --- a/lib/functions.php +++ b/lib/functions.php @@ -100,6 +100,11 @@ class Autoloader { return true; } + // don't load anything from a namespace, it's not our responsibility + if (strpos($class, "\\") !== false) { + return true; + } + // now iterate through the paths foreach ($paths as $path) { // valid directory? From a222114d0a41de43f80a76e9573e1a14fd40d4f4 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sun, 25 Feb 2018 15:02:58 +0100 Subject: [PATCH 066/746] remove unnecessary parameter-checks as they will never happen; make Customers.update callable for customers Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Admins.php | 28 +- lib/classes/api/commands/class.Customers.php | 954 +++++++++--------- lib/classes/api/commands/class.Domains.php | 24 +- lib/classes/api/commands/class.Ftps.php | 8 +- lib/classes/api/commands/class.Mysqls.php | 24 +- .../output/function.standard_error.php | 2 +- 6 files changed, 488 insertions(+), 552 deletions(-) diff --git a/lib/classes/api/commands/class.Admins.php b/lib/classes/api/commands/class.Admins.php index a37a894c..6c0de7ae 100644 --- a/lib/classes/api/commands/class.Admins.php +++ b/lib/classes/api/commands/class.Admins.php @@ -63,12 +63,8 @@ class Admins extends ApiCommand implements ResourceEntity { $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); - $loginname = $this->getParam('loginname', $ln_optional, ''); - - if ($id <= 0 && empty($loginname)) { - throw new Exception("Either 'id' or 'loginname' parameter must be given", 406); - } - + $loginname = trim($this->getParam('loginname', $ln_optional, '')); + if ($this->isAdmin() && ($this->getUserDetail('change_serversettings') == 1 || ($this->getUserDetail('adminid') == $id || $this->getUserDetail('loginname') == $loginname))) { $result_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_ADMINS . "` @@ -318,11 +314,7 @@ class Admins extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); - $loginname = $this->getParam('loginname', $ln_optional, ''); - - if ($id <= 0 && empty($loginname)) { - throw new Exception("Either 'id' or 'loginname' parameter must be given", 406); - } + $loginname = trim($this->getParam('loginname', $ln_optional, '')); $json_result = Admins::getLocal($this->getUserData(), array( 'id' => $id, @@ -583,11 +575,7 @@ class Admins extends ApiCommand implements ResourceEntity if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); - $loginname = $this->getParam('loginname', $ln_optional, ''); - - if ($id <= 0 && empty($loginname)) { - throw new Exception("Either 'id' or 'loginname' parameter must be given", 406); - } + $loginname = trim($this->getParam('loginname', $ln_optional, '')); $json_result = Admins::getLocal($this->getUserData(), array( 'id' => $id, @@ -652,12 +640,8 @@ class Admins extends ApiCommand implements ResourceEntity if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); - $loginname = $this->getParam('loginname', $ln_optional, ''); - - if ($id <= 0 && empty($loginname)) { - throw new Exception("Either 'id' or 'loginname' parameter must be given", 406); - } - + $loginname = trim($this->getParam('loginname', $ln_optional, '')); + $json_result = Admins::getLocal($this->getUserData(), array( 'id' => $id, 'loginname' => $loginname diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index d3a762e4..e827d32a 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -70,11 +70,7 @@ class Customers extends ApiCommand implements ResourceEntity { $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); - $loginname = $this->getParam('loginname', $ln_optional, ''); - - if ($id <= 0 && empty($loginname)) { - throw new Exception("Either 'id' or 'loginname' parameter must be given", 406); - } + $loginname = trim($this->getParam('loginname', $ln_optional, '')); if ($this->isAdmin()) { $result_stmt = Database::prepare(" @@ -131,7 +127,7 @@ class Customers extends ApiCommand implements ResourceEntity $phone = $this->getParam('phone', true, ''); $fax = $this->getParam('fax', true, ''); $customernumber = $this->getParam('customernumber', true, ''); - $def_language = $this->getParam('def_language', true, ''); + $def_language = $this->getParam('def_language', true, Settings::Get('panel.standardlanguage')); $gender = intval_ressource($this->getParam('gender', true, 0)); $custom_notes = $this->getParam('custom_notes', true, ''); $custom_notes_show = $this->getParam('custom_notes_show', true, 0); @@ -681,379 +677,373 @@ class Customers extends ApiCommand implements ResourceEntity */ public function update() { + $id = $this->getParam('id', true, 0); + $ln_optional = ($id <= 0 ? false : true); + $loginname = trim($this->getParam('loginname', $ln_optional, '')); + + $json_result = Customers::getLocal($this->getUserData(), array( + 'id' => $id, + 'loginname' => $loginname + ))->get(); + $result = json_decode($json_result, true)['data']; + $id = $result['customerid']; + if ($this->isAdmin()) { - $id = $this->getParam('id', true, 0); - $ln_optional = ($id <= 0 ? false : true); - $loginname = $this->getParam('loginname', $ln_optional, ''); + // parameters + $move_to_admin = intval_ressource($this->getParam('move_to_admin', true, 0)); - if ($id <= 0 && empty($loginname)) { - throw new Exception("Either 'id' or 'loginname' parameter must be given", 406); - } - - $json_result = Customers::getLocal($this->getUserData(), array( - 'id' => $id, - 'loginname' => $loginname - ))->get(); - $result = json_decode($json_result, true)['data']; - $id = $result['customerid']; - - if ($this->isAdmin()) { - // parameters - $move_to_admin = intval_ressource($this->getParam('move_to_admin', true, 0)); - - $idna_convert = new idna_convert_wrapper(); - $email = $this->getParam('email', true, $idna_convert->decode($result['email'])); - $name = $this->getParam('name', true, $result['name']); - $firstname = $this->getParam('firstname', true, $result['firstname']); - $company = $this->getParam('company', true, $result['company']); - $street = $this->getParam('street', true, $result['street']); - $zipcode = $this->getParam('zipcode', true, $result['zipcode']); - $city = $this->getParam('city', true, $result['city']); - $phone = $this->getParam('phone', true, $result['phone']); - $fax = $this->getParam('fax', true, $result['fax']); - $customernumber = $this->getParam('customernumber', true, $result['customernumber']); - $def_language = $this->getParam('def_language', true, $result['def_language']); - $gender = intval_ressource($this->getParam('gender', true, $result['gender'])); - $custom_notes = $this->getParam('custom_notes', true, $result['custom_notes']); - $custom_notes_show = $this->getParam('custom_notes_show', true, $result['custom_notes_show']); - - $dec_places = Settings::Get('panel.decimal_places'); - $diskspace = $this->getUlParam('diskspace', 'diskspace_ul', true, round($result['diskspace'] / 1024, $dec_places)); - $traffic = $this->getUlParam('traffic', 'traffic_ul', true, round($result['traffic'] / (1024 * 1024), $dec_places)); - $subdomains = $this->getUlParam('subdomains', 'subdomains_ul', true, $result['subdomains']); - $emails = $this->getUlParam('emails', 'emails_ul', true, $result['emails']); - $email_accounts = $this->getUlParam('email_accounts', 'email_accounts_ul', true, $result['email_accounts']); - $email_forwarders = $this->getUlParam('email_forwarders', 'email_forwarders_ul', true, $result['email_forwarders']); - $email_quota = $this->getUlParam('email_quota', 'email_quota_ul', true, $result['email_quota']); - $email_imap = $this->getParam('email_imap', true, $result['imap']); - $email_pop3 = $this->getParam('email_pop3', true, $result['pop3']); - $ftps = $this->getUlParam('ftps', 'ftps_ul', true, $result['ftps']); - $tickets = $this->getUlParam('tickets', 'tickets_ul', true, $result['tickets']); - $mysqls = $this->getUlParam('mysqls', 'mysqls_ul', true, $result['mysqls']); - $createstdsubdomain = $this->getParam('createstdsubdomain', true, 0); - $password = $this->getParam('new_customer_password', true, ''); - $sendpassword = $this->getParam('sendpassword', true, 0); - $phpenabled = $this->getParam('phpenabled', true, $result['phpenabled']); - $allowed_phpconfigs = $this->getParam('allowed_phpconfigs', true, json_decode($result['allowed_phpconfigs'], true)); - $perlenabled = $this->getParam('perlenabled', true, $result['perlenabled']); - $dnsenabled = $this->getParam('dnsenabled', true, $result['dnsenabled']); - $deactivated = $this->getParam('deactivated', true, $result['deactivated']); - $theme = $this->getParam('theme', true, $result['theme']); - } else { - // allowed parameters - $def_language = $this->getParam('def_language', true, $result['def_language']); - $password = $this->getParam('new_customer_password', true, ''); - $theme = $this->getParam('theme', true, $result['theme']); - - // unchangeable parameters for customers - $move_to_admin = 0; - $idna_convert = new idna_convert_wrapper(); - $email = $idna_convert->decode($result['email']); - $name = $result['name']; - $firstname = $result['firstname']; - $company = $result['company']; - $street = $result['street']; - $zipcode = $result['zipcode']; - $city = $result['city']; - $phone = $result['phone']; - $fax = $result['fax']; - $customernumber = $result['customernumber']; - $gender = $result['gender']; - $custom_notes = $result['custom_notes']; - $custom_notes_show = $result['custom_notes_show']; - - $dec_places = Settings::Get('panel.decimal_places'); - $diskspace = round($result['diskspace'] / 1024, $dec_places); - $traffic = round($result['traffic'] / (1024 * 1024), $dec_places); - $subdomains = $result['subdomains']; - $emails = $result['emails']; - $email_accounts = $result['email_accounts']; - $email_forwarders = $result['email_forwarders']; - $email_quota = $result['email_quota']; - $email_imap = $result['imap']; - $email_pop3 = $result['pop3']; - $ftps = $result['ftps']; - $tickets = $result['tickets']; - $mysqls = $result['mysqls']; - // if we got one, it's true so none will be generated (it exists already) - // if not, none will be generated - $createstdsubdomain = ($result['standardsubdomain'] > 0 ? 1 : 0); - $sendpassword = 0; - $phpenabled = $result['phpenabled']; - $allowed_phpconfigs = json_decode($result['allowed_phpconfigs'], true); - $perlenabled = $result['perlenabled']; - $dnsenabled = $result['dnsenabled']; - $deactivated = $result['deactivated']; - } - - // validation $idna_convert = new idna_convert_wrapper(); - $name = validate($name, 'name', '', '', array(), true); - $firstname = validate($firstname, 'first name', '', '', array(), true); - $company = validate($company, 'company', '', '', array(), true); - $street = validate($street, 'street', '', '', array(), true); - $zipcode = validate($zipcode, 'zipcode', '/^[0-9 \-A-Z]*$/', '', array(), true); - $city = validate($city, 'city', '', '', array(), true); - $phone = validate($phone, 'phone', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); - $fax = validate($fax, 'fax', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); - $email = $idna_convert->encode(validate($email, 'email', '', '', array(), true)); - $customernumber = validate($customernumber, 'customer number', '/^[A-Za-z0-9 \-]*$/Di', '', array(), true); - $def_language = validate($def_language, 'default language', '', '', array(), true); - $custom_notes = validate(str_replace("\r\n", "\n", $custom_notes), 'custom_notes', '/^[^\0]*$/', '', array(), true); - $theme = validate($theme, 'theme', '', '', array(), true); + $email = $this->getParam('email', true, $idna_convert->decode($result['email'])); + $name = $this->getParam('name', true, $result['name']); + $firstname = $this->getParam('firstname', true, $result['firstname']); + $company = $this->getParam('company', true, $result['company']); + $street = $this->getParam('street', true, $result['street']); + $zipcode = $this->getParam('zipcode', true, $result['zipcode']); + $city = $this->getParam('city', true, $result['city']); + $phone = $this->getParam('phone', true, $result['phone']); + $fax = $this->getParam('fax', true, $result['fax']); + $customernumber = $this->getParam('customernumber', true, $result['customernumber']); + $def_language = $this->getParam('def_language', true, $result['def_language']); + $gender = intval_ressource($this->getParam('gender', true, $result['gender'])); + $custom_notes = $this->getParam('custom_notes', true, $result['custom_notes']); + $custom_notes_show = $this->getParam('custom_notes_show', true, $result['custom_notes_show']); - if (Settings::Get('system.mail_quota_enabled') != '1') { - $email_quota = - 1; + $dec_places = Settings::Get('panel.decimal_places'); + $diskspace = $this->getUlParam('diskspace', 'diskspace_ul', true, round($result['diskspace'] / 1024, $dec_places)); + $traffic = $this->getUlParam('traffic', 'traffic_ul', true, round($result['traffic'] / (1024 * 1024), $dec_places)); + $subdomains = $this->getUlParam('subdomains', 'subdomains_ul', true, $result['subdomains']); + $emails = $this->getUlParam('emails', 'emails_ul', true, $result['emails']); + $email_accounts = $this->getUlParam('email_accounts', 'email_accounts_ul', true, $result['email_accounts']); + $email_forwarders = $this->getUlParam('email_forwarders', 'email_forwarders_ul', true, $result['email_forwarders']); + $email_quota = $this->getUlParam('email_quota', 'email_quota_ul', true, $result['email_quota']); + $email_imap = $this->getParam('email_imap', true, $result['imap']); + $email_pop3 = $this->getParam('email_pop3', true, $result['pop3']); + $ftps = $this->getUlParam('ftps', 'ftps_ul', true, $result['ftps']); + $tickets = $this->getUlParam('tickets', 'tickets_ul', true, $result['tickets']); + $mysqls = $this->getUlParam('mysqls', 'mysqls_ul', true, $result['mysqls']); + $createstdsubdomain = $this->getParam('createstdsubdomain', true, 0); + $password = $this->getParam('new_customer_password', true, ''); + $sendpassword = $this->getParam('sendpassword', true, 0); + $phpenabled = $this->getParam('phpenabled', true, $result['phpenabled']); + $allowed_phpconfigs = $this->getParam('allowed_phpconfigs', true, json_decode($result['allowed_phpconfigs'], true)); + $perlenabled = $this->getParam('perlenabled', true, $result['perlenabled']); + $dnsenabled = $this->getParam('dnsenabled', true, $result['dnsenabled']); + $deactivated = $this->getParam('deactivated', true, $result['deactivated']); + $theme = $this->getParam('theme', true, $result['theme']); + } else { + // allowed parameters + $def_language = $this->getParam('def_language', true, $result['def_language']); + $password = $this->getParam('new_customer_password', true, ''); + $theme = $this->getParam('theme', true, $result['theme']); + + // unchangeable parameters for customers + $move_to_admin = 0; + $idna_convert = new idna_convert_wrapper(); + $email = $idna_convert->decode($result['email']); + $name = $result['name']; + $firstname = $result['firstname']; + $company = $result['company']; + $street = $result['street']; + $zipcode = $result['zipcode']; + $city = $result['city']; + $phone = $result['phone']; + $fax = $result['fax']; + $customernumber = $result['customernumber']; + $gender = $result['gender']; + $custom_notes = $result['custom_notes']; + $custom_notes_show = $result['custom_notes_show']; + + $dec_places = Settings::Get('panel.decimal_places'); + $diskspace = round($result['diskspace'] / 1024, $dec_places); + $traffic = round($result['traffic'] / (1024 * 1024), $dec_places); + $subdomains = $result['subdomains']; + $emails = $result['emails']; + $email_accounts = $result['email_accounts']; + $email_forwarders = $result['email_forwarders']; + $email_quota = $result['email_quota']; + $email_imap = $result['imap']; + $email_pop3 = $result['pop3']; + $ftps = $result['ftps']; + $tickets = $result['tickets']; + $mysqls = $result['mysqls']; + // if we got one, it's true so none will be generated (it exists already) + // if not, none will be generated + $createstdsubdomain = ($result['standardsubdomain'] > 0 ? 1 : 0); + $sendpassword = 0; + $phpenabled = $result['phpenabled']; + $allowed_phpconfigs = json_decode($result['allowed_phpconfigs'], true); + $perlenabled = $result['perlenabled']; + $dnsenabled = $result['dnsenabled']; + $deactivated = $result['deactivated']; + } + + // validation + $idna_convert = new idna_convert_wrapper(); + $name = validate($name, 'name', '', '', array(), true); + $firstname = validate($firstname, 'first name', '', '', array(), true); + $company = validate($company, 'company', '', '', array(), true); + $street = validate($street, 'street', '', '', array(), true); + $zipcode = validate($zipcode, 'zipcode', '/^[0-9 \-A-Z]*$/', '', array(), true); + $city = validate($city, 'city', '', '', array(), true); + $phone = validate($phone, 'phone', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); + $fax = validate($fax, 'fax', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); + $email = $idna_convert->encode(validate($email, 'email', '', '', array(), true)); + $customernumber = validate($customernumber, 'customer number', '/^[A-Za-z0-9 \-]*$/Di', '', array(), true); + $def_language = validate($def_language, 'default language', '', '', array(), true); + $custom_notes = validate(str_replace("\r\n", "\n", $custom_notes), 'custom_notes', '/^[^\0]*$/', '', array(), true); + $theme = validate($theme, 'theme', '', '', array(), true); + + if (Settings::Get('system.mail_quota_enabled') != '1') { + $email_quota = - 1; + } + + if (Settings::Get('ticket.enabled') != '1') { + $tickets = - 1; + } + + if (empty($theme)) { + $theme = Settings::Get('panel.default_theme'); + } + + $diskspace = $diskspace * 1024; + $traffic = $traffic * 1024 * 1024; + + if ($this->isAdmin()) { + if (((($this->getUserDetail('diskspace_used') + $diskspace - $result['diskspace']) > $this->getUserDetail('diskspace')) && ($this->getUserDetail('diskspace') / 1024) != '-1') || ((($this->getUserDetail('mysqls_used') + $mysqls - $result['mysqls']) > $this->getUserDetail('mysqls')) && $this->getUserDetail('mysqls') != '-1') || ((($this->getUserDetail('emails_used') + $emails - $result['emails']) > $this->getUserDetail('emails')) && $this->getUserDetail('emails') != '-1') || ((($this->getUserDetail('email_accounts_used') + $email_accounts - $result['email_accounts']) > $this->getUserDetail('email_accounts')) && $this->getUserDetail('email_accounts') != '-1') || ((($this->getUserDetail('email_forwarders_used') + $email_forwarders - $result['email_forwarders']) > $this->getUserDetail('email_forwarders')) && $this->getUserDetail('email_forwarders') != '-1') || ((($this->getUserDetail('email_quota_used') + $email_quota - $result['email_quota']) > $this->getUserDetail('email_quota')) && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') || ((($this->getUserDetail('ftps_used') + $ftps - $result['ftps']) > $this->getUserDetail('ftps')) && $this->getUserDetail('ftps') != '-1') || ((($this->getUserDetail('tickets_used') + $tickets - $result['tickets']) > $this->getUserDetail('tickets')) && $this->getUserDetail('tickets') != '-1') || ((($this->getUserDetail('subdomains_used') + $subdomains - $result['subdomains']) > $this->getUserDetail('subdomains')) && $this->getUserDetail('subdomains') != '-1') || (($diskspace / 1024) == '-1' && ($this->getUserDetail('diskspace') / 1024) != '-1') || ($mysqls == '-1' && $this->getUserDetail('mysqls') != '-1') || ($emails == '-1' && $this->getUserDetail('emails') != '-1') || ($email_accounts == '-1' && $this->getUserDetail('email_accounts') != '-1') || ($email_forwarders == '-1' && $this->getUserDetail('email_forwarders') != '-1') || ($email_quota == '-1' && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') || ($ftps == '-1' && $this->getUserDetail('ftps') != '-1') || ($tickets == '-1' && $this->getUserDetail('tickets') != '-1') || ($subdomains == '-1' && $this->getUserDetail('subdomains') != '-1')) { + standard_error('youcantallocatemorethanyouhave', '', true); } - if (Settings::Get('ticket.enabled') != '1') { - $tickets = - 1; + // Either $name and $firstname or the $company must be inserted + if ($name == '' && $company == '') { + standard_error(array( + 'stringisempty', + 'myname' + ), '', true); + } elseif ($firstname == '' && $company == '') { + standard_error(array( + 'stringisempty', + 'myfirstname' + ), '', true); + } elseif ($email == '') { + standard_error(array( + 'stringisempty', + 'emailadd' + ), '', true); + } elseif (! validateEmail($email)) { + standard_error('emailiswrong', $email, true); } + } + + if ($password != '') { + $password = validatePassword($password, true); + $password = makeCryptPassword($password); + } else { + $password = $result['password']; + } + + if ($createstdsubdomain != '1') { + $createstdsubdomain = '0'; + } + + if ($createstdsubdomain == '1' && $result['standardsubdomain'] == '0') { - if (empty($theme)) { - $theme = Settings::Get('panel.default_theme'); - } - - $diskspace = $diskspace * 1024; - $traffic = $traffic * 1024 * 1024; - - if ($this->isAdmin()) { - if (((($this->getUserDetail('diskspace_used') + $diskspace - $result['diskspace']) > $this->getUserDetail('diskspace')) && ($this->getUserDetail('diskspace') / 1024) != '-1') || ((($this->getUserDetail('mysqls_used') + $mysqls - $result['mysqls']) > $this->getUserDetail('mysqls')) && $this->getUserDetail('mysqls') != '-1') || ((($this->getUserDetail('emails_used') + $emails - $result['emails']) > $this->getUserDetail('emails')) && $this->getUserDetail('emails') != '-1') || ((($this->getUserDetail('email_accounts_used') + $email_accounts - $result['email_accounts']) > $this->getUserDetail('email_accounts')) && $this->getUserDetail('email_accounts') != '-1') || ((($this->getUserDetail('email_forwarders_used') + $email_forwarders - $result['email_forwarders']) > $this->getUserDetail('email_forwarders')) && $this->getUserDetail('email_forwarders') != '-1') || ((($this->getUserDetail('email_quota_used') + $email_quota - $result['email_quota']) > $this->getUserDetail('email_quota')) && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') || ((($this->getUserDetail('ftps_used') + $ftps - $result['ftps']) > $this->getUserDetail('ftps')) && $this->getUserDetail('ftps') != '-1') || ((($this->getUserDetail('tickets_used') + $tickets - $result['tickets']) > $this->getUserDetail('tickets')) && $this->getUserDetail('tickets') != '-1') || ((($this->getUserDetail('subdomains_used') + $subdomains - $result['subdomains']) > $this->getUserDetail('subdomains')) && $this->getUserDetail('subdomains') != '-1') || (($diskspace / 1024) == '-1' && ($this->getUserDetail('diskspace') / 1024) != '-1') || ($mysqls == '-1' && $this->getUserDetail('mysqls') != '-1') || ($emails == '-1' && $this->getUserDetail('emails') != '-1') || ($email_accounts == '-1' && $this->getUserDetail('email_accounts') != '-1') || ($email_forwarders == '-1' && $this->getUserDetail('email_forwarders') != '-1') || ($email_quota == '-1' && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') || ($ftps == '-1' && $this->getUserDetail('ftps') != '-1') || ($tickets == '-1' && $this->getUserDetail('tickets') != '-1') || ($subdomains == '-1' && $this->getUserDetail('subdomains') != '-1')) { - standard_error('youcantallocatemorethanyouhave', '', true); - } - - // Either $name and $firstname or the $company must be inserted - if ($name == '' && $company == '') { - standard_error(array( - 'stringisempty', - 'myname' - ), '', true); - } elseif ($firstname == '' && $company == '') { - standard_error(array( - 'stringisempty', - 'myfirstname' - ), '', true); - } elseif ($email == '') { - standard_error(array( - 'stringisempty', - 'emailadd' - ), '', true); - } elseif (! validateEmail($email)) { - standard_error('emailiswrong', $email, true); - } - } - - if ($password != '') { - $password = validatePassword($password, true); - $password = makeCryptPassword($password); + if (Settings::Get('system.stdsubdomain') !== null && Settings::Get('system.stdsubdomain') != '') { + $_stdsubdomain = $result['loginname'] . '.' . Settings::Get('system.stdsubdomain'); } else { - $password = $result['password']; + $_stdsubdomain = $result['loginname'] . '.' . Settings::Get('system.hostname'); } - if ($createstdsubdomain != '1') { - $createstdsubdomain = '0'; + $ins_data = array( + 'domain' => $_stdsubdomain, + 'customerid' => $result['customerid'], + 'adminid' => $this->getUserDetail('adminid'), + 'docroot' => $result['documentroot'], + 'phpenabled' => $phpenabled, + 'openbasedir' => '1' + ); + $domainid = - 1; + try { + $std_domain = Domains::getLocal($this->getUserData(), $ins_data)->add(); + $domainid = json_decode($std_domain, true)['data']['id']; + } catch (Exception $e) { + $this->logger()->logAction(ADM_ACTION, LOG_ERR, "[API] Unable to add standard-subdomain: " . $e->getMessage()); } - if ($createstdsubdomain == '1' && $result['standardsubdomain'] == '0') { - - if (Settings::Get('system.stdsubdomain') !== null && Settings::Get('system.stdsubdomain') != '') { - $_stdsubdomain = $result['loginname'] . '.' . Settings::Get('system.stdsubdomain'); - } else { - $_stdsubdomain = $result['loginname'] . '.' . Settings::Get('system.hostname'); - } - - $ins_data = array( - 'domain' => $_stdsubdomain, - 'customerid' => $result['customerid'], - 'adminid' => $this->getUserDetail('adminid'), - 'docroot' => $result['documentroot'], - 'phpenabled' => $phpenabled, - 'openbasedir' => '1' - ); - $domainid = - 1; - try { - $std_domain = Domains::getLocal($this->getUserData(), $ins_data)->add(); - $domainid = json_decode($std_domain, true)['data']['id']; - } catch (Exception $e) { - $this->logger()->logAction(ADM_ACTION, LOG_ERR, "[API] Unable to add standard-subdomain: " . $e->getMessage()); - } - - if ($domainid > 0) { - $upd_stmt = Database::prepare(" + if ($domainid > 0) { + $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `standardsubdomain` = :domainid WHERE `customerid` = :customerid "); - Database::pexecute($upd_stmt, array( - 'domainid' => $domainid, - 'customerid' => $result['customerid'] - ), true, true); - $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] automatically added standardsubdomain for user '" . $result['loginname'] . "'"); - inserttask('1'); - } - } - - if ($createstdsubdomain == '0' && $result['standardsubdomain'] != '0') { - - try { - $std_domain = Domains::getLocal($this->getUserData(), array( - 'id' => $result['standardsubdomain'], - 'is_stdsubdomain' => 1 - ))->delete(); - } catch (Exception $e) { - $this->logger()->logAction(ADM_ACTION, LOG_ERR, "[API] Unable to delete standard-subdomain: " . $e->getMessage()); - } - $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] automatically deleted standardsubdomain for user '" . $result['loginname'] . "'"); + Database::pexecute($upd_stmt, array( + 'domainid' => $domainid, + 'customerid' => $result['customerid'] + ), true, true); + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] automatically added standardsubdomain for user '" . $result['loginname'] . "'"); inserttask('1'); } - - if ($deactivated != '1') { - $deactivated = '0'; + } + + if ($createstdsubdomain == '0' && $result['standardsubdomain'] != '0') { + try { + $std_domain = Domains::getLocal($this->getUserData(), array( + 'id' => $result['standardsubdomain'], + 'is_stdsubdomain' => 1 + ))->delete(); + } catch (Exception $e) { + $this->logger()->logAction(ADM_ACTION, LOG_ERR, "[API] Unable to delete standard-subdomain: " . $e->getMessage()); } + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] automatically deleted standardsubdomain for user '" . $result['loginname'] . "'"); + inserttask('1'); + } + + if ($deactivated != '1') { + $deactivated = '0'; + } + + if ($phpenabled != '0') { + $phpenabled = '1'; + } + + if ($perlenabled != '0') { + $perlenabled = '1'; + } + + if ($dnsenabled != '0') { + $dnsenabled = '1'; + } + + if ($phpenabled != $result['phpenabled'] || $perlenabled != $result['perlenabled']) { + inserttask('1'); + } + + // activate/deactivate customer services + if ($deactivated != $result['deactivated']) { - if ($phpenabled != '0') { - $phpenabled = '1'; - } + $yesno = (($deactivated) ? 'N' : 'Y'); + $pop3 = (($deactivated) ? '0' : (int) $result['pop3']); + $imap = (($deactivated) ? '0' : (int) $result['imap']); - if ($perlenabled != '0') { - $perlenabled = '1'; - } - - if ($dnsenabled != '0') { - $dnsenabled = '1'; - } - - if ($phpenabled != $result['phpenabled'] || $perlenabled != $result['perlenabled']) { - inserttask('1'); - } - - // activate/deactivate customer services - if ($deactivated != $result['deactivated']) { - - $yesno = (($deactivated) ? 'N' : 'Y'); - $pop3 = (($deactivated) ? '0' : (int) $result['pop3']); - $imap = (($deactivated) ? '0' : (int) $result['imap']); - - $upd_stmt = Database::prepare(" + $upd_stmt = Database::prepare(" UPDATE `" . TABLE_MAIL_USERS . "` SET `postfix`= :yesno, `pop3` = :pop3, `imap` = :imap WHERE `customerid` = :customerid "); - Database::pexecute($upd_stmt, array( - 'yesno' => $yesno, - 'pop3' => $pop3, - 'imap' => $imap, - 'customerid' => $id - )); - - $upd_stmt = Database::prepare(" + Database::pexecute($upd_stmt, array( + 'yesno' => $yesno, + 'pop3' => $pop3, + 'imap' => $imap, + 'customerid' => $id + )); + + $upd_stmt = Database::prepare(" UPDATE `" . TABLE_FTP_USERS . "` SET `login_enabled` = :yesno WHERE `customerid` = :customerid "); - Database::pexecute($upd_stmt, array( - 'yesno' => $yesno, - 'customerid' => $id - )); - - $upd_stmt = Database::prepare(" + Database::pexecute($upd_stmt, array( + 'yesno' => $yesno, + 'customerid' => $id + )); + + $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `deactivated`= :deactivated WHERE `customerid` = :customerid"); - Database::pexecute($upd_stmt, array( - 'deactivated' => $deactivated, - 'customerid' => $id - )); - - // Retrieve customer's databases - $databases_stmt = Database::prepare("SELECT * FROM " . TABLE_PANEL_DATABASES . " WHERE customerid = :customerid ORDER BY `dbserver`"); - Database::pexecute($databases_stmt, array( - 'customerid' => $id - )); - - Database::needRoot(true); - $last_dbserver = 0; - - $dbm = new DbManager($this->logger()); - - // For each of them - while ($row_database = $databases_stmt->fetch(PDO::FETCH_ASSOC)) { - - if ($last_dbserver != $row_database['dbserver']) { - $dbm->getManager()->flushPrivileges(); - Database::needRoot(true, $row_database['dbserver']); - $last_dbserver = $row_database['dbserver']; - } - - foreach (array_unique(explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) { - $mysql_access_host = trim($mysql_access_host); - - // Prevent access, if deactivated - if ($deactivated) { - // failsafe if user has been deleted manually (requires MySQL 4.1.2+) - $dbm->getManager()->disableUser($row_database['databasename'], $mysql_access_host); - } else { - // Otherwise grant access - $dbm->getManager()->enableUser($row_database['databasename'], $mysql_access_host); - } + Database::pexecute($upd_stmt, array( + 'deactivated' => $deactivated, + 'customerid' => $id + )); + + // Retrieve customer's databases + $databases_stmt = Database::prepare("SELECT * FROM " . TABLE_PANEL_DATABASES . " WHERE customerid = :customerid ORDER BY `dbserver`"); + Database::pexecute($databases_stmt, array( + 'customerid' => $id + )); + + Database::needRoot(true); + $last_dbserver = 0; + + $dbm = new DbManager($this->logger()); + + // For each of them + while ($row_database = $databases_stmt->fetch(PDO::FETCH_ASSOC)) { + + if ($last_dbserver != $row_database['dbserver']) { + $dbm->getManager()->flushPrivileges(); + Database::needRoot(true, $row_database['dbserver']); + $last_dbserver = $row_database['dbserver']; + } + + foreach (array_unique(explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) { + $mysql_access_host = trim($mysql_access_host); + + // Prevent access, if deactivated + if ($deactivated) { + // failsafe if user has been deleted manually (requires MySQL 4.1.2+) + $dbm->getManager()->disableUser($row_database['databasename'], $mysql_access_host); + } else { + // Otherwise grant access + $dbm->getManager()->enableUser($row_database['databasename'], $mysql_access_host); } } - - // At last flush the new privileges - $dbm->getManager()->flushPrivileges(); - Database::needRoot(false); - - $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] deactivated user '" . $result['loginname'] . "'"); - inserttask('1'); } + + // At last flush the new privileges + $dbm->getManager()->flushPrivileges(); + Database::needRoot(false); - // Disable or enable POP3 Login for customers Mail Accounts - if ($email_pop3 != $result['pop3']) { - $upd_stmt = Database::prepare("UPDATE `" . TABLE_MAIL_USERS . "` SET `pop3` = :pop3 WHERE `customerid` = :customerid"); - Database::pexecute($upd_stmt, array( - 'pop3' => $email_pop3, - 'customerid' => $id - )); - } - - // Disable or enable IMAP Login for customers Mail Accounts - if ($email_imap != $result['imap']) { - $upd_stmt = Database::prepare("UPDATE `" . TABLE_MAIL_USERS . "` SET `imap` = :imap WHERE `customerid` = :customerid"); - Database::pexecute($upd_stmt, array( - 'imap' => $email_imap, - 'customerid' => $id - )); - } - - $upd_data = array( - 'customerid' => $id, - 'passwd' => $password, - 'name' => $name, - 'firstname' => $firstname, - 'gender' => $gender, - 'company' => $company, - 'street' => $street, - 'zipcode' => $zipcode, - 'city' => $city, - 'phone' => $phone, - 'fax' => $fax, - 'email' => $email, - 'customerno' => $customernumber, - 'lang' => $def_language, - 'diskspace' => $diskspace, - 'traffic' => $traffic, - 'subdomains' => $subdomains, - 'emails' => $emails, - 'email_accounts' => $email_accounts, - 'email_forwarders' => $email_forwarders, - 'email_quota' => $email_quota, - 'ftps' => $ftps, - 'tickets' => $tickets, - 'mysqls' => $mysqls, - 'deactivated' => $deactivated, - 'phpenabled' => $phpenabled, - 'allowed_phpconfigs' => empty($allowed_phpconfigs) ? "" : json_encode($allowed_phpconfigs), - 'imap' => $email_imap, + $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] deactivated user '" . $result['loginname'] . "'"); + inserttask('1'); + } + + // Disable or enable POP3 Login for customers Mail Accounts + if ($email_pop3 != $result['pop3']) { + $upd_stmt = Database::prepare("UPDATE `" . TABLE_MAIL_USERS . "` SET `pop3` = :pop3 WHERE `customerid` = :customerid"); + Database::pexecute($upd_stmt, array( 'pop3' => $email_pop3, - 'perlenabled' => $perlenabled, - 'dnsenabled' => $dnsenabled, - 'custom_notes' => $custom_notes, - 'custom_notes_show' => $custom_notes_show, - 'theme' => $theme - ); - $upd_stmt = Database::prepare(" + 'customerid' => $id + )); + } + + // Disable or enable IMAP Login for customers Mail Accounts + if ($email_imap != $result['imap']) { + $upd_stmt = Database::prepare("UPDATE `" . TABLE_MAIL_USERS . "` SET `imap` = :imap WHERE `customerid` = :customerid"); + Database::pexecute($upd_stmt, array( + 'imap' => $email_imap, + 'customerid' => $id + )); + } + + $upd_data = array( + 'customerid' => $id, + 'passwd' => $password, + 'name' => $name, + 'firstname' => $firstname, + 'gender' => $gender, + 'company' => $company, + 'street' => $street, + 'zipcode' => $zipcode, + 'city' => $city, + 'phone' => $phone, + 'fax' => $fax, + 'email' => $email, + 'customerno' => $customernumber, + 'lang' => $def_language, + 'diskspace' => $diskspace, + 'traffic' => $traffic, + 'subdomains' => $subdomains, + 'emails' => $emails, + 'email_accounts' => $email_accounts, + 'email_forwarders' => $email_forwarders, + 'email_quota' => $email_quota, + 'ftps' => $ftps, + 'tickets' => $tickets, + 'mysqls' => $mysqls, + 'deactivated' => $deactivated, + 'phpenabled' => $phpenabled, + 'allowed_phpconfigs' => empty($allowed_phpconfigs) ? "" : json_encode($allowed_phpconfigs), + 'imap' => $email_imap, + 'pop3' => $email_pop3, + 'perlenabled' => $perlenabled, + 'dnsenabled' => $dnsenabled, + 'custom_notes' => $custom_notes, + 'custom_notes_show' => $custom_notes_show, + 'theme' => $theme + ); + $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `name` = :name, `firstname` = :firstname, @@ -1090,136 +1080,134 @@ class Customers extends ApiCommand implements ResourceEntity `theme` = :theme WHERE `customerid` = :customerid "); - Database::pexecute($upd_stmt, $upd_data); + Database::pexecute($upd_stmt, $upd_data); + + if ($this->isAdmin()) { + // Using filesystem - quota, insert a task which cleans the filesystem - quota + inserttask('10'); - if ($this->isAdmin()) { - // Using filesystem - quota, insert a task which cleans the filesystem - quota - inserttask('10'); - - $admin_update_query = "UPDATE `" . TABLE_PANEL_ADMINS . "` SET `customers_used` = `customers_used` "; - - if ($mysqls != '-1' || $result['mysqls'] != '-1') { - $admin_update_query .= ", `mysqls_used` = `mysqls_used` "; - - if ($mysqls != '-1') { - $admin_update_query .= " + 0" . (int) $mysqls . " "; - } - if ($result['mysqls'] != '-1') { - $admin_update_query .= " - 0" . (int) $result['mysqls'] . " "; - } - } - - if ($emails != '-1' || $result['emails'] != '-1') { - $admin_update_query .= ", `emails_used` = `emails_used` "; - - if ($emails != '-1') { - $admin_update_query .= " + 0" . (int) $emails . " "; - } - if ($result['emails'] != '-1') { - $admin_update_query .= " - 0" . (int) $result['emails'] . " "; - } - } - - if ($email_accounts != '-1' || $result['email_accounts'] != '-1') { - $admin_update_query .= ", `email_accounts_used` = `email_accounts_used` "; - - if ($email_accounts != '-1') { - $admin_update_query .= " + 0" . (int) $email_accounts . " "; - } - if ($result['email_accounts'] != '-1') { - $admin_update_query .= " - 0" . (int) $result['email_accounts'] . " "; - } - } - - if ($email_forwarders != '-1' || $result['email_forwarders'] != '-1') { - $admin_update_query .= ", `email_forwarders_used` = `email_forwarders_used` "; - - if ($email_forwarders != '-1') { - $admin_update_query .= " + 0" . (int) $email_forwarders . " "; - } - if ($result['email_forwarders'] != '-1') { - $admin_update_query .= " - 0" . (int) $result['email_forwarders'] . " "; - } - } - - if ($email_quota != '-1' || $result['email_quota'] != '-1') { - $admin_update_query .= ", `email_quota_used` = `email_quota_used` "; - - if ($email_quota != '-1') { - $admin_update_query .= " + 0" . (int) $email_quota . " "; - } - if ($result['email_quota'] != '-1') { - $admin_update_query .= " - 0" . (int) $result['email_quota'] . " "; - } - } - - if ($subdomains != '-1' || $result['subdomains'] != '-1') { - $admin_update_query .= ", `subdomains_used` = `subdomains_used` "; - - if ($subdomains != '-1') { - $admin_update_query .= " + 0" . (int) $subdomains . " "; - } - if ($result['subdomains'] != '-1') { - $admin_update_query .= " - 0" . (int) $result['subdomains'] . " "; - } - } - - if ($ftps != '-1' || $result['ftps'] != '-1') { - $admin_update_query .= ", `ftps_used` = `ftps_used` "; - - if ($ftps != '-1') { - $admin_update_query .= " + 0" . (int) $ftps . " "; - } - if ($result['ftps'] != '-1') { - $admin_update_query .= " - 0" . (int) $result['ftps'] . " "; - } - } - - if ($tickets != '-1' || $result['tickets'] != '-1') { - $admin_update_query .= ", `tickets_used` = `tickets_used` "; - - if ($tickets != '-1') { - $admin_update_query .= " + 0" . (int) $tickets . " "; - } - if ($result['tickets'] != '-1') { - $admin_update_query .= " - 0" . (int) $result['tickets'] . " "; - } - } - - if (($diskspace / 1024) != '-1' || ($result['diskspace'] / 1024) != '-1') { - $admin_update_query .= ", `diskspace_used` = `diskspace_used` "; - - if (($diskspace / 1024) != '-1') { - $admin_update_query .= " + 0" . (int) $diskspace . " "; - } - if (($result['diskspace'] / 1024) != '-1') { - $admin_update_query .= " - 0" . (int) $result['diskspace'] . " "; - } - } - - $admin_update_query .= " WHERE `adminid` = '" . (int) $result['adminid'] . "'"; - Database::query($admin_update_query); - } + $admin_update_query = "UPDATE `" . TABLE_PANEL_ADMINS . "` SET `customers_used` = `customers_used` "; - $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] edited user '" . $result['loginname'] . "'"); - - /* - * move customer to another admin/reseller; #1166 - */ - if ($move_to_admin > 0 && $move_to_admin != $result['adminid']) { - $json_result = Customers::getLocal($this->getUserData(), array( - 'id' => $result['customerid'], - 'adminid' => $move_to_admin - ))->move(); - $move_result = json_decode($json_result, true)['data']; - if ($move_result != true) { - standard_error('moveofcustomerfailed', $move_result, true); + if ($mysqls != '-1' || $result['mysqls'] != '-1') { + $admin_update_query .= ", `mysqls_used` = `mysqls_used` "; + + if ($mysqls != '-1') { + $admin_update_query .= " + 0" . (int) $mysqls . " "; + } + if ($result['mysqls'] != '-1') { + $admin_update_query .= " - 0" . (int) $result['mysqls'] . " "; } } - return $this->response(200, "successfull", $upd_data); + if ($emails != '-1' || $result['emails'] != '-1') { + $admin_update_query .= ", `emails_used` = `emails_used` "; + + if ($emails != '-1') { + $admin_update_query .= " + 0" . (int) $emails . " "; + } + if ($result['emails'] != '-1') { + $admin_update_query .= " - 0" . (int) $result['emails'] . " "; + } + } + + if ($email_accounts != '-1' || $result['email_accounts'] != '-1') { + $admin_update_query .= ", `email_accounts_used` = `email_accounts_used` "; + + if ($email_accounts != '-1') { + $admin_update_query .= " + 0" . (int) $email_accounts . " "; + } + if ($result['email_accounts'] != '-1') { + $admin_update_query .= " - 0" . (int) $result['email_accounts'] . " "; + } + } + + if ($email_forwarders != '-1' || $result['email_forwarders'] != '-1') { + $admin_update_query .= ", `email_forwarders_used` = `email_forwarders_used` "; + + if ($email_forwarders != '-1') { + $admin_update_query .= " + 0" . (int) $email_forwarders . " "; + } + if ($result['email_forwarders'] != '-1') { + $admin_update_query .= " - 0" . (int) $result['email_forwarders'] . " "; + } + } + + if ($email_quota != '-1' || $result['email_quota'] != '-1') { + $admin_update_query .= ", `email_quota_used` = `email_quota_used` "; + + if ($email_quota != '-1') { + $admin_update_query .= " + 0" . (int) $email_quota . " "; + } + if ($result['email_quota'] != '-1') { + $admin_update_query .= " - 0" . (int) $result['email_quota'] . " "; + } + } + + if ($subdomains != '-1' || $result['subdomains'] != '-1') { + $admin_update_query .= ", `subdomains_used` = `subdomains_used` "; + + if ($subdomains != '-1') { + $admin_update_query .= " + 0" . (int) $subdomains . " "; + } + if ($result['subdomains'] != '-1') { + $admin_update_query .= " - 0" . (int) $result['subdomains'] . " "; + } + } + + if ($ftps != '-1' || $result['ftps'] != '-1') { + $admin_update_query .= ", `ftps_used` = `ftps_used` "; + + if ($ftps != '-1') { + $admin_update_query .= " + 0" . (int) $ftps . " "; + } + if ($result['ftps'] != '-1') { + $admin_update_query .= " - 0" . (int) $result['ftps'] . " "; + } + } + + if ($tickets != '-1' || $result['tickets'] != '-1') { + $admin_update_query .= ", `tickets_used` = `tickets_used` "; + + if ($tickets != '-1') { + $admin_update_query .= " + 0" . (int) $tickets . " "; + } + if ($result['tickets'] != '-1') { + $admin_update_query .= " - 0" . (int) $result['tickets'] . " "; + } + } + + if (($diskspace / 1024) != '-1' || ($result['diskspace'] / 1024) != '-1') { + $admin_update_query .= ", `diskspace_used` = `diskspace_used` "; + + if (($diskspace / 1024) != '-1') { + $admin_update_query .= " + 0" . (int) $diskspace . " "; + } + if (($result['diskspace'] / 1024) != '-1') { + $admin_update_query .= " - 0" . (int) $result['diskspace'] . " "; + } + } + + $admin_update_query .= " WHERE `adminid` = '" . (int) $result['adminid'] . "'"; + Database::query($admin_update_query); } - throw new Exception("Not allowed to execute given command.", 403); + + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] edited user '" . $result['loginname'] . "'"); + + /* + * move customer to another admin/reseller; #1166 + */ + if ($move_to_admin > 0 && $move_to_admin != $result['adminid']) { + $json_result = Customers::getLocal($this->getUserData(), array( + 'id' => $result['customerid'], + 'adminid' => $move_to_admin + ))->move(); + $move_result = json_decode($json_result, true)['data']; + if ($move_result != true) { + standard_error('moveofcustomerfailed', $move_result, true); + } + } + + return $this->response(200, "successfull", $upd_data); } /** @@ -1241,13 +1229,9 @@ class Customers extends ApiCommand implements ResourceEntity if ($this->isAdmin()) { $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); - $loginname = $this->getParam('loginname', $ln_optional, ''); + $loginname = trim($this->getParam('loginname', $ln_optional, '')); $delete_userfiles = $this->getParam('delete_userfiles', true, 0); - if ($id <= 0 && empty($loginname)) { - throw new Exception("Either 'id' or 'loginname' parameter must be given", 406); - } - $json_result = Customers::getLocal($this->getUserData(), array( 'id' => $id, 'loginname' => $loginname @@ -1487,11 +1471,7 @@ class Customers extends ApiCommand implements ResourceEntity if ($this->isAdmin()) { $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); - $loginname = $this->getParam('loginname', $ln_optional, ''); - - if ($id <= 0 && empty($loginname)) { - throw new Exception("Either 'id' or 'loginname' parameter must be given", 406); - } + $loginname = trim($this->getParam('loginname', $ln_optional, '')); $json_result = Customers::getLocal($this->getUserData(), array( 'id' => $id, @@ -1539,18 +1519,18 @@ class Customers extends ApiCommand implements ResourceEntity 'id' => $id ))->get(); $c_result = json_decode($json_result, true)['data']; - + // check if target-admin is the current admin if ($adminid == $c_result['adminid']) { throw new Exception("Cannot move customer to the same admin/reseller as he currently is assigned to", 406); } - + // get target admin $json_result = Customers::getLocal($this->getUserData(), array( 'id' => $adminid ))->get(); $a_result = json_decode($json_result, true)['data']; - + // Update customer entry $updCustomer_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `adminid` = :adminid WHERE `customerid` = :cid @@ -1580,7 +1560,7 @@ class Customers extends ApiCommand implements ResourceEntity // now, recalculate the resource-usage for the old and the new admin updateCounters(false); - + $log->logAction(ADM_ACTION, LOG_INFO, "[API] moved user '" . $c_result['loginname'] . "' from admin/reseller '" . $c_result['adminname'] . " to admin/reseller '" . $a_result['loginname'] . "'"); return $this->response(200, "successfull", true); } diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index ad879bb6..31ca5e63 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -73,14 +73,9 @@ class Domains extends ApiCommand implements ResourceEntity if ($this->isAdmin()) { $id = $this->getParam('id', true, 0); $dn_optional = ($id <= 0 ? false : true); - $domainname = $this->getParam('domainname', $dn_optional, ''); + $domainname = trim($this->getParam('domainname', $dn_optional, '')); $no_std_subdomain = $this->getParam('no_std_subdomain', true, false); - $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] get domain #" . $id); - - if ($id <= 0 && empty($domainname)) { - throw new Exception("Either 'id' or 'domainname' parameter must be given", 406); - } - + // convert possible idn domain to punycode if (substr($domainname, 0, 4) != 'xn--') { $idna_convert = new idna_convert_wrapper(); @@ -101,6 +96,7 @@ class Domains extends ApiCommand implements ResourceEntity } $result = Database::pexecute_first($result_stmt, $params, true, true); if ($result) { + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] get domain '" . $result['domain'] . "'"); return $this->response(200, "successfull", $result); } $key = ($id > 0 ? "id #" . $id : "domainname '" . $domainname . "'"); @@ -778,11 +774,7 @@ class Domains extends ApiCommand implements ResourceEntity // parameters $id = $this->getParam('id', true, 0); $dn_optional = ($id <= 0 ? false : true); - $domainname = $this->getParam('domainname', $dn_optional, ''); - - if ($id <= 0 && empty($domainname)) { - throw new Exception("Either 'id' or 'domainname' parameter must be given", 406); - } + $domainname = trim($this->getParam('domainname', $dn_optional, '')); // get requested domain $json_result = Domains::getLocal($this->getUserData(), array( @@ -1595,14 +1587,10 @@ class Domains extends ApiCommand implements ResourceEntity if ($this->isAdmin()) { $id = $this->getParam('id', true, 0); $dn_optional = ($id <= 0 ? false : true); - $domainname = $this->getParam('domainname', $dn_optional, ''); + $domainname = trim($this->getParam('domainname', $dn_optional, '')); $is_stdsubdomain = $this->getParam('is_stdsubdomain', true, 0); $remove_subbutmain_domains = $this->getParam('delete_mainsubdomains', true, 0); - - if ($id <= 0 && empty($domainname)) { - throw new Exception("Either 'id' or 'domainname' parameter must be given", 406); - } - + $json_result = Domains::getLocal($this->getUserData(), array( 'id' => $id, 'domainname' => $domainname diff --git a/lib/classes/api/commands/class.Ftps.php b/lib/classes/api/commands/class.Ftps.php index 352302e3..e0150ab4 100644 --- a/lib/classes/api/commands/class.Ftps.php +++ b/lib/classes/api/commands/class.Ftps.php @@ -37,12 +37,8 @@ class Ftps extends ApiCommand implements ResourceEntity { $id = $this->getParam('id', true, 0); $un_optional = ($id <= 0 ? false : true); - $username = $this->getParam('username', $un_optional, ''); - - if ($id <= 0 && empty($username)) { - throw new Exception("Either 'id' or 'username' parameter must be given", 406); - } - + $username = trim($this->getParam('username', $un_optional, '')); + $params = array(); if ($this->isAdmin()) { if ($this->getUserDetail('customers_see_all') == false) { diff --git a/lib/classes/api/commands/class.Mysqls.php b/lib/classes/api/commands/class.Mysqls.php index 7b72a65c..5fa12a1e 100644 --- a/lib/classes/api/commands/class.Mysqls.php +++ b/lib/classes/api/commands/class.Mysqls.php @@ -238,13 +238,9 @@ class Mysqls extends ApiCommand implements ResourceEntity { $id = $this->getParam('id', true, 0); $dn_optional = ($id <= 0 ? false : true); - $dbname = $this->getParam('dbname', $dn_optional, ''); + $dbname = trim($this->getParam('dbname', $dn_optional, '')); $dbserver = $this->getParam('mysql_server', true, - 1); - - if ($id <= 0 && empty($dbname)) { - throw new Exception("Either 'id' or 'dbname' parameter must be given", 406); - } - + if ($this->isAdmin()) { if ($this->getUserDetail('customers_see_all') != 1) { // if it's a reseller or an admin who cannot see all customers, we need to check @@ -341,13 +337,9 @@ class Mysqls extends ApiCommand implements ResourceEntity { $id = $this->getParam('id', true, 0); $dn_optional = ($id <= 0 ? false : true); - $dbname = $this->getParam('dbname', $dn_optional, ''); + $dbname = trim($this->getParam('dbname', $dn_optional, '')); $dbserver = $this->getParam('mysql_server', true, - 1); - - if ($id <= 0 && empty($dbname)) { - throw new Exception("Either 'id' or 'dbname' parameter must be given", 406); - } - + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'mysql')) { throw new Exception("You cannot access this resource", 405); } @@ -549,13 +541,9 @@ class Mysqls extends ApiCommand implements ResourceEntity { $id = $this->getParam('id', true, 0); $dn_optional = ($id <= 0 ? false : true); - $dbname = $this->getParam('dbname', $dn_optional, ''); + $dbname = trim($this->getParam('dbname', $dn_optional, '')); $dbserver = $this->getParam('mysql_server', true, - 1); - - if ($id <= 0 && empty($dbname)) { - throw new Exception("Either 'id' or 'dbname' parameter must be given", 406); - } - + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'mysql')) { throw new Exception("You cannot access this resource", 405); } diff --git a/lib/functions/output/function.standard_error.php b/lib/functions/output/function.standard_error.php index 9bcb3397..089d2343 100644 --- a/lib/functions/output/function.standard_error.php +++ b/lib/functions/output/function.standard_error.php @@ -61,7 +61,7 @@ function standard_error($errors = '', $replacer = '', $throw_exception = false) } if ($throw_exception) { - throw new Exception($error, 400); + throw new Exception(strip_tags($error), 400); } eval("echo \"" . getTemplate('misc/error', '1') . "\";"); exit; From 0958d07f2343d8fc98e52b5756996a9dc6f70af4 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sun, 25 Feb 2018 16:49:24 +0100 Subject: [PATCH 067/746] fixes in Admins and Customers ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/abstract.ApiCommand.php | 16 ++++++--- lib/classes/api/commands/class.Admins.php | 34 +++++++------------- lib/classes/api/commands/class.Customers.php | 30 ++++++----------- 3 files changed, 33 insertions(+), 47 deletions(-) diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index 82fd38c8..afdba0c5 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -102,7 +102,7 @@ abstract class ApiCommand public function __construct($header = null, $params = null, $userinfo = null) { global $lng, $version, $dbversion, $branding; - + $this->version = $version; $this->dbversion = $dbversion; $this->branding = $branding; @@ -116,13 +116,13 @@ abstract class ApiCommand throw new Exception("Invalid user data", 500); } $this->logger = FroxlorLogger::getInstanceOf($this->user_data); - + // check whether the user is deactivated if ($this->getUserDetail('deactivated') == 1) { $this->logger()->logAction(LOG_ERROR, LOG_INFO, "[API] User '" . $this->getUserDetail('loginnname') . "' tried to use API but is deactivated"); throw new Exception("Account suspended", 406); } - + $this->initLang(); $this->lng = $lng; $this->initMail(); @@ -166,8 +166,14 @@ abstract class ApiCommand // now include the selected language if its not english if ($language != 'English') { - foreach ($langs[$language] as $key => $value) { - include_once makeSecurePath(FROXLOR_INSTALL_DIR . '/' . $value['file']); + if (isset($langs[$language])) { + foreach ($langs[$language] as $key => $value) { + include_once makeSecurePath(FROXLOR_INSTALL_DIR . '/' . $value['file']); + } + } else { + if ($this->debug) { + $this->logger()->logAction(LOG_ERROR, LOG_DEBUG, "[API] unable to include user-language '" . $language . "'. Not found in database.", 404); + } } } diff --git a/lib/classes/api/commands/class.Admins.php b/lib/classes/api/commands/class.Admins.php index 6c0de7ae..36c1005b 100644 --- a/lib/classes/api/commands/class.Admins.php +++ b/lib/classes/api/commands/class.Admins.php @@ -99,7 +99,7 @@ class Admins extends ApiCommand implements ResourceEntity $email = $this->getParam('email'); // parameters - $def_language = $this->getParam('def_language', true, ''); + $def_language = $this->getParam('def_language', true, Settings::Get('panel.standardlanguage')); $custom_notes = $this->getParam('custom_notes', true, ''); $custom_notes_show = $this->getParam('custom_notes_show', true, 0); $password = $this->getParam('admin_password', true, ''); @@ -152,28 +152,18 @@ class Admins extends ApiCommand implements ResourceEntity $traffic = $traffic * 1024 * 1024; // Check if the account already exists - try { - $dup_check_result = Customers::getLocal($this->getUserData(), array( - 'loginname' => $loginname - ))->get(); - $loginname_check = json_decode($dup_check_result, true)['data']; - } catch (Exception $e) { - $loginname_check = array( - 'loginname' => '' - ); - } + // do not check via api as we skip any permission checks for this task + $loginname_check_stmt = Database::prepare(" + SELECT `loginname` FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `loginname` = :login + "); + $loginname_check = Database::pexecute_first($loginname_check_stmt, array('login' => $loginname), true, true); // Check if an admin with the loginname already exists - try { - $dup_check_result = Admins::getLocal($this->getUserData(), array( - 'loginname' => $loginname - ))->get(); - $loginname_check_admin = json_decode($dup_check_result, true)['data']; - } catch (Exception $e) { - $loginname_check_admin = array( - 'loginname' => '' - ); - } + // do not check via api as we skip any permission checks for this task + $loginname_check_admin_stmt = Database::prepare(" + SELECT `loginname` FROM `" . TABLE_PANEL_ADMINS . "` WHERE `loginname` = :login + "); + $loginname_check_admin = Database::pexecute_first($loginname_check_admin_stmt, array('login' => $loginname), true, true); if ($loginname == '') { standard_error(array( @@ -290,7 +280,7 @@ class Admins extends ApiCommand implements ResourceEntity $adminid = Database::lastInsertId(); $ins_data['adminid'] = $adminid; $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] added admin '" . $loginname . "'"); - return $this->response(200, "successfull", $admin_ins_data); + return $this->response(200, "successfull", $ins_data); } } throw new Exception("Not allowed to execute given command.", 403); diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index e827d32a..2b978684 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -243,28 +243,18 @@ class Customers extends ApiCommand implements ResourceEntity } // Check if the account already exists - try { - $dup_check_result = Customers::getLocal($this->getUserData(), array( - 'loginname' => $loginname - ))->get(); - $loginname_check = json_decode($dup_check_result, true)['data']; - } catch (Exception $e) { - $loginname_check = array( - 'loginname' => '' - ); - } + // do not check via api as we skip any permission checks for this task + $loginname_check_stmt = Database::prepare(" + SELECT `loginname` FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `loginname` = :login + "); + $loginname_check = Database::pexecute_first($loginname_check_stmt, array('login' => $loginname), true, true); // Check if an admin with the loginname already exists - try { - $dup_check_result = Admins::getLocal($this->getUserData(), array( - 'loginname' => $loginname - ))->get(); - $loginname_check_admin = json_decode($dup_check_result, true)['data']; - } catch (Exception $e) { - $loginname_check_admin = array( - 'loginname' => '' - ); - } + // do not check via api as we skip any permission checks for this task + $loginname_check_admin_stmt = Database::prepare(" + SELECT `loginname` FROM `" . TABLE_PANEL_ADMINS . "` WHERE `loginname` = :login + "); + $loginname_check_admin = Database::pexecute_first($loginname_check_admin_stmt, array('login' => $loginname), true, true); if (strtolower($loginname_check['loginname']) == strtolower($loginname) || strtolower($loginname_check_admin['loginname']) == strtolower($loginname)) { standard_error('loginnameexists', $loginname, true); From f32a1921c5ea513d4eae39ef59e1142f7f073845 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sun, 25 Feb 2018 17:03:49 +0100 Subject: [PATCH 068/746] re-read admin/customer when adding/updating so we return the fields from the table, not the placeholders of the prepared-statement Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Admins.php | 19 +++++++++++++++--- lib/classes/api/commands/class.Customers.php | 21 ++++++++++++++------ 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/lib/classes/api/commands/class.Admins.php b/lib/classes/api/commands/class.Admins.php index 36c1005b..382ff0c0 100644 --- a/lib/classes/api/commands/class.Admins.php +++ b/lib/classes/api/commands/class.Admins.php @@ -280,7 +280,13 @@ class Admins extends ApiCommand implements ResourceEntity $adminid = Database::lastInsertId(); $ins_data['adminid'] = $adminid; $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] added admin '" . $loginname . "'"); - return $this->response(200, "successfull", $ins_data); + + // get all admin-data for return-array + $json_result = Admins::getLocal($this->getUserData(), array( + 'id' => $adminid + ))->get(); + $result = json_decode($json_result, true)['data']; + return $this->response(200, "successfull", $result); } } throw new Exception("Not allowed to execute given command.", 403); @@ -540,9 +546,14 @@ class Admins extends ApiCommand implements ResourceEntity WHERE `adminid` = :adminid "); Database::pexecute($upd_stmt, $upd_data, true, true); - $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] edited admin '" . $result['loginname'] . "'"); - return $this->response(200, "successfull", $upd_data); + + // get all admin-data for return-array + $json_result = Admins::getLocal($this->getUserData(), array( + 'id' => $adminid + ))->get(); + $result = json_decode($json_result, true)['data']; + return $this->response(200, "successfull", $result); } } throw new Exception("Not allowed to execute given command.", 403); @@ -647,6 +658,8 @@ class Admins extends ApiCommand implements ResourceEntity Database::pexecute($result_stmt, array( 'id' => $id ), true, true); + // set the new value for result-array + $result['loginfail_count'] = 0; $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] unlocked admin '" . $result['loginname'] . "'"); return $this->response(200, "successfull", $result); diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index 2b978684..e6b49ec1 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -428,9 +428,8 @@ class Customers extends ApiCommand implements ResourceEntity // update last account number Settings::Set('system.lastaccountnumber', $accountnumber, true); } - + $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] added customer '" . $loginname . "'"); - $customer_ins_data = $ins_data; unset($ins_data); // insert task to create homedir etc. @@ -644,9 +643,13 @@ class Customers extends ApiCommand implements ResourceEntity $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] automatically sent password to user '" . $loginname . "'"); } } - $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] added customer '" . $loginname . "'"); - return $this->response(200, "successfull", $customer_ins_data); + + $json_result = Customers::getLocal($this->getUserData(), array( + 'loginname' => $loginname + ))->get(); + $result = json_decode($json_result, true)['data']; + return $this->response(200, "successfull", $result); } throw new Exception("No more resources available", 406); } @@ -1196,8 +1199,12 @@ class Customers extends ApiCommand implements ResourceEntity standard_error('moveofcustomerfailed', $move_result, true); } } - - return $this->response(200, "successfull", $upd_data); + + $json_result = Customers::getLocal($this->getUserData(), array( + 'loginname' => $result['customerid'] + ))->get(); + $result = json_decode($json_result, true)['data']; + return $this->response(200, "successfull", $result); } /** @@ -1478,6 +1485,8 @@ class Customers extends ApiCommand implements ResourceEntity Database::pexecute($result_stmt, array( 'id' => $id ), true, true); + // set the new value for result-array + $result['loginfail_count'] = 0; $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] unlocked customer '" . $result['loginname'] . "'"); return $this->response(200, "successfull", $result); From e66dde2e646f11630d5c5a806ec45ea8bbae2c9c Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sun, 25 Feb 2018 17:08:30 +0100 Subject: [PATCH 069/746] id <> loginname, grrr Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Customers.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index e6b49ec1..8813adc2 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -1201,7 +1201,7 @@ class Customers extends ApiCommand implements ResourceEntity } $json_result = Customers::getLocal($this->getUserData(), array( - 'loginname' => $result['customerid'] + 'id' => $result['customerid'] ))->get(); $result = json_decode($json_result, true)['data']; return $this->response(200, "successfull", $result); From 2b366c8f23317ccab87f873af999147e7d05dd5e Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sun, 25 Feb 2018 18:22:15 +0100 Subject: [PATCH 070/746] add field for fullchain to be stored in ssl-certificates-table; create fullchain file if given (it's not used by froxlor); do not generate/renew certificates for disabled customers domains Signed-off-by: Michael Kaufmann (d00p) --- install/froxlor.sql | 3 ++- install/updates/froxlor/0.9/update_0.9.inc.php | 9 +++++++++ lib/classes/webserver/class.DomainSSL.php | 4 ++++ lib/version.inc.php | 2 +- scripts/jobs/cron_letsencrypt.php | 3 +++ scripts/jobs/cron_letsencrypt_v2.php | 3 +++ 6 files changed, 22 insertions(+), 2 deletions(-) diff --git a/install/froxlor.sql b/install/froxlor.sql index 870e30d4..0876cffd 100644 --- a/install/froxlor.sql +++ b/install/froxlor.sql @@ -688,7 +688,7 @@ opcache.interned_strings_buffer'), ('panel', 'password_special_char', '!?<>§$%+#=@'), ('panel', 'customer_hide_options', ''), ('panel', 'version', '0.9.39.5'), - ('panel', 'db_version', '201802130'); + ('panel', 'db_version', '201802250'); DROP TABLE IF EXISTS `panel_tasks`; @@ -1004,6 +1004,7 @@ CREATE TABLE IF NOT EXISTS `domain_ssl_settings` ( `ssl_ca_file` mediumtext, `ssl_cert_chainfile` mediumtext, `ssl_csr_file` mediumtext, + `ssl_fullchain_file` mediumtext, `expirationdate` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM CHARSET=utf8 COLLATE=utf8_general_ci; diff --git a/install/updates/froxlor/0.9/update_0.9.inc.php b/install/updates/froxlor/0.9/update_0.9.inc.php index 0abab0fe..0e9c09e6 100644 --- a/install/updates/froxlor/0.9/update_0.9.inc.php +++ b/install/updates/froxlor/0.9/update_0.9.inc.php @@ -3939,3 +3939,12 @@ if (isFroxlorVersion('0.9.39.4')) { showUpdateStep("Updating from 0.9.39.4 to 0.9.39.5", false); updateToVersion('0.9.39.5'); } + +if (isDatabaseVersion('201802130')) { + + showUpdateStep("Adding fullchain field to ssl certificates"); + Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` ADD `ssl_fullchain_file` mediumtext AFTER `ssl_csr_file`;"); + lastStepStatus(0); + + updateToDbVersion('201802250'); +} diff --git a/lib/classes/webserver/class.DomainSSL.php b/lib/classes/webserver/class.DomainSSL.php index 82ae9fcd..73399bfa 100644 --- a/lib/classes/webserver/class.DomainSSL.php +++ b/lib/classes/webserver/class.DomainSSL.php @@ -89,6 +89,10 @@ class DomainSSL { $ssl_files['ssl_cert_chainfile'] = makeCorrectFile($sslcertpath.'/'.$domain['domain'].'_chain.pem'); } } + // will only be generated to be used externally, froxlor does not need this + if ($dom_certs['ssl_fullchain_file'] != '') { + $ssl_files['ssl_fullchain_file'] = makeCorrectFile($sslcertpath.'/'.$domain['domain'].'_fullchain.pem'); + } // create them on the filesystem foreach ($ssl_files as $type => $filename) { if ($filename != '') { diff --git a/lib/version.inc.php b/lib/version.inc.php index 49935fb7..9ad32b3a 100644 --- a/lib/version.inc.php +++ b/lib/version.inc.php @@ -19,7 +19,7 @@ $version = '0.9.39.5'; // Database version (YYYYMMDDC where C is a daily counter) -$dbversion = '201802130'; +$dbversion = '201802250'; // Distribution branding-tag (used for Debian etc.) $branding = ''; diff --git a/scripts/jobs/cron_letsencrypt.php b/scripts/jobs/cron_letsencrypt.php index b25b9a8d..98ee24ce 100644 --- a/scripts/jobs/cron_letsencrypt.php +++ b/scripts/jobs/cron_letsencrypt.php @@ -60,6 +60,7 @@ $certificates_stmt = Database::query(" dom.`id` = domssl.`domainid` WHERE dom.`customerid` = cust.`customerid` + AND cust.deactivated = 0 AND dom.`letsencrypt` = 1 AND dom.`aliasdomain` IS NULL AND dom.`iswildcarddomain` = 0 @@ -92,6 +93,7 @@ $updcert_stmt = Database::prepare(" `ssl_ca_file` = :ca, `ssl_cert_chainfile` = :chain, `ssl_csr_file` = :csr, + `ssl_fullchain_file` = :fullchain, `expirationdate` = :expirationdate "); @@ -182,6 +184,7 @@ if (Settings::Get('system.le_froxlor_enabled') == '1') { 'ca' => $return['chain'], 'chain' => $return['chain'], 'csr' => $return['csr'], + 'fullchain' => $return['fullchain'], 'expirationdate' => date('Y-m-d H:i:s', $newcert['validTo_time_t']) )); diff --git a/scripts/jobs/cron_letsencrypt_v2.php b/scripts/jobs/cron_letsencrypt_v2.php index 6b1148d3..6e27361b 100644 --- a/scripts/jobs/cron_letsencrypt_v2.php +++ b/scripts/jobs/cron_letsencrypt_v2.php @@ -55,6 +55,7 @@ $certificates_stmt = Database::query(" dom.`id` = domssl.`domainid` WHERE dom.`customerid` = cust.`customerid` + AND cust.deactivated = 0 AND dom.`letsencrypt` = 1 AND dom.`aliasdomain` IS NULL AND dom.`iswildcarddomain` = 0 @@ -88,6 +89,7 @@ $updcert_stmt = Database::prepare(" `ssl_ca_file` = :ca, `ssl_cert_chainfile` = :chain, `ssl_csr_file` = :csr, + `ssl_fullchain_file` = :fullchain, `expirationdate` = :expirationdate "); @@ -178,6 +180,7 @@ if (Settings::Get('system.le_froxlor_enabled') == '1') { 'ca' => $return['chain'], 'chain' => $return['chain'], 'csr' => $return['csr'], + 'fullchain' => $return['fullchain'], 'expirationdate' => date('Y-m-d H:i:s', $newcert['validTo_time_t']) )); From 5c330505ea6a508ebdd7d9ca13d20586dc208efd Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sun, 25 Feb 2018 20:47:36 +0100 Subject: [PATCH 071/746] correct Admins.update and Admins.delete Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Admins.php | 576 +++++++++++----------- 1 file changed, 297 insertions(+), 279 deletions(-) diff --git a/lib/classes/api/commands/class.Admins.php b/lib/classes/api/commands/class.Admins.php index 382ff0c0..d6af8258 100644 --- a/lib/classes/api/commands/class.Admins.php +++ b/lib/classes/api/commands/class.Admins.php @@ -54,7 +54,7 @@ class Admins extends ApiCommand implements ResourceEntity * optional, the admin-id * @param string $loginname * optional, the loginname - * + * * @access admin * @throws Exception * @return array @@ -64,7 +64,7 @@ class Admins extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); $loginname = trim($this->getParam('loginname', $ln_optional, '')); - + if ($this->isAdmin() && ($this->getUserDetail('change_serversettings') == 1 || ($this->getUserDetail('adminid') == $id || $this->getUserDetail('loginname') == $loginname))) { $result_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_ADMINS . "` @@ -93,11 +93,11 @@ class Admins extends ApiCommand implements ResourceEntity public function add() { if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { - + // required parameters $name = $this->getParam('name'); $email = $this->getParam('email'); - + // parameters $def_language = $this->getParam('def_language', true, Settings::Get('panel.standardlanguage')); $custom_notes = $this->getParam('custom_notes', true, ''); @@ -105,7 +105,7 @@ class Admins extends ApiCommand implements ResourceEntity $password = $this->getParam('admin_password', true, ''); $sendpassword = $this->getParam('sendpassword', true, 0); $loginname = $this->getParam('new_loginname', true, ''); - + $diskspace = $this->getUlParam('diskspace', 'diskspace_ul', true, 0); $traffic = $this->getUlParam('traffic', 'traffic_ul', true, 0); $customers = $this->getUlParam('customers', 'customers_ul', true, 0); @@ -118,53 +118,57 @@ class Admins extends ApiCommand implements ResourceEntity $ftps = $this->getUlParam('ftps', 'ftps_ul', true, 0); $tickets = $this->getUlParam('tickets', 'tickets_ul', true, 0); $mysqls = $this->getUlParam('mysqls', 'mysqls_ul', true, 0); - + $customers_see_all = $this->getParam('customers_see_all', true, 0); $domains_see_all = $this->getParam('domains_see_all', true, 0); $tickets_see_all = $this->getParam('tickets_see_all', true, 0); $caneditphpsettings = $this->getParam('caneditphpsettings', true, 0); $change_serversettings = $this->getParam('change_serversettings', true, 0); - $ipaddress = intval_ressource($this->getParam('ipaddress', true, -1)); - + $ipaddress = intval_ressource($this->getParam('ipaddress', true, - 1)); + // validation $name = validate($name, 'name', '', '', array(), true); $idna_convert = new idna_convert_wrapper(); $email = $idna_convert->encode(validate($email, 'email', '', '', array(), true)); $def_language = validate($def_language, 'default language', '', '', array(), true); $custom_notes = validate(str_replace("\r\n", "\n", $custom_notes), 'custom_notes', '/^[^\0]*$/', '', array(), true); - + if (Settings::Get('system.mail_quota_enabled') != '1') { $email_quota = - 1; } - + if (Settings::Get('ticket.enabled') != '1') { $tickets = - 1; } - + $password = validate($password, 'password', '', '', array(), true); // only check if not empty, // cause empty == generate password automatically if ($password != '') { $password = validatePassword($password, true); } - + $diskspace = $diskspace * 1024; $traffic = $traffic * 1024 * 1024; - + // Check if the account already exists // do not check via api as we skip any permission checks for this task $loginname_check_stmt = Database::prepare(" SELECT `loginname` FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `loginname` = :login "); - $loginname_check = Database::pexecute_first($loginname_check_stmt, array('login' => $loginname), true, true); - + $loginname_check = Database::pexecute_first($loginname_check_stmt, array( + 'login' => $loginname + ), true, true); + // Check if an admin with the loginname already exists // do not check via api as we skip any permission checks for this task $loginname_check_admin_stmt = Database::prepare(" SELECT `loginname` FROM `" . TABLE_PANEL_ADMINS . "` WHERE `loginname` = :login "); - $loginname_check_admin = Database::pexecute_first($loginname_check_admin_stmt, array('login' => $loginname), true, true); - + $loginname_check_admin = Database::pexecute_first($loginname_check_admin_stmt, array( + 'login' => $loginname + ), true, true); + if ($loginname == '') { standard_error(array( 'stringisempty', @@ -190,33 +194,33 @@ class Admins extends ApiCommand implements ResourceEntity } elseif (! validateEmail($email)) { standard_error('emailiswrong', $email, true); } else { - + if ($customers_see_all != '1') { $customers_see_all = '0'; } - + if ($domains_see_all != '1') { $domains_see_all = '0'; } - + if ($caneditphpsettings != '1') { $caneditphpsettings = '0'; } - + if ($change_serversettings != '1') { $change_serversettings = '0'; } - + if ($tickets_see_all != '1') { $tickets_see_all = '0'; } - + if ($password == '') { $password = generatePassword(); } - + $_theme = Settings::Get('panel.default_theme'); - + $ins_data = array( 'loginname' => $loginname, 'password' => makeCryptPassword($password), @@ -245,7 +249,7 @@ class Admins extends ApiCommand implements ResourceEntity 'custom_notes' => $custom_notes, 'custom_notes_show' => $custom_notes_show ); - + $ins_stmt = Database::prepare(" INSERT INTO `" . TABLE_PANEL_ADMINS . "` SET `loginname` = :loginname, @@ -276,11 +280,11 @@ class Admins extends ApiCommand implements ResourceEntity `custom_notes_show` = :custom_notes_show "); Database::pexecute($ins_stmt, $ins_data, true, true); - + $adminid = Database::lastInsertId(); $ins_data['adminid'] = $adminid; $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] added admin '" . $loginname . "'"); - + // get all admin-data for return-array $json_result = Admins::getLocal($this->getUserData(), array( 'id' => $adminid @@ -299,261 +303,263 @@ class Admins extends ApiCommand implements ResourceEntity * optional, the admin-id * @param string $loginname * optional, the loginname - * + * * @access admin * @throws Exception * @return array */ public function update() { - if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { - + if ($this->isAdmin()) { + $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); $loginname = trim($this->getParam('loginname', $ln_optional, '')); - + $json_result = Admins::getLocal($this->getUserData(), array( 'id' => $id, 'loginname' => $loginname ))->get(); $result = json_decode($json_result, true)['data']; $id = $result['adminid']; - - // parameters - $name = $this->getParam('name', true, $result['name']); - $idna_convert = new idna_convert_wrapper(); - $email = $this->getParam('email', true, $idna_convert->decode($result['email'])); - $password = $this->getParam('admin_password', true, ''); - $def_language = $this->getParam('def_language', true, $result['def_language']); - $custom_notes = $this->getParam('custom_notes', true, $result['custom_notes']); - $custom_notes_show = $this->getParam('custom_notes_show', true, $result['custom_notes_show']); - $theme = $this->getParam('theme', true, $result['theme']); - - // you cannot edit some of the details of yourself - if ($result['adminid'] == $this->getUserDetail('userid')) { - $deactivated = $result['deactivated']; - $customers = $result['customers']; - $domains = $result['domains']; - $subdomains = $result['subdomains']; - $emails = $result['emails']; - $email_accounts = $result['email_accounts']; - $email_forwarders = $result['email_forwarders']; - $email_quota = $result['email_quota']; - $ftps = $result['ftps']; - $tickets = $result['tickets']; - $mysqls = $result['mysqls']; - $tickets_see_all = $result['tickets_see_all']; - $customers_see_all = $result['customers_see_all']; - $domains_see_all = $result['domains_see_all']; - $caneditphpsettings = $result['caneditphpsettings']; - $change_serversettings = $result['change_serversettings']; - $diskspace = $result['diskspace']; - $traffic = $result['traffic']; - $ipaddress = $result['ip']; - } else { - $deactivated = $this->getParam('deactivated', true, $result['deactivated']); - - $dec_places = Settings::Get('panel.decimal_places'); - $diskspace = $this->getUlParam('diskspace', 'diskspace_ul', true, round($result['diskspace'] / 1024, $dec_places)); - $traffic = $this->getUlParam('traffic', 'traffic_ul', true, round($result['traffic'] / (1024 * 1024), $dec_places)); - $customers = $this->getUlParam('customers', 'customers_ul', true, $result['customers']); - $domains = $this->getUlParam('domains', 'domains_ul', true, $result['domains']); - $subdomains = $this->getUlParam('subdomains', 'subdomains_ul', true, $result['subdomains']); - $emails = $this->getUlParam('emails', 'emails_ul', true, $result['emails']); - $email_accounts = $this->getUlParam('email_accounts', 'email_accounts_ul', true, $result['email_accounts']); - $email_forwarders = $this->getUlParam('email_forwarders', 'email_forwarders_ul', true, $result['email_forwarders']); - $email_quota = $this->getUlParam('email_quota', 'email_quota_ul', true, $result['email_quota']); - $ftps = $this->getUlParam('ftps', 'ftps_ul', true, $result['ftps']); - $tickets = $this->getUlParam('tickets', 'tickets_ul', true, $result['tickets']); - $mysqls = $this->getUlParam('mysqls', 'mysqls_ul', true, $result['mysqls']); - - $customers_see_all = $this->getParam('customers_see_all', true, $result['customers_see_all']); - $domains_see_all = $this->getParam('domains_see_all', true, $result['domains_see_all']); - $tickets_see_all = $this->getParam('tickets_see_all', true, $result['tickets_see_all']); - $caneditphpsettings = $this->getParam('caneditphpsettings', true, $result['caneditphpsettings']); - $change_serversettings = $this->getParam('change_serversettings', true, $result['change_serversettings']); - $ipaddress = intval_ressource($this->getParam('ipaddress', true, $result['ip'])); - - $diskspace = $diskspace * 1024; - $traffic = $traffic * 1024 * 1024; - } - - // validation - $name = validate($name, 'name', '', '', array(), true); - $idna_convert = new idna_convert_wrapper(); - $email = $idna_convert->encode(validate($email, 'email', '', '', array(), true)); - $def_language = validate($def_language, 'default language', '', '', array(), true); - $custom_notes = validate(str_replace("\r\n", "\n", $custom_notes), 'custom_notes', '/^[^\0]*$/', '', array(), true); - $theme = validate($theme, 'theme', '', '', array(), true); - $password = validate($password, 'password', '', '', array(), true); - - if (Settings::Get('system.mail_quota_enabled') != '1') { - $email_quota = - 1; - } - - if (Settings::Get('ticket.enabled') != '1') { - $tickets = - 1; - } - - if (empty($theme)) { - $theme = Settings::Get('panel.default_theme'); - } - - if ($name == '') { - standard_error(array( - 'stringisempty', - 'myname' - ), '', true); - } elseif ($email == '') { - standard_error(array( - 'stringisempty', - 'emailadd' - ), '', true); - } elseif (! validateEmail($email)) { - standard_error('emailiswrong', $email, true); - } else { - - if ($deactivated != '1') { - $deactivated = '0'; - } - - if ($customers_see_all != '1') { - $customers_see_all = '0'; - } - - if ($domains_see_all != '1') { - $domains_see_all = '0'; - } - - if ($caneditphpsettings != '1') { - $caneditphpsettings = '0'; - } - - if ($change_serversettings != '1') { - $change_serversettings = '0'; - } - - if ($tickets_see_all != '1') { - $tickets_see_all = '0'; - } - - if ($password != '') { - $password = validatePassword($password, true); - $password = makeCryptPassword($password); + + if ($this->getUserDetail('change_serversettings') == 1 || $result['adminid'] == $this->getUserDetail('adminid')) { + // parameters + $name = $this->getParam('name', true, $result['name']); + $idna_convert = new idna_convert_wrapper(); + $email = $this->getParam('email', true, $idna_convert->decode($result['email'])); + $password = $this->getParam('admin_password', true, ''); + $def_language = $this->getParam('def_language', true, $result['def_language']); + $custom_notes = $this->getParam('custom_notes', true, $result['custom_notes']); + $custom_notes_show = $this->getParam('custom_notes_show', true, $result['custom_notes_show']); + $theme = $this->getParam('theme', true, $result['theme']); + + // you cannot edit some of the details of yourself + if ($result['adminid'] == $this->getUserDetail('adminid')) { + $deactivated = $result['deactivated']; + $customers = $result['customers']; + $domains = $result['domains']; + $subdomains = $result['subdomains']; + $emails = $result['emails']; + $email_accounts = $result['email_accounts']; + $email_forwarders = $result['email_forwarders']; + $email_quota = $result['email_quota']; + $ftps = $result['ftps']; + $tickets = $result['tickets']; + $mysqls = $result['mysqls']; + $tickets_see_all = $result['tickets_see_all']; + $customers_see_all = $result['customers_see_all']; + $domains_see_all = $result['domains_see_all']; + $caneditphpsettings = $result['caneditphpsettings']; + $change_serversettings = $result['change_serversettings']; + $diskspace = $result['diskspace']; + $traffic = $result['traffic']; + $ipaddress = $result['ip']; } else { - $password = $result['password']; + $deactivated = $this->getParam('deactivated', true, $result['deactivated']); + + $dec_places = Settings::Get('panel.decimal_places'); + $diskspace = $this->getUlParam('diskspace', 'diskspace_ul', true, round($result['diskspace'] / 1024, $dec_places)); + $traffic = $this->getUlParam('traffic', 'traffic_ul', true, round($result['traffic'] / (1024 * 1024), $dec_places)); + $customers = $this->getUlParam('customers', 'customers_ul', true, $result['customers']); + $domains = $this->getUlParam('domains', 'domains_ul', true, $result['domains']); + $subdomains = $this->getUlParam('subdomains', 'subdomains_ul', true, $result['subdomains']); + $emails = $this->getUlParam('emails', 'emails_ul', true, $result['emails']); + $email_accounts = $this->getUlParam('email_accounts', 'email_accounts_ul', true, $result['email_accounts']); + $email_forwarders = $this->getUlParam('email_forwarders', 'email_forwarders_ul', true, $result['email_forwarders']); + $email_quota = $this->getUlParam('email_quota', 'email_quota_ul', true, $result['email_quota']); + $ftps = $this->getUlParam('ftps', 'ftps_ul', true, $result['ftps']); + $tickets = $this->getUlParam('tickets', 'tickets_ul', true, $result['tickets']); + $mysqls = $this->getUlParam('mysqls', 'mysqls_ul', true, $result['mysqls']); + + $customers_see_all = $this->getParam('customers_see_all', true, $result['customers_see_all']); + $domains_see_all = $this->getParam('domains_see_all', true, $result['domains_see_all']); + $tickets_see_all = $this->getParam('tickets_see_all', true, $result['tickets_see_all']); + $caneditphpsettings = $this->getParam('caneditphpsettings', true, $result['caneditphpsettings']); + $change_serversettings = $this->getParam('change_serversettings', true, $result['change_serversettings']); + $ipaddress = intval_ressource($this->getParam('ipaddress', true, $result['ip'])); + + $diskspace = $diskspace * 1024; + $traffic = $traffic * 1024 * 1024; } - - // check if a resource was set to something lower - // than actually used by the admin/reseller - $res_warning = ""; - if ($customers != $result['customers'] && $customers != -1 && $customers < $result['customers_used']) { - $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'customers'); + + // validation + $name = validate($name, 'name', '', '', array(), true); + $idna_convert = new idna_convert_wrapper(); + $email = $idna_convert->encode(validate($email, 'email', '', '', array(), true)); + $def_language = validate($def_language, 'default language', '', '', array(), true); + $custom_notes = validate(str_replace("\r\n", "\n", $custom_notes), 'custom_notes', '/^[^\0]*$/', '', array(), true); + $theme = validate($theme, 'theme', '', '', array(), true); + $password = validate($password, 'password', '', '', array(), true); + + if (Settings::Get('system.mail_quota_enabled') != '1') { + $email_quota = - 1; } - if ($domains != $result['domains'] && $domains != -1 && $domains < $result['domains_used']) { - $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'domains'); + + if (Settings::Get('ticket.enabled') != '1') { + $tickets = - 1; } - if ($diskspace != $result['diskspace'] && ($diskspace / 1024) != -1 && $diskspace < $result['diskspace_used']) { - $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'diskspace'); + + if (empty($theme)) { + $theme = Settings::Get('panel.default_theme'); } - if ($traffic != $result['traffic'] && ($traffic / 1024 / 1024) != -1 && $traffic < $result['traffic_used']) { - $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'traffic'); + + if ($name == '') { + standard_error(array( + 'stringisempty', + 'myname' + ), '', true); + } elseif ($email == '') { + standard_error(array( + 'stringisempty', + 'emailadd' + ), '', true); + } elseif (! validateEmail($email)) { + standard_error('emailiswrong', $email, true); + } else { + + if ($deactivated != '1') { + $deactivated = '0'; + } + + if ($customers_see_all != '1') { + $customers_see_all = '0'; + } + + if ($domains_see_all != '1') { + $domains_see_all = '0'; + } + + if ($caneditphpsettings != '1') { + $caneditphpsettings = '0'; + } + + if ($change_serversettings != '1') { + $change_serversettings = '0'; + } + + if ($tickets_see_all != '1') { + $tickets_see_all = '0'; + } + + if ($password != '') { + $password = validatePassword($password, true); + $password = makeCryptPassword($password); + } else { + $password = $result['password']; + } + + // check if a resource was set to something lower + // than actually used by the admin/reseller + $res_warning = ""; + if ($customers != $result['customers'] && $customers != - 1 && $customers < $result['customers_used']) { + $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'customers'); + } + if ($domains != $result['domains'] && $domains != - 1 && $domains < $result['domains_used']) { + $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'domains'); + } + if ($diskspace != $result['diskspace'] && ($diskspace / 1024) != - 1 && $diskspace < $result['diskspace_used']) { + $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'diskspace'); + } + if ($traffic != $result['traffic'] && ($traffic / 1024 / 1024) != - 1 && $traffic < $result['traffic_used']) { + $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'traffic'); + } + if ($emails != $result['emails'] && $emails != - 1 && $emails < $result['emails_used']) { + $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'emails'); + } + if ($email_accounts != $result['email_accounts'] && $email_accounts != - 1 && $email_accounts < $result['email_accounts_used']) { + $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'email accounts'); + } + if ($email_forwarders != $result['email_forwarders'] && $email_forwarders != - 1 && $email_forwarders < $result['email_forwarders_used']) { + $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'email forwarders'); + } + if ($email_quota != $result['email_quota'] && $email_quota != - 1 && $email_quota < $result['email_quota_used']) { + $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'email quota'); + } + if ($ftps != $result['ftps'] && $ftps != - 1 && $ftps < $result['ftps_used']) { + $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'ftps'); + } + if ($tickets != $result['tickets'] && $tickets != - 1 && $tickets < $result['tickets_used']) { + $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'tickets'); + } + if ($mysqls != $result['mysqls'] && $mysqls != - 1 && $mysqls < $result['mysqls_used']) { + $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'mysqls'); + } + + if (! empty($res_warning)) { + throw new Exception($res_warning, 406); + } + + $upd_data = array( + 'password' => $password, + 'name' => $name, + 'email' => $email, + 'lang' => $def_language, + 'change_serversettings' => $change_serversettings, + 'customers' => $customers, + 'customers_see_all' => $customers_see_all, + 'domains' => $domains, + 'domains_see_all' => $domains_see_all, + 'caneditphpsettings' => $caneditphpsettings, + 'diskspace' => $diskspace, + 'traffic' => $traffic, + 'subdomains' => $subdomains, + 'emails' => $emails, + 'accounts' => $email_accounts, + 'forwarders' => $email_forwarders, + 'quota' => $email_quota, + 'ftps' => $ftps, + 'tickets' => $tickets, + 'tickets_see_all' => $tickets_see_all, + 'mysqls' => $mysqls, + 'ip' => $ipaddress, + 'deactivated' => $deactivated, + 'custom_notes' => $custom_notes, + 'custom_notes_show' => $custom_notes_show, + 'theme' => $theme, + 'adminid' => $id + ); + + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_ADMINS . "` SET + `password` = :password, + `name` = :name, + `email` = :email, + `def_language` = :lang, + `change_serversettings` = :change_serversettings, + `customers` = :customers, + `customers_see_all` = :customers_see_all, + `domains` = :domains, + `domains_see_all` = :domains_see_all, + `caneditphpsettings` = :caneditphpsettings, + `diskspace` = :diskspace, + `traffic` = :traffic, + `subdomains` = :subdomains, + `emails` = :emails, + `email_accounts` = :accounts, + `email_forwarders` = :forwarders, + `email_quota` = :quota, + `ftps` = :ftps, + `tickets` = :tickets, + `tickets_see_all` = :tickets_see_all, + `mysqls` = :mysqls, + `ip` = :ip, + `deactivated` = :deactivated, + `custom_notes` = :custom_notes, + `custom_notes_show` = :custom_notes_show, + `theme` = :theme + WHERE `adminid` = :adminid + "); + Database::pexecute($upd_stmt, $upd_data, true, true); + $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] edited admin '" . $result['loginname'] . "'"); + + // get all admin-data for return-array + $json_result = Admins::getLocal($this->getUserData(), array( + 'id' => $adminid + ))->get(); + $result = json_decode($json_result, true)['data']; + return $this->response(200, "successfull", $result); } - if ($emails != $result['emails'] && $emails != -1 && $emails < $result['emails_used']) { - $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'emails'); - } - if ($email_accounts != $result['email_accounts'] && $email_accounts != -1 && $email_accounts < $result['email_accounts_used']) { - $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'email accounts'); - } - if ($email_forwarders != $result['email_forwarders'] && $email_forwarders != -1 && $email_forwarders < $result['email_forwarders_used']) { - $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'email forwarders'); - } - if ($email_quota != $result['email_quota'] && $email_quota != -1 && $email_quota < $result['email_quota_used']) { - $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'email quota'); - } - if ($ftps != $result['ftps'] && $ftps != -1 && $ftps < $result['ftps_used']) { - $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'ftps'); - } - if ($tickets != $result['tickets'] && $tickets != -1 && $tickets < $result['tickets_used']) { - $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'tickets'); - } - if ($mysqls != $result['mysqls'] && $mysqls != -1 && $mysqls < $result['mysqls_used']) { - $res_warning .= sprintf($this->lng['error']['setlessthanalreadyused'], 'mysqls'); - } - - if (!empty($res_warning)) { - throw new Exception($res_warning, 406); - } - - $upd_data = array( - 'password' => $password, - 'name' => $name, - 'email' => $email, - 'lang' => $def_language, - 'change_serversettings' => $change_serversettings, - 'customers' => $customers, - 'customers_see_all' => $customers_see_all, - 'domains' => $domains, - 'domains_see_all' => $domains_see_all, - 'caneditphpsettings' => $caneditphpsettings, - 'diskspace' => $diskspace, - 'traffic' => $traffic, - 'subdomains' => $subdomains, - 'emails' => $emails, - 'accounts' => $email_accounts, - 'forwarders' => $email_forwarders, - 'quota' => $email_quota, - 'ftps' => $ftps, - 'tickets' => $tickets, - 'tickets_see_all' => $tickets_see_all, - 'mysqls' => $mysqls, - 'ip' => $ipaddress, - 'deactivated' => $deactivated, - 'custom_notes' => $custom_notes, - 'custom_notes_show' => $custom_notes_show, - 'theme' => $theme, - 'adminid' => $id - ); - - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_ADMINS . "` SET - `password` = :password, - `name` = :name, - `email` = :email, - `def_language` = :lang, - `change_serversettings` = :change_serversettings, - `customers` = :customers, - `customers_see_all` = :customers_see_all, - `domains` = :domains, - `domains_see_all` = :domains_see_all, - `caneditphpsettings` = :caneditphpsettings, - `diskspace` = :diskspace, - `traffic` = :traffic, - `subdomains` = :subdomains, - `emails` = :emails, - `email_accounts` = :accounts, - `email_forwarders` = :forwarders, - `email_quota` = :quota, - `ftps` = :ftps, - `tickets` = :tickets, - `tickets_see_all` = :tickets_see_all, - `mysqls` = :mysqls, - `ip` = :ip, - `deactivated` = :deactivated, - `custom_notes` = :custom_notes, - `custom_notes_show` = :custom_notes_show, - `theme` = :theme - WHERE `adminid` = :adminid - "); - Database::pexecute($upd_stmt, $upd_data, true, true); - $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] edited admin '" . $result['loginname'] . "'"); - - // get all admin-data for return-array - $json_result = Admins::getLocal($this->getUserData(), array( - 'id' => $adminid - ))->get(); - $result = json_decode($json_result, true)['data']; - return $this->response(200, "successfull", $result); } } throw new Exception("Not allowed to execute given command.", 403); @@ -566,7 +572,7 @@ class Admins extends ApiCommand implements ResourceEntity * optional, the admin-id * @param string $loginname * optional, the loginname - * + * * @access admin * @throws Exception * @return array @@ -577,46 +583,58 @@ class Admins extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); $loginname = trim($this->getParam('loginname', $ln_optional, '')); - + $json_result = Admins::getLocal($this->getUserData(), array( 'id' => $id, 'loginname' => $loginname ))->get(); $result = json_decode($json_result, true)['data']; $id = $result['adminid']; - + // don't be stupid - if ($id == $this->getUserDetail('userid')) { + if ($id == $this->getUserDetail('adminid')) { standard_error('youcantdeleteyourself', '', true); } - + $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_PANEL_ADMINS . "` WHERE `adminid` = :adminid "); - Database::pexecute($del_stmt, array('adminid' => $id), true, true); - + Database::pexecute($del_stmt, array( + 'adminid' => $id + ), true, true); + $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_PANEL_TRAFFIC_ADMINS . "` WHERE `adminid` = :adminid "); - Database::pexecute($del_stmt, array('adminid' => $id), true, true); - + Database::pexecute($del_stmt, array( + 'adminid' => $id + ), true, true); + $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_PANEL_DISKSPACE_ADMINS . "` WHERE `adminid` = :adminid "); - Database::pexecute($del_stmt, array('adminid' => $id), true, true); - + Database::pexecute($del_stmt, array( + 'adminid' => $id + ), true, true); + $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `adminid` = :userid WHERE `adminid` = :adminid "); - Database::pexecute($upd_stmt, array('userid' => $this->getUserDetail('userid'), 'adminid' => $id), true, true); - + Database::pexecute($upd_stmt, array( + 'userid' => $this->getUserDetail('adminid'), + 'adminid' => $id + ), true, true); + $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `adminid` = :userid WHERE `adminid` = :adminid "); - Database::pexecute($upd_stmt, array('userid' => $this->getUserDetail('userid'), 'adminid' => $id), true, true); - + Database::pexecute($upd_stmt, array( + 'userid' => $this->getUserDetail('adminid'), + 'adminid' => $id + ), true, true); + $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] deleted admin '" . $result['loginname'] . "'"); updateCounters(); return $this->response(200, "successfull", $result); @@ -631,7 +649,7 @@ class Admins extends ApiCommand implements ResourceEntity * optional, the admin-id * @param string $loginname * optional, the loginname - * + * * @access admin * @throws Exception * @return array @@ -642,7 +660,7 @@ class Admins extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); $loginname = trim($this->getParam('loginname', $ln_optional, '')); - + $json_result = Admins::getLocal($this->getUserData(), array( 'id' => $id, 'loginname' => $loginname From ae42e87a64059a42d82d389e822a73ecd255332c Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sun, 25 Feb 2018 21:26:51 +0100 Subject: [PATCH 072/746] fix in Admins.update; code-format Customers ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Admins.php | 2 +- lib/classes/api/commands/class.Customers.php | 80 ++++++++++---------- 2 files changed, 43 insertions(+), 39 deletions(-) diff --git a/lib/classes/api/commands/class.Admins.php b/lib/classes/api/commands/class.Admins.php index d6af8258..9c1f9407 100644 --- a/lib/classes/api/commands/class.Admins.php +++ b/lib/classes/api/commands/class.Admins.php @@ -555,7 +555,7 @@ class Admins extends ApiCommand implements ResourceEntity // get all admin-data for return-array $json_result = Admins::getLocal($this->getUserData(), array( - 'id' => $adminid + 'id' => $result['adminid'] ))->get(); $result = json_decode($json_result, true)['data']; return $this->response(200, "successfull", $result); diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index 8813adc2..b74dbd61 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -247,14 +247,18 @@ class Customers extends ApiCommand implements ResourceEntity $loginname_check_stmt = Database::prepare(" SELECT `loginname` FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `loginname` = :login "); - $loginname_check = Database::pexecute_first($loginname_check_stmt, array('login' => $loginname), true, true); + $loginname_check = Database::pexecute_first($loginname_check_stmt, array( + 'login' => $loginname + ), true, true); // Check if an admin with the loginname already exists // do not check via api as we skip any permission checks for this task $loginname_check_admin_stmt = Database::prepare(" SELECT `loginname` FROM `" . TABLE_PANEL_ADMINS . "` WHERE `loginname` = :login "); - $loginname_check_admin = Database::pexecute_first($loginname_check_admin_stmt, array('login' => $loginname), true, true); + $loginname_check_admin = Database::pexecute_first($loginname_check_admin_stmt, array( + 'login' => $loginname + ), true, true); if (strtolower($loginname_check['loginname']) == strtolower($loginname) || strtolower($loginname_check_admin['loginname']) == strtolower($loginname)) { standard_error('loginnameexists', $loginname, true); @@ -428,7 +432,7 @@ class Customers extends ApiCommand implements ResourceEntity // update last account number Settings::Set('system.lastaccountnumber', $accountnumber, true); } - + $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] added customer '" . $loginname . "'"); unset($ins_data); @@ -644,7 +648,7 @@ class Customers extends ApiCommand implements ResourceEntity } } $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] added customer '" . $loginname . "'"); - + $json_result = Customers::getLocal($this->getUserData(), array( 'loginname' => $loginname ))->get(); @@ -673,14 +677,14 @@ class Customers extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); $loginname = trim($this->getParam('loginname', $ln_optional, '')); - + $json_result = Customers::getLocal($this->getUserData(), array( 'id' => $id, 'loginname' => $loginname ))->get(); $result = json_decode($json_result, true)['data']; $id = $result['customerid']; - + if ($this->isAdmin()) { // parameters $move_to_admin = intval_ressource($this->getParam('move_to_admin', true, 0)); @@ -745,7 +749,7 @@ class Customers extends ApiCommand implements ResourceEntity $gender = $result['gender']; $custom_notes = $result['custom_notes']; $custom_notes_show = $result['custom_notes_show']; - + $dec_places = Settings::Get('panel.decimal_places'); $diskspace = round($result['diskspace'] / 1024, $dec_places); $traffic = round($result['traffic'] / (1024 * 1024), $dec_places); @@ -769,7 +773,7 @@ class Customers extends ApiCommand implements ResourceEntity $dnsenabled = $result['dnsenabled']; $deactivated = $result['deactivated']; } - + // validation $idna_convert = new idna_convert_wrapper(); $name = validate($name, 'name', '', '', array(), true); @@ -785,22 +789,22 @@ class Customers extends ApiCommand implements ResourceEntity $def_language = validate($def_language, 'default language', '', '', array(), true); $custom_notes = validate(str_replace("\r\n", "\n", $custom_notes), 'custom_notes', '/^[^\0]*$/', '', array(), true); $theme = validate($theme, 'theme', '', '', array(), true); - + if (Settings::Get('system.mail_quota_enabled') != '1') { $email_quota = - 1; } - + if (Settings::Get('ticket.enabled') != '1') { $tickets = - 1; } - + if (empty($theme)) { $theme = Settings::Get('panel.default_theme'); } - + $diskspace = $diskspace * 1024; $traffic = $traffic * 1024 * 1024; - + if ($this->isAdmin()) { if (((($this->getUserDetail('diskspace_used') + $diskspace - $result['diskspace']) > $this->getUserDetail('diskspace')) && ($this->getUserDetail('diskspace') / 1024) != '-1') || ((($this->getUserDetail('mysqls_used') + $mysqls - $result['mysqls']) > $this->getUserDetail('mysqls')) && $this->getUserDetail('mysqls') != '-1') || ((($this->getUserDetail('emails_used') + $emails - $result['emails']) > $this->getUserDetail('emails')) && $this->getUserDetail('emails') != '-1') || ((($this->getUserDetail('email_accounts_used') + $email_accounts - $result['email_accounts']) > $this->getUserDetail('email_accounts')) && $this->getUserDetail('email_accounts') != '-1') || ((($this->getUserDetail('email_forwarders_used') + $email_forwarders - $result['email_forwarders']) > $this->getUserDetail('email_forwarders')) && $this->getUserDetail('email_forwarders') != '-1') || ((($this->getUserDetail('email_quota_used') + $email_quota - $result['email_quota']) > $this->getUserDetail('email_quota')) && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') || ((($this->getUserDetail('ftps_used') + $ftps - $result['ftps']) > $this->getUserDetail('ftps')) && $this->getUserDetail('ftps') != '-1') || ((($this->getUserDetail('tickets_used') + $tickets - $result['tickets']) > $this->getUserDetail('tickets')) && $this->getUserDetail('tickets') != '-1') || ((($this->getUserDetail('subdomains_used') + $subdomains - $result['subdomains']) > $this->getUserDetail('subdomains')) && $this->getUserDetail('subdomains') != '-1') || (($diskspace / 1024) == '-1' && ($this->getUserDetail('diskspace') / 1024) != '-1') || ($mysqls == '-1' && $this->getUserDetail('mysqls') != '-1') || ($emails == '-1' && $this->getUserDetail('emails') != '-1') || ($email_accounts == '-1' && $this->getUserDetail('email_accounts') != '-1') || ($email_forwarders == '-1' && $this->getUserDetail('email_forwarders') != '-1') || ($email_quota == '-1' && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') || ($ftps == '-1' && $this->getUserDetail('ftps') != '-1') || ($tickets == '-1' && $this->getUserDetail('tickets') != '-1') || ($subdomains == '-1' && $this->getUserDetail('subdomains') != '-1')) { standard_error('youcantallocatemorethanyouhave', '', true); @@ -826,18 +830,18 @@ class Customers extends ApiCommand implements ResourceEntity standard_error('emailiswrong', $email, true); } } - + if ($password != '') { $password = validatePassword($password, true); $password = makeCryptPassword($password); } else { $password = $result['password']; } - + if ($createstdsubdomain != '1') { $createstdsubdomain = '0'; } - + if ($createstdsubdomain == '1' && $result['standardsubdomain'] == '0') { if (Settings::Get('system.stdsubdomain') !== null && Settings::Get('system.stdsubdomain') != '') { @@ -874,7 +878,7 @@ class Customers extends ApiCommand implements ResourceEntity inserttask('1'); } } - + if ($createstdsubdomain == '0' && $result['standardsubdomain'] != '0') { try { $std_domain = Domains::getLocal($this->getUserData(), array( @@ -887,27 +891,27 @@ class Customers extends ApiCommand implements ResourceEntity $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] automatically deleted standardsubdomain for user '" . $result['loginname'] . "'"); inserttask('1'); } - + if ($deactivated != '1') { $deactivated = '0'; } - + if ($phpenabled != '0') { $phpenabled = '1'; } - + if ($perlenabled != '0') { $perlenabled = '1'; } - + if ($dnsenabled != '0') { $dnsenabled = '1'; } - + if ($phpenabled != $result['phpenabled'] || $perlenabled != $result['perlenabled']) { inserttask('1'); } - + // activate/deactivate customer services if ($deactivated != $result['deactivated']) { @@ -924,7 +928,7 @@ class Customers extends ApiCommand implements ResourceEntity 'imap' => $imap, 'customerid' => $id )); - + $upd_stmt = Database::prepare(" UPDATE `" . TABLE_FTP_USERS . "` SET `login_enabled` = :yesno WHERE `customerid` = :customerid "); @@ -932,37 +936,37 @@ class Customers extends ApiCommand implements ResourceEntity 'yesno' => $yesno, 'customerid' => $id )); - + $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `deactivated`= :deactivated WHERE `customerid` = :customerid"); Database::pexecute($upd_stmt, array( 'deactivated' => $deactivated, 'customerid' => $id )); - + // Retrieve customer's databases $databases_stmt = Database::prepare("SELECT * FROM " . TABLE_PANEL_DATABASES . " WHERE customerid = :customerid ORDER BY `dbserver`"); Database::pexecute($databases_stmt, array( 'customerid' => $id )); - + Database::needRoot(true); $last_dbserver = 0; - + $dbm = new DbManager($this->logger()); - + // For each of them while ($row_database = $databases_stmt->fetch(PDO::FETCH_ASSOC)) { - + if ($last_dbserver != $row_database['dbserver']) { $dbm->getManager()->flushPrivileges(); Database::needRoot(true, $row_database['dbserver']); $last_dbserver = $row_database['dbserver']; } - + foreach (array_unique(explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) { $mysql_access_host = trim($mysql_access_host); - + // Prevent access, if deactivated if ($deactivated) { // failsafe if user has been deleted manually (requires MySQL 4.1.2+) @@ -973,7 +977,7 @@ class Customers extends ApiCommand implements ResourceEntity } } } - + // At last flush the new privileges $dbm->getManager()->flushPrivileges(); Database::needRoot(false); @@ -981,7 +985,7 @@ class Customers extends ApiCommand implements ResourceEntity $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] deactivated user '" . $result['loginname'] . "'"); inserttask('1'); } - + // Disable or enable POP3 Login for customers Mail Accounts if ($email_pop3 != $result['pop3']) { $upd_stmt = Database::prepare("UPDATE `" . TABLE_MAIL_USERS . "` SET `pop3` = :pop3 WHERE `customerid` = :customerid"); @@ -990,7 +994,7 @@ class Customers extends ApiCommand implements ResourceEntity 'customerid' => $id )); } - + // Disable or enable IMAP Login for customers Mail Accounts if ($email_imap != $result['imap']) { $upd_stmt = Database::prepare("UPDATE `" . TABLE_MAIL_USERS . "` SET `imap` = :imap WHERE `customerid` = :customerid"); @@ -999,7 +1003,7 @@ class Customers extends ApiCommand implements ResourceEntity 'customerid' => $id )); } - + $upd_data = array( 'customerid' => $id, 'passwd' => $password, @@ -1074,7 +1078,7 @@ class Customers extends ApiCommand implements ResourceEntity WHERE `customerid` = :customerid "); Database::pexecute($upd_stmt, $upd_data); - + if ($this->isAdmin()) { // Using filesystem - quota, insert a task which cleans the filesystem - quota inserttask('10'); @@ -1199,7 +1203,7 @@ class Customers extends ApiCommand implements ResourceEntity standard_error('moveofcustomerfailed', $move_result, true); } } - + $json_result = Customers::getLocal($this->getUserData(), array( 'id' => $result['customerid'] ))->get(); From 4a1decf359a921778376146052d2499a8332e148 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 26 Feb 2018 07:51:44 +0100 Subject: [PATCH 073/746] do not update fields of customer a customer cannot even change; unset custom_notes when admin of customer set custom_notes_show to 0 Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Customers.php | 468 +++++++++---------- 1 file changed, 225 insertions(+), 243 deletions(-) diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index b74dbd61..529f401a 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -95,6 +95,10 @@ class Customers extends ApiCommand implements ResourceEntity } $result = Database::pexecute_first($result_stmt, $params, true, true); if ($result) { + // check whether the admin does not want the customer to see the notes + if (! $this->isAdmin() && $result['custom_notes_show'] != 1) { + $result['custom_notes'] = ""; + } $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_NOTICE, "[API] get customer '" . $result['loginname'] . "'"); return $this->response(200, "successfull", $result); } @@ -732,62 +736,24 @@ class Customers extends ApiCommand implements ResourceEntity $def_language = $this->getParam('def_language', true, $result['def_language']); $password = $this->getParam('new_customer_password', true, ''); $theme = $this->getParam('theme', true, $result['theme']); - - // unchangeable parameters for customers - $move_to_admin = 0; - $idna_convert = new idna_convert_wrapper(); - $email = $idna_convert->decode($result['email']); - $name = $result['name']; - $firstname = $result['firstname']; - $company = $result['company']; - $street = $result['street']; - $zipcode = $result['zipcode']; - $city = $result['city']; - $phone = $result['phone']; - $fax = $result['fax']; - $customernumber = $result['customernumber']; - $gender = $result['gender']; - $custom_notes = $result['custom_notes']; - $custom_notes_show = $result['custom_notes_show']; - - $dec_places = Settings::Get('panel.decimal_places'); - $diskspace = round($result['diskspace'] / 1024, $dec_places); - $traffic = round($result['traffic'] / (1024 * 1024), $dec_places); - $subdomains = $result['subdomains']; - $emails = $result['emails']; - $email_accounts = $result['email_accounts']; - $email_forwarders = $result['email_forwarders']; - $email_quota = $result['email_quota']; - $email_imap = $result['imap']; - $email_pop3 = $result['pop3']; - $ftps = $result['ftps']; - $tickets = $result['tickets']; - $mysqls = $result['mysqls']; - // if we got one, it's true so none will be generated (it exists already) - // if not, none will be generated - $createstdsubdomain = ($result['standardsubdomain'] > 0 ? 1 : 0); - $sendpassword = 0; - $phpenabled = $result['phpenabled']; - $allowed_phpconfigs = json_decode($result['allowed_phpconfigs'], true); - $perlenabled = $result['perlenabled']; - $dnsenabled = $result['dnsenabled']; - $deactivated = $result['deactivated']; } // validation - $idna_convert = new idna_convert_wrapper(); - $name = validate($name, 'name', '', '', array(), true); - $firstname = validate($firstname, 'first name', '', '', array(), true); - $company = validate($company, 'company', '', '', array(), true); - $street = validate($street, 'street', '', '', array(), true); - $zipcode = validate($zipcode, 'zipcode', '/^[0-9 \-A-Z]*$/', '', array(), true); - $city = validate($city, 'city', '', '', array(), true); - $phone = validate($phone, 'phone', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); - $fax = validate($fax, 'fax', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); - $email = $idna_convert->encode(validate($email, 'email', '', '', array(), true)); - $customernumber = validate($customernumber, 'customer number', '/^[A-Za-z0-9 \-]*$/Di', '', array(), true); + if ($this->isAdmin()) { + $idna_convert = new idna_convert_wrapper(); + $name = validate($name, 'name', '', '', array(), true); + $firstname = validate($firstname, 'first name', '', '', array(), true); + $company = validate($company, 'company', '', '', array(), true); + $street = validate($street, 'street', '', '', array(), true); + $zipcode = validate($zipcode, 'zipcode', '/^[0-9 \-A-Z]*$/', '', array(), true); + $city = validate($city, 'city', '', '', array(), true); + $phone = validate($phone, 'phone', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); + $fax = validate($fax, 'fax', '/^[0-9\- \+\(\)\/]*$/', '', array(), true); + $email = $idna_convert->encode(validate($email, 'email', '', '', array(), true)); + $customernumber = validate($customernumber, 'customer number', '/^[A-Za-z0-9 \-]*$/Di', '', array(), true); + $custom_notes = validate(str_replace("\r\n", "\n", $custom_notes), 'custom_notes', '/^[^\0]*$/', '', array(), true); + } $def_language = validate($def_language, 'default language', '', '', array(), true); - $custom_notes = validate(str_replace("\r\n", "\n", $custom_notes), 'custom_notes', '/^[^\0]*$/', '', array(), true); $theme = validate($theme, 'theme', '', '', array(), true); if (Settings::Get('system.mail_quota_enabled') != '1') { @@ -802,10 +768,11 @@ class Customers extends ApiCommand implements ResourceEntity $theme = Settings::Get('panel.default_theme'); } - $diskspace = $diskspace * 1024; - $traffic = $traffic * 1024 * 1024; - if ($this->isAdmin()) { + + $diskspace = $diskspace * 1024; + $traffic = $traffic * 1024 * 1024; + if (((($this->getUserDetail('diskspace_used') + $diskspace - $result['diskspace']) > $this->getUserDetail('diskspace')) && ($this->getUserDetail('diskspace') / 1024) != '-1') || ((($this->getUserDetail('mysqls_used') + $mysqls - $result['mysqls']) > $this->getUserDetail('mysqls')) && $this->getUserDetail('mysqls') != '-1') || ((($this->getUserDetail('emails_used') + $emails - $result['emails']) > $this->getUserDetail('emails')) && $this->getUserDetail('emails') != '-1') || ((($this->getUserDetail('email_accounts_used') + $email_accounts - $result['email_accounts']) > $this->getUserDetail('email_accounts')) && $this->getUserDetail('email_accounts') != '-1') || ((($this->getUserDetail('email_forwarders_used') + $email_forwarders - $result['email_forwarders']) > $this->getUserDetail('email_forwarders')) && $this->getUserDetail('email_forwarders') != '-1') || ((($this->getUserDetail('email_quota_used') + $email_quota - $result['email_quota']) > $this->getUserDetail('email_quota')) && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') || ((($this->getUserDetail('ftps_used') + $ftps - $result['ftps']) > $this->getUserDetail('ftps')) && $this->getUserDetail('ftps') != '-1') || ((($this->getUserDetail('tickets_used') + $tickets - $result['tickets']) > $this->getUserDetail('tickets')) && $this->getUserDetail('tickets') != '-1') || ((($this->getUserDetail('subdomains_used') + $subdomains - $result['subdomains']) > $this->getUserDetail('subdomains')) && $this->getUserDetail('subdomains') != '-1') || (($diskspace / 1024) == '-1' && ($this->getUserDetail('diskspace') / 1024) != '-1') || ($mysqls == '-1' && $this->getUserDetail('mysqls') != '-1') || ($emails == '-1' && $this->getUserDetail('emails') != '-1') || ($email_accounts == '-1' && $this->getUserDetail('email_accounts') != '-1') || ($email_forwarders == '-1' && $this->getUserDetail('email_forwarders') != '-1') || ($email_quota == '-1' && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') || ($ftps == '-1' && $this->getUserDetail('ftps') != '-1') || ($tickets == '-1' && $this->getUserDetail('tickets') != '-1') || ($subdomains == '-1' && $this->getUserDetail('subdomains') != '-1')) { standard_error('youcantallocatemorethanyouhave', '', true); } @@ -838,210 +805,224 @@ class Customers extends ApiCommand implements ResourceEntity $password = $result['password']; } - if ($createstdsubdomain != '1') { - $createstdsubdomain = '0'; - } - - if ($createstdsubdomain == '1' && $result['standardsubdomain'] == '0') { - - if (Settings::Get('system.stdsubdomain') !== null && Settings::Get('system.stdsubdomain') != '') { - $_stdsubdomain = $result['loginname'] . '.' . Settings::Get('system.stdsubdomain'); - } else { - $_stdsubdomain = $result['loginname'] . '.' . Settings::Get('system.hostname'); + if ($this->isAdmin()) { + if ($createstdsubdomain != '1') { + $createstdsubdomain = '0'; } - $ins_data = array( - 'domain' => $_stdsubdomain, - 'customerid' => $result['customerid'], - 'adminid' => $this->getUserDetail('adminid'), - 'docroot' => $result['documentroot'], - 'phpenabled' => $phpenabled, - 'openbasedir' => '1' - ); - $domainid = - 1; - try { - $std_domain = Domains::getLocal($this->getUserData(), $ins_data)->add(); - $domainid = json_decode($std_domain, true)['data']['id']; - } catch (Exception $e) { - $this->logger()->logAction(ADM_ACTION, LOG_ERR, "[API] Unable to add standard-subdomain: " . $e->getMessage()); - } - - if ($domainid > 0) { - $upd_stmt = Database::prepare(" + if ($createstdsubdomain == '1' && $result['standardsubdomain'] == '0') { + + if (Settings::Get('system.stdsubdomain') !== null && Settings::Get('system.stdsubdomain') != '') { + $_stdsubdomain = $result['loginname'] . '.' . Settings::Get('system.stdsubdomain'); + } else { + $_stdsubdomain = $result['loginname'] . '.' . Settings::Get('system.hostname'); + } + + $ins_data = array( + 'domain' => $_stdsubdomain, + 'customerid' => $result['customerid'], + 'adminid' => $this->getUserDetail('adminid'), + 'docroot' => $result['documentroot'], + 'phpenabled' => $phpenabled, + 'openbasedir' => '1' + ); + $domainid = - 1; + try { + $std_domain = Domains::getLocal($this->getUserData(), $ins_data)->add(); + $domainid = json_decode($std_domain, true)['data']['id']; + } catch (Exception $e) { + $this->logger()->logAction(ADM_ACTION, LOG_ERR, "[API] Unable to add standard-subdomain: " . $e->getMessage()); + } + + if ($domainid > 0) { + $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `standardsubdomain` = :domainid WHERE `customerid` = :customerid "); - Database::pexecute($upd_stmt, array( - 'domainid' => $domainid, - 'customerid' => $result['customerid'] - ), true, true); - $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] automatically added standardsubdomain for user '" . $result['loginname'] . "'"); + Database::pexecute($upd_stmt, array( + 'domainid' => $domainid, + 'customerid' => $result['customerid'] + ), true, true); + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] automatically added standardsubdomain for user '" . $result['loginname'] . "'"); + inserttask('1'); + } + } + + if ($createstdsubdomain == '0' && $result['standardsubdomain'] != '0') { + try { + $std_domain = Domains::getLocal($this->getUserData(), array( + 'id' => $result['standardsubdomain'], + 'is_stdsubdomain' => 1 + ))->delete(); + } catch (Exception $e) { + $this->logger()->logAction(ADM_ACTION, LOG_ERR, "[API] Unable to delete standard-subdomain: " . $e->getMessage()); + } + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] automatically deleted standardsubdomain for user '" . $result['loginname'] . "'"); inserttask('1'); } - } - - if ($createstdsubdomain == '0' && $result['standardsubdomain'] != '0') { - try { - $std_domain = Domains::getLocal($this->getUserData(), array( - 'id' => $result['standardsubdomain'], - 'is_stdsubdomain' => 1 - ))->delete(); - } catch (Exception $e) { - $this->logger()->logAction(ADM_ACTION, LOG_ERR, "[API] Unable to delete standard-subdomain: " . $e->getMessage()); + + if ($deactivated != '1') { + $deactivated = '0'; } - $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] automatically deleted standardsubdomain for user '" . $result['loginname'] . "'"); - inserttask('1'); - } - - if ($deactivated != '1') { - $deactivated = '0'; - } - - if ($phpenabled != '0') { - $phpenabled = '1'; - } - - if ($perlenabled != '0') { - $perlenabled = '1'; - } - - if ($dnsenabled != '0') { - $dnsenabled = '1'; - } - - if ($phpenabled != $result['phpenabled'] || $perlenabled != $result['perlenabled']) { - inserttask('1'); - } - - // activate/deactivate customer services - if ($deactivated != $result['deactivated']) { - $yesno = (($deactivated) ? 'N' : 'Y'); - $pop3 = (($deactivated) ? '0' : (int) $result['pop3']); - $imap = (($deactivated) ? '0' : (int) $result['imap']); + if ($phpenabled != '0') { + $phpenabled = '1'; + } - $upd_stmt = Database::prepare(" + if ($perlenabled != '0') { + $perlenabled = '1'; + } + + if ($dnsenabled != '0') { + $dnsenabled = '1'; + } + + if ($phpenabled != $result['phpenabled'] || $perlenabled != $result['perlenabled']) { + inserttask('1'); + } + + // activate/deactivate customer services + if ($deactivated != $result['deactivated']) { + + $yesno = (($deactivated) ? 'N' : 'Y'); + $pop3 = (($deactivated) ? '0' : (int) $result['pop3']); + $imap = (($deactivated) ? '0' : (int) $result['imap']); + + $upd_stmt = Database::prepare(" UPDATE `" . TABLE_MAIL_USERS . "` SET `postfix`= :yesno, `pop3` = :pop3, `imap` = :imap WHERE `customerid` = :customerid "); - Database::pexecute($upd_stmt, array( - 'yesno' => $yesno, - 'pop3' => $pop3, - 'imap' => $imap, - 'customerid' => $id - )); - - $upd_stmt = Database::prepare(" + Database::pexecute($upd_stmt, array( + 'yesno' => $yesno, + 'pop3' => $pop3, + 'imap' => $imap, + 'customerid' => $id + )); + + $upd_stmt = Database::prepare(" UPDATE `" . TABLE_FTP_USERS . "` SET `login_enabled` = :yesno WHERE `customerid` = :customerid "); - Database::pexecute($upd_stmt, array( - 'yesno' => $yesno, - 'customerid' => $id - )); - - $upd_stmt = Database::prepare(" + Database::pexecute($upd_stmt, array( + 'yesno' => $yesno, + 'customerid' => $id + )); + + $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `deactivated`= :deactivated WHERE `customerid` = :customerid"); - Database::pexecute($upd_stmt, array( - 'deactivated' => $deactivated, - 'customerid' => $id - )); - - // Retrieve customer's databases - $databases_stmt = Database::prepare("SELECT * FROM " . TABLE_PANEL_DATABASES . " WHERE customerid = :customerid ORDER BY `dbserver`"); - Database::pexecute($databases_stmt, array( - 'customerid' => $id - )); - - Database::needRoot(true); - $last_dbserver = 0; - - $dbm = new DbManager($this->logger()); - - // For each of them - while ($row_database = $databases_stmt->fetch(PDO::FETCH_ASSOC)) { + Database::pexecute($upd_stmt, array( + 'deactivated' => $deactivated, + 'customerid' => $id + )); - if ($last_dbserver != $row_database['dbserver']) { - $dbm->getManager()->flushPrivileges(); - Database::needRoot(true, $row_database['dbserver']); - $last_dbserver = $row_database['dbserver']; - } + // Retrieve customer's databases + $databases_stmt = Database::prepare("SELECT * FROM " . TABLE_PANEL_DATABASES . " WHERE customerid = :customerid ORDER BY `dbserver`"); + Database::pexecute($databases_stmt, array( + 'customerid' => $id + )); - foreach (array_unique(explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) { - $mysql_access_host = trim($mysql_access_host); + Database::needRoot(true); + $last_dbserver = 0; + + $dbm = new DbManager($this->logger()); + + // For each of them + while ($row_database = $databases_stmt->fetch(PDO::FETCH_ASSOC)) { - // Prevent access, if deactivated - if ($deactivated) { - // failsafe if user has been deleted manually (requires MySQL 4.1.2+) - $dbm->getManager()->disableUser($row_database['databasename'], $mysql_access_host); - } else { - // Otherwise grant access - $dbm->getManager()->enableUser($row_database['databasename'], $mysql_access_host); + if ($last_dbserver != $row_database['dbserver']) { + $dbm->getManager()->flushPrivileges(); + Database::needRoot(true, $row_database['dbserver']); + $last_dbserver = $row_database['dbserver']; + } + + foreach (array_unique(explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) { + $mysql_access_host = trim($mysql_access_host); + + // Prevent access, if deactivated + if ($deactivated) { + // failsafe if user has been deleted manually (requires MySQL 4.1.2+) + $dbm->getManager()->disableUser($row_database['databasename'], $mysql_access_host); + } else { + // Otherwise grant access + $dbm->getManager()->enableUser($row_database['databasename'], $mysql_access_host); + } } } + + // At last flush the new privileges + $dbm->getManager()->flushPrivileges(); + Database::needRoot(false); + + $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] deactivated user '" . $result['loginname'] . "'"); + inserttask('1'); } - // At last flush the new privileges - $dbm->getManager()->flushPrivileges(); - Database::needRoot(false); + // Disable or enable POP3 Login for customers Mail Accounts + if ($email_pop3 != $result['pop3']) { + $upd_stmt = Database::prepare("UPDATE `" . TABLE_MAIL_USERS . "` SET `pop3` = :pop3 WHERE `customerid` = :customerid"); + Database::pexecute($upd_stmt, array( + 'pop3' => $email_pop3, + 'customerid' => $id + )); + } - $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] deactivated user '" . $result['loginname'] . "'"); - inserttask('1'); - } - - // Disable or enable POP3 Login for customers Mail Accounts - if ($email_pop3 != $result['pop3']) { - $upd_stmt = Database::prepare("UPDATE `" . TABLE_MAIL_USERS . "` SET `pop3` = :pop3 WHERE `customerid` = :customerid"); - Database::pexecute($upd_stmt, array( - 'pop3' => $email_pop3, - 'customerid' => $id - )); - } - - // Disable or enable IMAP Login for customers Mail Accounts - if ($email_imap != $result['imap']) { - $upd_stmt = Database::prepare("UPDATE `" . TABLE_MAIL_USERS . "` SET `imap` = :imap WHERE `customerid` = :customerid"); - Database::pexecute($upd_stmt, array( - 'imap' => $email_imap, - 'customerid' => $id - )); + // Disable or enable IMAP Login for customers Mail Accounts + if ($email_imap != $result['imap']) { + $upd_stmt = Database::prepare("UPDATE `" . TABLE_MAIL_USERS . "` SET `imap` = :imap WHERE `customerid` = :customerid"); + Database::pexecute($upd_stmt, array( + 'imap' => $email_imap, + 'customerid' => $id + )); + } } $upd_data = array( 'customerid' => $id, 'passwd' => $password, - 'name' => $name, - 'firstname' => $firstname, - 'gender' => $gender, - 'company' => $company, - 'street' => $street, - 'zipcode' => $zipcode, - 'city' => $city, - 'phone' => $phone, - 'fax' => $fax, - 'email' => $email, - 'customerno' => $customernumber, 'lang' => $def_language, - 'diskspace' => $diskspace, - 'traffic' => $traffic, - 'subdomains' => $subdomains, - 'emails' => $emails, - 'email_accounts' => $email_accounts, - 'email_forwarders' => $email_forwarders, - 'email_quota' => $email_quota, - 'ftps' => $ftps, - 'tickets' => $tickets, - 'mysqls' => $mysqls, - 'deactivated' => $deactivated, - 'phpenabled' => $phpenabled, - 'allowed_phpconfigs' => empty($allowed_phpconfigs) ? "" : json_encode($allowed_phpconfigs), - 'imap' => $email_imap, - 'pop3' => $email_pop3, - 'perlenabled' => $perlenabled, - 'dnsenabled' => $dnsenabled, - 'custom_notes' => $custom_notes, - 'custom_notes_show' => $custom_notes_show, 'theme' => $theme ); - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET + + if ($this->isAdmin()) { + $admin_upd_data = array( + 'name' => $name, + 'firstname' => $firstname, + 'gender' => $gender, + 'company' => $company, + 'street' => $street, + 'zipcode' => $zipcode, + 'city' => $city, + 'phone' => $phone, + 'fax' => $fax, + 'email' => $email, + 'customerno' => $customernumber, + 'diskspace' => $diskspace, + 'traffic' => $traffic, + 'subdomains' => $subdomains, + 'emails' => $emails, + 'email_accounts' => $email_accounts, + 'email_forwarders' => $email_forwarders, + 'email_quota' => $email_quota, + 'ftps' => $ftps, + 'tickets' => $tickets, + 'mysqls' => $mysqls, + 'deactivated' => $deactivated, + 'phpenabled' => $phpenabled, + 'allowed_phpconfigs' => empty($allowed_phpconfigs) ? "" : json_encode($allowed_phpconfigs), + 'imap' => $email_imap, + 'pop3' => $email_pop3, + 'perlenabled' => $perlenabled, + 'dnsenabled' => $dnsenabled, + 'custom_notes' => $custom_notes, + 'custom_notes_show' => $custom_notes_show + ); + $upd_data = $upd_data + $admin_upd_data; + } + + $upd_query = "UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET + `def_language` = :lang, + `password` = :passwd, + `theme` = :theme"; + + if ($this->isAdmin()) { + $admin_upd_query = ", `name` = :name, `firstname` = :firstname, `gender` = :gender, @@ -1053,8 +1034,6 @@ class Customers extends ApiCommand implements ResourceEntity `fax` = :fax, `email` = :email, `customernumber` = :customerno, - `def_language` = :lang, - `password` = :passwd, `diskspace` = :diskspace, `traffic` = :traffic, `subdomains` = :subdomains, @@ -1073,10 +1052,11 @@ class Customers extends ApiCommand implements ResourceEntity `perlenabled` = :perlenabled, `dnsenabled` = :dnsenabled, `custom_notes` = :custom_notes, - `custom_notes_show` = :custom_notes_show, - `theme` = :theme - WHERE `customerid` = :customerid - "); + `custom_notes_show` = :custom_notes_show"; + $upd_query .= $admin_upd_query; + } + $upd_query .= " WHERE `customerid` = :customerid"; + $upd_stmt = Database::prepare($upd_query); Database::pexecute($upd_stmt, $upd_data); if ($this->isAdmin()) { @@ -1193,14 +1173,16 @@ class Customers extends ApiCommand implements ResourceEntity /* * move customer to another admin/reseller; #1166 */ - if ($move_to_admin > 0 && $move_to_admin != $result['adminid']) { - $json_result = Customers::getLocal($this->getUserData(), array( - 'id' => $result['customerid'], - 'adminid' => $move_to_admin - ))->move(); - $move_result = json_decode($json_result, true)['data']; - if ($move_result != true) { - standard_error('moveofcustomerfailed', $move_result, true); + if ($this->isAdmin()) { + if ($move_to_admin > 0 && $move_to_admin != $result['adminid']) { + $json_result = Customers::getLocal($this->getUserData(), array( + 'id' => $result['customerid'], + 'adminid' => $move_to_admin + ))->move(); + $move_result = json_decode($json_result, true)['data']; + if ($move_result != true) { + standard_error('moveofcustomerfailed', $move_result, true); + } } } From 592c9ed0b9d42ffb9000ab0be41f39cbf989cc13 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 26 Feb 2018 08:21:56 +0100 Subject: [PATCH 074/746] automatically trim() all parameters given Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/abstract.ApiCommand.php | 15 ++++++++++++++ lib/classes/api/commands/class.Admins.php | 8 ++++---- lib/classes/api/commands/class.Customers.php | 21 +++++--------------- lib/classes/api/commands/class.Domains.php | 14 ++++++------- lib/classes/api/commands/class.Ftps.php | 2 +- lib/classes/api/commands/class.Mysqls.php | 6 +++--- 6 files changed, 35 insertions(+), 31 deletions(-) diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index afdba0c5..c11938bb 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -106,6 +106,10 @@ abstract class ApiCommand $this->version = $version; $this->dbversion = $dbversion; $this->branding = $branding; + + if (! is_null($params)) { + $params = $this->trimArray($params); + } $this->cmd_params = $params; if (! empty($header)) { $this->readUserData($header); @@ -463,4 +467,15 @@ abstract class ApiCommand } throw new Exception("Invalid API credentials", 400); } + + private function trimArray($input) + { + if (! is_array($input)) { + return trim($input); + } + return array_map(array( + $this, + 'trimArray' + ), $input); + } } diff --git a/lib/classes/api/commands/class.Admins.php b/lib/classes/api/commands/class.Admins.php index 9c1f9407..12f648e3 100644 --- a/lib/classes/api/commands/class.Admins.php +++ b/lib/classes/api/commands/class.Admins.php @@ -63,7 +63,7 @@ class Admins extends ApiCommand implements ResourceEntity { $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); - $loginname = trim($this->getParam('loginname', $ln_optional, '')); + $loginname = $this->getParam('loginname', $ln_optional, ''); if ($this->isAdmin() && ($this->getUserDetail('change_serversettings') == 1 || ($this->getUserDetail('adminid') == $id || $this->getUserDetail('loginname') == $loginname))) { $result_stmt = Database::prepare(" @@ -314,7 +314,7 @@ class Admins extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); - $loginname = trim($this->getParam('loginname', $ln_optional, '')); + $loginname = $this->getParam('loginname', $ln_optional, ''); $json_result = Admins::getLocal($this->getUserData(), array( 'id' => $id, @@ -582,7 +582,7 @@ class Admins extends ApiCommand implements ResourceEntity if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); - $loginname = trim($this->getParam('loginname', $ln_optional, '')); + $loginname = $this->getParam('loginname', $ln_optional, ''); $json_result = Admins::getLocal($this->getUserData(), array( 'id' => $id, @@ -659,7 +659,7 @@ class Admins extends ApiCommand implements ResourceEntity if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); - $loginname = trim($this->getParam('loginname', $ln_optional, '')); + $loginname = $this->getParam('loginname', $ln_optional, ''); $json_result = Admins::getLocal($this->getUserData(), array( 'id' => $id, diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index 529f401a..de370b9c 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -70,7 +70,7 @@ class Customers extends ApiCommand implements ResourceEntity { $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); - $loginname = trim($this->getParam('loginname', $ln_optional, '')); + $loginname = $this->getParam('loginname', $ln_optional, ''); if ($this->isAdmin()) { $result_stmt = Database::prepare(" @@ -208,18 +208,7 @@ class Customers extends ApiCommand implements ResourceEntity standard_error('youcantallocatemorethanyouhave', '', true); } - // Either $name and $firstname or the $company must be inserted - if ($name == '' && $company == '') { - standard_error(array( - 'stringisempty', - 'myname' - ), '', true); - } elseif ($firstname == '' && $company == '') { - standard_error(array( - 'stringisempty', - 'myfirstname' - ), '', true); - } elseif ($email == '') { + if ($email == '') { standard_error(array( 'stringisempty', 'emailadd' @@ -680,7 +669,7 @@ class Customers extends ApiCommand implements ResourceEntity { $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); - $loginname = trim($this->getParam('loginname', $ln_optional, '')); + $loginname = $this->getParam('loginname', $ln_optional, ''); $json_result = Customers::getLocal($this->getUserData(), array( 'id' => $id, @@ -1212,7 +1201,7 @@ class Customers extends ApiCommand implements ResourceEntity if ($this->isAdmin()) { $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); - $loginname = trim($this->getParam('loginname', $ln_optional, '')); + $loginname = $this->getParam('loginname', $ln_optional, ''); $delete_userfiles = $this->getParam('delete_userfiles', true, 0); $json_result = Customers::getLocal($this->getUserData(), array( @@ -1454,7 +1443,7 @@ class Customers extends ApiCommand implements ResourceEntity if ($this->isAdmin()) { $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); - $loginname = trim($this->getParam('loginname', $ln_optional, '')); + $loginname = $this->getParam('loginname', $ln_optional, ''); $json_result = Customers::getLocal($this->getUserData(), array( 'id' => $id, diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index 31ca5e63..c9539d6d 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -73,7 +73,7 @@ class Domains extends ApiCommand implements ResourceEntity if ($this->isAdmin()) { $id = $this->getParam('id', true, 0); $dn_optional = ($id <= 0 ? false : true); - $domainname = trim($this->getParam('domainname', $dn_optional, '')); + $domainname = $this->getParam('domainname', $dn_optional, ''); $no_std_subdomain = $this->getParam('no_std_subdomain', true, false); // convert possible idn domain to punycode @@ -131,8 +131,8 @@ class Domains extends ApiCommand implements ResourceEntity $speciallogfile = $this->getParam('speciallogfile', true, 0); $aliasdomain = intval($this->getParam('alias', true, 0)); $issubof = intval($this->getParam('issubof', true, 0)); - $registration_date = trim($this->getParam('registration_date', true, '')); - $termination_date = trim($this->getParam('termination_date', true, '')); + $registration_date = $this->getParam('registration_date', true, ''); + $termination_date = $this->getParam('termination_date', true, ''); $caneditdomain = $this->getParam('caneditdomain', true, 0); $isbinddomain = $this->getParam('isbinddomain', true, 0); $zonefile = $this->getParam('zonefile', true, ''); @@ -774,7 +774,7 @@ class Domains extends ApiCommand implements ResourceEntity // parameters $id = $this->getParam('id', true, 0); $dn_optional = ($id <= 0 ? false : true); - $domainname = trim($this->getParam('domainname', $dn_optional, '')); + $domainname = $this->getParam('domainname', $dn_optional, ''); // get requested domain $json_result = Domains::getLocal($this->getUserData(), array( @@ -799,8 +799,8 @@ class Domains extends ApiCommand implements ResourceEntity $speciallogverified = $this->getParam('speciallogverified', true, 0); $aliasdomain = intval($this->getParam('alias', true, $result['aliasdomain'])); $issubof = intval($this->getParam('issubof', true, $result['ismainbutsubto'])); - $registration_date = trim($this->getParam('registration_date', true, $result['registration_date'])); - $termination_date = trim($this->getParam('termination_date', true, $result['termination_date'])); + $registration_date = $this->getParam('registration_date', true, $result['registration_date']); + $termination_date = $this->getParam('termination_date', true, $result['termination_date']); $caneditdomain = $this->getParam('caneditdomain', true, $result['caneditdomain']); $isbinddomain = $this->getParam('isbinddomain', true, $result['isbinddomain']); $zonefile = $this->getParam('zonefile', true, $result['zonefile']); @@ -1587,7 +1587,7 @@ class Domains extends ApiCommand implements ResourceEntity if ($this->isAdmin()) { $id = $this->getParam('id', true, 0); $dn_optional = ($id <= 0 ? false : true); - $domainname = trim($this->getParam('domainname', $dn_optional, '')); + $domainname = $this->getParam('domainname', $dn_optional, ''); $is_stdsubdomain = $this->getParam('is_stdsubdomain', true, 0); $remove_subbutmain_domains = $this->getParam('delete_mainsubdomains', true, 0); diff --git a/lib/classes/api/commands/class.Ftps.php b/lib/classes/api/commands/class.Ftps.php index e0150ab4..0766e58c 100644 --- a/lib/classes/api/commands/class.Ftps.php +++ b/lib/classes/api/commands/class.Ftps.php @@ -37,7 +37,7 @@ class Ftps extends ApiCommand implements ResourceEntity { $id = $this->getParam('id', true, 0); $un_optional = ($id <= 0 ? false : true); - $username = trim($this->getParam('username', $un_optional, '')); + $username = $this->getParam('username', $un_optional, ''); $params = array(); if ($this->isAdmin()) { diff --git a/lib/classes/api/commands/class.Mysqls.php b/lib/classes/api/commands/class.Mysqls.php index 5fa12a1e..68e24b61 100644 --- a/lib/classes/api/commands/class.Mysqls.php +++ b/lib/classes/api/commands/class.Mysqls.php @@ -238,7 +238,7 @@ class Mysqls extends ApiCommand implements ResourceEntity { $id = $this->getParam('id', true, 0); $dn_optional = ($id <= 0 ? false : true); - $dbname = trim($this->getParam('dbname', $dn_optional, '')); + $dbname = $this->getParam('dbname', $dn_optional, ''); $dbserver = $this->getParam('mysql_server', true, - 1); if ($this->isAdmin()) { @@ -337,7 +337,7 @@ class Mysqls extends ApiCommand implements ResourceEntity { $id = $this->getParam('id', true, 0); $dn_optional = ($id <= 0 ? false : true); - $dbname = trim($this->getParam('dbname', $dn_optional, '')); + $dbname = $this->getParam('dbname', $dn_optional, ''); $dbserver = $this->getParam('mysql_server', true, - 1); if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'mysql')) { @@ -541,7 +541,7 @@ class Mysqls extends ApiCommand implements ResourceEntity { $id = $this->getParam('id', true, 0); $dn_optional = ($id <= 0 ? false : true); - $dbname = trim($this->getParam('dbname', $dn_optional, '')); + $dbname = $this->getParam('dbname', $dn_optional, ''); $dbserver = $this->getParam('mysql_server', true, - 1); if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'mysql')) { From c9256c0020b675ac3f376a4d079a93c7c5b3ce1d Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 26 Feb 2018 09:01:13 +0100 Subject: [PATCH 075/746] add 'adminname' to result in Customers.get; fix Customers.move and return customer-data there instead of just true Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Customers.php | 28 +++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index de370b9c..40823efd 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -74,8 +74,9 @@ class Customers extends ApiCommand implements ResourceEntity if ($this->isAdmin()) { $result_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_CUSTOMERS . "` - WHERE " . ($id > 0 ? "`customerid` = :idln" : "`loginname` = :idln") . ($this->getUserDetail('customers_see_all') ? '' : " AND `adminid` = :adminid")); + SELECT `c`.*, `a`.`loginname` AS `adminname` + FROM `" . TABLE_PANEL_CUSTOMERS . "` `c`, `" . TABLE_PANEL_ADMINS . "` `a` + WHERE " . ($id > 0 ? "`c`.`customerid` = :idln" : "`c`.`loginname` = :idln") . ($this->getUserDetail('customers_see_all') ? '' : " AND `c`.`adminid` = :adminid"). " AND `c`.`adminid` = `a`.`adminid`"); $params = array( 'idln' => ($id <= 0 ? $loginname : $id) ); @@ -1480,19 +1481,22 @@ class Customers extends ApiCommand implements ResourceEntity * * @access admin * @throws Exception - * @return bool true on success, error-message on failure + * @return array */ public function move() { if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { - $id = $this->getParam('id'); $adminid = $this->getParam('adminid'); + $id = $this->getParam('id', true, 0); + $ln_optional = ($id <= 0 ? false : true); + $loginname = $this->getParam('loginname', $ln_optional, ''); - // get customer - $json_result = Admins::getLocal($this->getUserData(), array( - 'id' => $id + $json_result = Customers::getLocal($this->getUserData(), array( + 'id' => $id, + 'loginname' => $loginname ))->get(); $c_result = json_decode($json_result, true)['data']; + $id = $c_result['customerid']; // check if target-admin is the current admin if ($adminid == $c_result['adminid']) { @@ -1500,7 +1504,7 @@ class Customers extends ApiCommand implements ResourceEntity } // get target admin - $json_result = Customers::getLocal($this->getUserData(), array( + $json_result = Admins::getLocal($this->getUserData(), array( 'id' => $adminid ))->get(); $a_result = json_decode($json_result, true)['data']; @@ -1535,8 +1539,12 @@ class Customers extends ApiCommand implements ResourceEntity // now, recalculate the resource-usage for the old and the new admin updateCounters(false); - $log->logAction(ADM_ACTION, LOG_INFO, "[API] moved user '" . $c_result['loginname'] . "' from admin/reseller '" . $c_result['adminname'] . " to admin/reseller '" . $a_result['loginname'] . "'"); - return $this->response(200, "successfull", true); + $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] moved user '" . $c_result['loginname'] . "' from admin/reseller '" . $c_result['adminname'] . " to admin/reseller '" . $a_result['loginname'] . "'"); + $json_result = Customers::getLocal($this->getUserData(), array( + 'id' => $c_result['customerid'] + ))->get(); + $result = json_decode($json_result, true)['data']; + return $this->response(200, "successfull", $result); } throw new Exception("Not allowed to execute given command.", 403); } From 6191ee6fba27d30da2ae6d5e14265955d68bf50d Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 26 Feb 2018 09:36:58 +0100 Subject: [PATCH 076/746] add Ftps.list and Ftps.delete Signed-off-by: Michael Kaufmann (d00p) --- customer_ftp.php | 63 +------- lib/classes/api/commands/class.Ftps.php | 183 +++++++++++++++++++++- lib/classes/api/commands/class.Mysqls.php | 3 +- 3 files changed, 186 insertions(+), 63 deletions(-) diff --git a/customer_ftp.php b/customer_ftp.php index e40886d5..bd6cbe11 100644 --- a/customer_ftp.php +++ b/customer_ftp.php @@ -90,66 +90,11 @@ if ($page == 'overview') { if (isset($result['username']) && $result['username'] != $userinfo['loginname']) { if (isset($_POST['send']) && $_POST['send'] == 'send') { - $stmt = Database::prepare("UPDATE `" . TABLE_FTP_USERS . "` - SET `up_count` = `up_count` + :up_count, - `up_bytes` = `up_bytes` + :up_bytes, - `down_count` = `down_count` + :down_count, - `down_bytes` = `down_bytes` + :down_bytes - WHERE `username` = :username" - ); - $params = array( - "up_count" => $result['up_count'], - "up_bytes" => $result['up_bytes'], - "down_count" => $result['down_count'], - "down_bytes" => $result['down_bytes'], - "username" => $userinfo['loginname'] - ); - Database::pexecute($stmt, $params); - - $result_stmt = Database::prepare("SELECT `username`, `homedir` FROM `" . TABLE_FTP_USERS . "` - WHERE `customerid` = :customerid - AND `id` = :id" - ); - Database::pexecute($result_stmt, array("customerid" => $userinfo['customerid'], "id" => $id)); - $result = $result_stmt->fetch(PDO::FETCH_ASSOC); - - $stmt = Database::prepare("DELETE FROM `" . TABLE_FTP_QUOTATALLIES . "` WHERE `name` = :name"); - Database::pexecute($stmt, array("name" => $result['username'])); - - $stmt = Database::prepare("DELETE FROM `" . TABLE_FTP_USERS . "` - WHERE `customerid` = :customerid - AND `id` = :id" - ); - Database::pexecute($stmt, array("customerid" => $userinfo['customerid'], "id" => $id)); - - $stmt = Database::prepare(" - UPDATE `" . TABLE_FTP_GROUPS . "` SET - `members` = REPLACE(`members`, :username,'') - WHERE `customerid` = :customerid - "); - Database::pexecute($stmt, array("username" => ",".$result['username'], "customerid" => $userinfo['customerid'])); - - $log->logAction(USR_ACTION, LOG_INFO, "deleted ftp-account '" . $result['username'] . "'"); - - $resetaccnumber = ($userinfo['ftps_used'] == '1') ? " , `ftp_lastaccountnumber`='0'" : ''; - - // refs #293 - if (isset($_POST['delete_userfiles']) && (int)$_POST['delete_userfiles'] == 1) { - inserttask('8', $userinfo['loginname'], $result['homedir']); - } else { - if (Settings::Get('system.nssextrausers') == 1) - { - // this is used so that the libnss-extrausers cron is fired - inserttask(5); - } + try { + Ftps::getLocal($userinfo, $_POST)->delete(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - - $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` - SET `ftps_used` = `ftps_used` - 1 $resetaccnumber - WHERE `customerid` = :customerid" - ); - Database::pexecute($stmt, array("customerid" => $userinfo['customerid'])); - redirectTo($filename, array('page' => $page, 's' => $s)); } else { ask_yesno_withcheckbox('ftp_reallydelete', 'admin_customer_alsoremoveftphomedir', $filename, array('id' => $id, 'page' => $page, 'action' => $action), $result['username']); diff --git a/lib/classes/api/commands/class.Ftps.php b/lib/classes/api/commands/class.Ftps.php index 0766e58c..7005ab99 100644 --- a/lib/classes/api/commands/class.Ftps.php +++ b/lib/classes/api/commands/class.Ftps.php @@ -38,7 +38,7 @@ class Ftps extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $un_optional = ($id <= 0 ? false : true); $username = $this->getParam('username', $un_optional, ''); - + $params = array(); if ($this->isAdmin()) { if ($this->getUserDetail('customers_see_all') == false) { @@ -86,9 +86,186 @@ class Ftps extends ApiCommand implements ResourceEntity public function update() {} + /** + * list all ftp-users, if called from an admin, list all ftp-users of all customers you are allowed to view, or specify id or loginname for one specific customer + * + * @param int $customerid + * optional, admin-only, select ftp-users of a specific customer by id + * @param string $loginname + * optional, admin-only, select ftp-users of a specific customer by loginname + * + * @access admin, customer + * @throws Exception + * @return array count|list + */ public function list() - {} + { + if ($this->isAdmin()) { + // if we're an admin, list all ftp-users of all the admins customers + // or optionally for one specific customer identified by id or loginname + $customerid = $this->getParam('customerid', true, 0); + $loginname = $this->getParam('loginname', true, ''); + + if (! empty($customer_id) || ! empty($loginname)) { + $json_result = Customers::getLocal($this->getUserData(), array( + 'id' => $customerid, + 'loginname' => $loginname + ))->get(); + $custom_list_result = array( + json_decode($json_result, true)['data'] + ); + } else { + $json_result = Customers::getLocal($this->getUserData())->list(); + $custom_list_result = json_decode($json_result, true)['data']['list']; + } + $customer_ids = array(); + foreach ($custom_list_result as $customer) { + $customer_ids[] = $customer['customerid']; + } + } else { + if (Settings::IsInList('panel.customer_hide_options', 'ftp')) { + throw new Exception("You cannot access this resource", 405); + } + $customer_ids = array( + $this->getUserDetail('customerid') + ); + } + $result = array(); + $params['customerid'] = implode(", ", $customer_ids); + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_FTP_USERS . "` + WHERE `customerid` IN (:customerid) + "); + Database::pexecute($result_stmt, $params); + while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { + $result[] = $row; + } + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_NOTICE, "[API] list ftp-users"); + return $this->response(200, "successfull", array( + 'count' => count($result), + 'list' => $result + )); + } + /** + * delete a ftp-user by either id or username + * + * @param int $id + * optional, the ftp-user-id + * @param string $username + * optional, the username + * @param bool $delete_userfiles + * optional, default false + * + * @access admin, customer + * @throws Exception + * @return array + */ public function delete() - {} + { + $id = $this->getParam('id', true, 0); + $un_optional = ($id <= 0 ? false : true); + $username = $this->getParam('username', $un_optional, ''); + $delete_userfiles = $this->getParam('delete_userfiles', true, 0); + + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'ftp')) { + throw new Exception("You cannot access this resource", 405); + } + + // get ftp-user + $json_result = Ftps::getLocal($this->getUserData(), array( + 'id' => $id, + 'username' => $username + ))->get(); + $result = json_decode($json_result, true)['data']; + $id = $result['id']; + + if ($this->isAdmin()) { + // get customer-data + $json_result = Customers::getLocal($this->getUserData(), array( + 'id' => $result['customerid'] + ))->get(); + $customer_data = json_decode($json_result, true)['data']; + } else { + $customer_data = $this->getUserData(); + } + + // add usage of this ftp-user to main-ftp user of customer if different + if ($result['username'] != $customer_data['loginname']) { + $stmt = Database::prepare("UPDATE `" . TABLE_FTP_USERS . "` + SET `up_count` = `up_count` + :up_count, + `up_bytes` = `up_bytes` + :up_bytes, + `down_count` = `down_count` + :down_count, + `down_bytes` = `down_bytes` + :down_bytes + WHERE `username` = :username + "); + $params = array( + "up_count" => $result['up_count'], + "up_bytes" => $result['up_bytes'], + "down_count" => $result['down_count'], + "down_bytes" => $result['down_bytes'], + "username" => $customer_data['loginname'] + ); + Database::pexecute($stmt, $params, true, true); + } + + // remove all quotatallies + $stmt = Database::prepare("DELETE FROM `" . TABLE_FTP_QUOTATALLIES . "` WHERE `name` = :name"); + Database::pexecute($stmt, array( + "name" => $result['username'] + ), true, tue); + + // remove user itself + $stmt = Database::prepare(" + DELETE FROM `" . TABLE_FTP_USERS . "` WHERE `customerid` = :customerid AND `id` = :id + "); + Database::pexecute($stmt, array( + "customerid" => $customer_data['customerid'], + "id" => $id + ), true, true); + + // update ftp-groups + $stmt = Database::prepare(" + UPDATE `" . TABLE_FTP_GROUPS . "` SET + `members` = REPLACE(`members`, :username,'') + WHERE `customerid` = :customerid + "); + Database::pexecute($stmt, array( + "username" => "," . $result['username'], + "customerid" => $customer_data['customerid'] + ), true, true); + + $log->logAction(USR_ACTION, LOG_INFO, "deleted ftp-account '" . $result['username'] . "'"); + + // refs #293 + if ($delete_userfiles == 1) { + inserttask('8', $customer_data['loginname'], $result['homedir']); + } else { + if (Settings::Get('system.nssextrausers') == 1) { + // this is used so that the libnss-extrausers cron is fired + inserttask(5); + } + } + + // decrease ftp-user usage for customer + $resetaccnumber = ($customer_data['ftps_used'] == '1') ? " , `ftp_lastaccountnumber`='0'" : ''; + $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` + SET `ftps_used` = `ftps_used` - 1 $resetaccnumber + WHERE `customerid` = :customerid"); + Database::pexecute($stmt, array( + "customerid" => $customer_data['customerid'] + ), true, true); + // update admin usage + $stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_ADMINS . "` + SET `mysqls_used` = `mysqls_used` - 1 + WHERE `adminid` = :adminid + "); + Database::pexecute($stmt, array( + "adminid" => ($this->isAdmin() ? $customer_data['adminid'] : $this->getUserDetail('adminid')) + ), true, true); + + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_WARNING, "[API] deleted ftp-user '" . $result['username'] . "'"); + return $this->response(200, "successfull", $result); + } } diff --git a/lib/classes/api/commands/class.Mysqls.php b/lib/classes/api/commands/class.Mysqls.php index 68e24b61..ad2ea24c 100644 --- a/lib/classes/api/commands/class.Mysqls.php +++ b/lib/classes/api/commands/class.Mysqls.php @@ -440,6 +440,7 @@ class Mysqls extends ApiCommand implements ResourceEntity * optional, admin-only, select dbs of a specific customer by loginname * * @access admin, customer + * @throws Exception * @return array count|list */ public function list() @@ -560,7 +561,6 @@ class Mysqls extends ApiCommand implements ResourceEntity Database::needRoot(true, $result['dbserver']); $dbm = new DbManager($this->logger()); $dbm->getManager()->deleteDatabase($result['databasename']); - $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_WARNING, "[API] deleted database '" . $result['databasename'] . "'"); Database::needRoot(false); // End root-session @@ -602,6 +602,7 @@ class Mysqls extends ApiCommand implements ResourceEntity "adminid" => ($this->isAdmin() ? $customer['adminid'] : $this->getUserDetail('adminid')), ), true, true); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_WARNING, "[API] deleted database '" . $result['databasename'] . "'"); return $this->response(200, "successfull", $result); } } From ceb8619552f2d135ca83741a0d35b8c9e3b5495c Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 26 Feb 2018 12:15:48 +0100 Subject: [PATCH 077/746] preparations for assign-multiple-ips-to-an-admin in Api, not in webinterface yet Signed-off-by: Michael Kaufmann (d00p) --- install/froxlor.sql | 2 +- .../updates/froxlor/0.10/update_0.10.inc.php | 14 +++++++ lib/classes/api/commands/class.Admins.php | 10 ++--- .../api/commands/class.IpsAndPorts.php | 41 +++++++++++++------ 4 files changed, 48 insertions(+), 19 deletions(-) diff --git a/install/froxlor.sql b/install/froxlor.sql index 8fb1e8e8..cf40bfc3 100644 --- a/install/froxlor.sql +++ b/install/froxlor.sql @@ -95,7 +95,7 @@ CREATE TABLE `panel_admins` ( `name` varchar(255) NOT NULL default '', `email` varchar(255) NOT NULL default '', `def_language` varchar(255) NOT NULL default '', - `ip` tinyint(4) NOT NULL default '-1', + `ip` varchar(500) NOT NULL default '-1', `customers` int(15) NOT NULL default '0', `customers_used` int(15) NOT NULL default '0', `customers_see_all` tinyint(1) NOT NULL default '0', diff --git a/install/updates/froxlor/0.10/update_0.10.inc.php b/install/updates/froxlor/0.10/update_0.10.inc.php index 1748ecc3..25b35a6f 100644 --- a/install/updates/froxlor/0.10/update_0.10.inc.php +++ b/install/updates/froxlor/0.10/update_0.10.inc.php @@ -51,4 +51,18 @@ if (isFroxlorVersion('0.10.0')) { showUpdateStep("Adding new default-ssl-ip setting"); Settings::AddNew('system.defaultsslip', ''); lastStepStatus(0); + + showUpdateStep("Altering admin ip's field to allow multiple ip addresses"); + // get all admins for updating the new field + $sel_stmt = Database::prepare("SELECT adminid, ip FROM `panel_admins`"); + Database::pexecute($sel_stmt); + $all_admins = $sel_stmt->fetchAll(PDO::FETCH_ASSOC); + Database::query("ALTER TABLE `panel_admins` MODIFY `ip` varchar(500) NOT NULL default '-1';"); + $upd_stmt = Database::prepare("UPDATE `panel_admins` SET `ip` = :ip WHERE `adminid` = :adminid"); + foreach ($all_admins as $adm) { + if ($admin['ip'] != -1) { + Database::pexecute($upd_stmt, array('ip' => json_encode($adm['ip']), 'adminid' => $adm['adminid'])); + } + } + lastStepStatus(0); } diff --git a/lib/classes/api/commands/class.Admins.php b/lib/classes/api/commands/class.Admins.php index 12f648e3..f5780ea3 100644 --- a/lib/classes/api/commands/class.Admins.php +++ b/lib/classes/api/commands/class.Admins.php @@ -124,7 +124,7 @@ class Admins extends ApiCommand implements ResourceEntity $tickets_see_all = $this->getParam('tickets_see_all', true, 0); $caneditphpsettings = $this->getParam('caneditphpsettings', true, 0); $change_serversettings = $this->getParam('change_serversettings', true, 0); - $ipaddress = intval_ressource($this->getParam('ipaddress', true, - 1)); + $ipaddress = $this->getParam('ipaddress', true, -1); // validation $name = validate($name, 'name', '', '', array(), true); @@ -244,7 +244,7 @@ class Admins extends ApiCommand implements ResourceEntity 'tickets' => $tickets, 'tickets_see_all' => $tickets_see_all, 'mysqls' => $mysqls, - 'ip' => $ipaddress, + 'ip' => empty($ipaddress) ? "" : (is_array($ipaddress) && $ipaddress > 0 ? json_encode($ipaddress) : -1), 'theme' => $_theme, 'custom_notes' => $custom_notes, 'custom_notes_show' => $custom_notes_show @@ -354,7 +354,7 @@ class Admins extends ApiCommand implements ResourceEntity $change_serversettings = $result['change_serversettings']; $diskspace = $result['diskspace']; $traffic = $result['traffic']; - $ipaddress = $result['ip']; + $ipaddress = ($result['ip'] != -1 ? json_decode($result['ip'], true) : -1); } else { $deactivated = $this->getParam('deactivated', true, $result['deactivated']); @@ -377,7 +377,7 @@ class Admins extends ApiCommand implements ResourceEntity $tickets_see_all = $this->getParam('tickets_see_all', true, $result['tickets_see_all']); $caneditphpsettings = $this->getParam('caneditphpsettings', true, $result['caneditphpsettings']); $change_serversettings = $this->getParam('change_serversettings', true, $result['change_serversettings']); - $ipaddress = intval_ressource($this->getParam('ipaddress', true, $result['ip'])); + $ipaddress = $this->getParam('ipaddress', true, ($result['ip'] != -1 ? json_decode($result['ip'], true) : -1)); $diskspace = $diskspace * 1024; $traffic = $traffic * 1024 * 1024; @@ -512,7 +512,7 @@ class Admins extends ApiCommand implements ResourceEntity 'tickets' => $tickets, 'tickets_see_all' => $tickets_see_all, 'mysqls' => $mysqls, - 'ip' => $ipaddress, + 'ip' => empty($ipaddress) ? "" : (is_array($ipaddress) && $ipaddress > 0 ? json_encode($ipaddress) : -1), 'deactivated' => $deactivated, 'custom_notes' => $custom_notes, 'custom_notes_show' => $custom_notes_show, diff --git a/lib/classes/api/commands/class.IpsAndPorts.php b/lib/classes/api/commands/class.IpsAndPorts.php index 32ab79a3..a2bb599f 100644 --- a/lib/classes/api/commands/class.IpsAndPorts.php +++ b/lib/classes/api/commands/class.IpsAndPorts.php @@ -27,10 +27,14 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity */ public function list() { - if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { + if ($this->isAdmin() && ($this->getUserDetail('change_serversettings') || ! empty($this->getUserDetail('ip')))) { $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] list ips and ports"); + $ip_where = ""; + if (!empty($this->getUserDetail('ip')) && $this->getUserDetail('ip') != -1) { + $ip_where = "WHERE `id` IN (".implode(", ", json_decode($this->getUserDetail('ip'), true)).")"; + } $result_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_IPSANDPORTS . "` ORDER BY `ip` ASC, `port` ASC + SELECT * FROM `" . TABLE_PANEL_IPSANDPORTS . "` " . $ip_where . " ORDER BY `ip` ASC, `port` ASC "); Database::pexecute($result_stmt, null, true, true); $result = array(); @@ -50,16 +54,21 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity * * @param int $id * ip-port-id - * + * * @access admin * @throws Exception * @return array */ public function get() { - if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { + if ($this->isAdmin() && ($this->getUserDetail('change_serversettings') || ! empty($this->getUserDetail('ip')))) { $id = $this->getParam('id'); - $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] get ip and port #" . $id); + if (!empty($this->getUserDetail('ip')) && $this->getUserDetail('ip') != -1) { + $allowed_ips = json_decode($this->getUserDetail('ip'), true); + if (!in_array($id, $allowed_ips)) { + throw new Exception("You cannot access this resource", 405); + } + } $result_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :id "); @@ -67,6 +76,7 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity 'id' => $id ), true, true); if ($result) { + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] get ip " . $result['ip'] . " " . $result['port']); return $this->response(200, "successfull", $result); } throw new Exception("IP/port with id #" . $id . " could not be found", 404); @@ -204,7 +214,12 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity $ip = '[' . $ip . ']'; } $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] added IP/port '" . $ip . ":" . $port . "'"); - return $this->response(200, "successfull", $ins_data); + // get ip for return-array + $json_result = IpsAndPorts::getLocal($this->getUserData(), array( + 'id' => $ins_data['id'] + ))->get(); + $result = json_decode($json_result, true)['data']; + return $this->response(200, "successfull", $result); } throw new Exception("Not allowed to execute given command.", 403); } @@ -220,7 +235,7 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity */ public function update() { - if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { + if ($this->isAdmin() && ($this->getUserDetail('change_serversettings') || ! empty($this->getUserDetail('ip')))) { $id = $this->getParam('id'); $json_result = IpsAndPorts::getLocal($this->getUserData(), array( @@ -368,7 +383,7 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity * * @param int $id * ip-port-id - * + * * @access admin * @throws Exception * @return array @@ -411,17 +426,17 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity if ($result['ip'] != '') { $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_PANEL_IPSANDPORTS . "` - WHERE `id` = :id - "); + DELETE FROM `" . TABLE_PANEL_IPSANDPORTS . "` + WHERE `id` = :id + "); Database::pexecute($del_stmt, array( 'id' => $id )); // also, remove connections to domains (multi-stack) $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_ipandports` = :id - "); + DELETE FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_ipandports` = :id + "); Database::pexecute($del_stmt, array( 'id' => $id )); From b0f355ba2f426ed7ccfc956dc4f6c08d9c82bf76 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 26 Feb 2018 15:49:41 +0100 Subject: [PATCH 078/746] fixed in Mysqls.add, added Ftps.add Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Ftps.php | 264 +++++++++++++++++++++- lib/classes/api/commands/class.Mysqls.php | 9 +- 2 files changed, 270 insertions(+), 3 deletions(-) diff --git a/lib/classes/api/commands/class.Ftps.php b/lib/classes/api/commands/class.Ftps.php index 7005ab99..595a5f78 100644 --- a/lib/classes/api/commands/class.Ftps.php +++ b/lib/classes/api/commands/class.Ftps.php @@ -18,8 +18,270 @@ class Ftps extends ApiCommand implements ResourceEntity { + /** + * add a new ftp-user + * + * @param string $ftp_password + * password for the created database and database-user + * @param string $path + * destination path + * @param string $ftp_description + * optional, description for ftp-user + * @param bool $sendinfomail + * optional, send created resource-information to customer, default: false + * @param string $shell + * optional, default /bin/false (not changeable when deactivated) + * @param string $ftp_username + * optional if customer.ftpatdomain is allowed, specify an username + * @param string $ftp_domain + * optional if customer.ftpatdomain is allowed, specify a domain (customer must be owner) + * @param int $customer_id + * required when called as admin, not needed when called as customer + * + * @access admin, customer + * @throws Exception + * @return array + */ public function add() - {} + { + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'ftp')) { + throw new Exception("You cannot access this resource", 405); + } + + if ($this->getUserDetail('ftps_used') < $this->getUserDetail('ftps') || $this->getUserDetail('ftps') == '-1') { + + // required paramters + $path = $this->getParam('path'); + $password = $this->getParam('ftp_password'); + + // parameters + $description = $this->getParam('ftp_description', true, ''); + $sendinfomail = $this->getParam('sendinfomail', true, 0); + $shell = $this->getParam('shell', true, '/bin/false'); + + $ftpusername = $this->getParam('ftp_username', true, ''); + $ftpdomain = $this->getParam('ftp_domain', true, ''); + + // validation + $password = validate($password, 'password', '', '', array(), true); + $password = validatePassword($password, true); + $description = validate(trim($description), 'description', '', '', array(), true); + + if (Settings::Get('system.allow_customer_shell') == '1') { + $shell = validate(trim($shell), 'shell', '', '', array(), true); + } else { + $shell = "/bin/false"; + } + + if (Settings::Get('customer.ftpatdomain') == '1') { + $ftpusername = validate(trim($ftpusername), 'username', '/^[a-zA-Z0-9][a-zA-Z0-9\-_]+\$?$/', '', array(), true); + $idna_convert = new idna_convert_wrapper(); + $ftpdomain = $idna_convert->encode(validate($ftpdomain, 'domain', '', '', array(), true)); + } + + $params = array(); + // get needed customer info to reduce the mysql-usage-counter by one + if ($this->isAdmin()) { + // get customer id + $customer_id = $this->getParam('customer_id'); + $json_result = Customers::getLocal($this->getUserData(), array( + 'id' => $customer_id + ))->get(); + $customer = json_decode($json_result, true)['data']; + // check whether the customer has enough resources to get the database added + if ($customer['ftps_used'] >= $customer['ftps'] && $customer['ftps'] != '-1') { + throw new Exception("Customer has no more resources available", 406); + } + } else { + $customer_id = $this->getUserDetail('customer_id'); + $customer = $this->getUserData(); + } + + if ($sendinfomail != 1) { + $sendinfomail = 0; + } + + if (Settings::Get('customer.ftpatdomain') == '1') { + if ($ftpusername == '') { + standard_error(array( + 'stringisempty', + 'username' + ), '', true); + } + $ftpdomain_check_stmt = Database::prepare("SELECT `id`, `domain`, `customerid` FROM `" . TABLE_PANEL_DOMAINS . "` + WHERE `domain` = :domain + AND `customerid` = :customerid"); + $ftpdomain_check = Database::pexecute_first($ftpdomain_check_stmt, array( + "domain" => $ftpdomain, + "customerid" => $customer_id + ), true, true); + + if ($ftpdomain_check && $ftpdomain_check['domain'] != $ftpdomain) { + standard_error('maindomainnonexist', $domain, true); + } + $username = $ftpusername . "@" . $ftpdomain; + } else { + $username = $customer['loginname'] . Settings::Get('customer.ftpprefix') . (intval($customer['ftp_lastaccountnumber']) + 1); + } + + $username_check_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_FTP_USERS . "` WHERE `username` = :username + "); + $username_check = Database::pexecute_first($username_check_stmt, array( + "username" => $username + ), true, true); + + if (! empty($username_check) && $username_check['username'] = $username) { + standard_error('usernamealreadyexists', $username, true); + } elseif ($password == '') { + standard_error(array( + 'stringisempty', + 'mypassword' + ), '', true); + } elseif ($username == $password) { + standard_error('passwordshouldnotbeusername', '', true); + } else { + $path = makeCorrectDir($customer['documentroot'] . '/' . $path); + $cryptPassword = makeCryptPassword($password); + + $stmt = Database::prepare("INSERT INTO `" . TABLE_FTP_USERS . "` + (`customerid`, `username`, `description`, `password`, `homedir`, `login_enabled`, `uid`, `gid`, `shell`) + VALUES (:customerid, :username, :description, :password, :homedir, 'y', :guid, :guid, :shell)"); + $params = array( + "customerid" => $customer_id, + "username" => $username, + "description" => $description, + "password" => $cryptPassword, + "homedir" => $path, + "guid" => $customer['guid'], + "shell" => $shell + ); + Database::pexecute($stmt, $params, true, true); + $ftp_userid = Database::lastInsertId(); + + $result_stmt = Database::prepare(" + SELECT `bytes_in_used` FROM `" . TABLE_FTP_QUOTATALLIES . "` WHERE `name` = :name + "); + Database::pexecute($result_stmt, array( + "name" => $customer['loginname'] + ), true, true); + + while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { + $stmt = Database::prepare("INSERT INTO `" . TABLE_FTP_QUOTATALLIES . "` + (`name`, `quota_type`, `bytes_in_used`, `bytes_out_used`, `bytes_xfer_used`, `files_in_used`, `files_out_used`, `files_xfer_used`) + VALUES (:name, 'user', :bytes_in_used, '0', '0', '0', '0', '0') + "); + Database::pexecute($stmt, array( + "name" => $username, + "bytes_in_used" => $row['bytes_in_used'] + ), true, true); + } + + $stmt = Database::prepare(" + UPDATE `" . TABLE_FTP_GROUPS . "` + SET `members` = CONCAT_WS(',',`members`, :username) + WHERE `customerid`= :customerid AND `gid`= :guid + "); + $params = array( + "username" => $username, + "customerid" => $customer_id, + "guid" => $customer['guid'] + ); + Database::pexecute($stmt, $params, true, true); + + $stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_CUSTOMERS . "` + SET `ftps_used` = `ftps_used` + 1, + `ftp_lastaccountnumber` = `ftp_lastaccountnumber` + 1 + WHERE `customerid` = :customerid + "); + Database::pexecute($stmt, array( + "customerid" => $customer_id + ), true, true); + + $stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_ADMINS . "` + SET `ftps_used` = `ftps_used` + 1 + WHERE `adminid` = :adminid + "); + Database::pexecute($stmt, array( + "adminid" => $customer['adminid'] + ), true, true); + + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] added ftp-account '" . $username . " (" . $path . ")'"); + inserttask(5); + + if ($sendinfomail == 1) { + $replace_arr = array( + 'SALUTATION' => getCorrectUserSalutation($customer), + 'CUST_NAME' => getCorrectUserSalutation($customer), // < keep this for compatibility + 'USR_NAME' => $username, + 'USR_PASS' => $password, + 'USR_PATH' => makeCorrectDir(str_replace($customer['documentroot'], "/", $path)) + ); + + $def_language = $customer['def_language']; + $result_stmt = Database::prepare(" + SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` + WHERE `adminid` = :adminid + AND `language` = :lang + AND `templategroup`='mails' + AND `varname`='new_ftpaccount_by_customer_subject' + "); + Database::pexecute($result_stmt, array( + "adminid" => $customer['adminid'], + "lang" => $def_language + )); + $result = $result_stmt->fetch(PDO::FETCH_ASSOC); + $mail_subject = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $this->lng['mails']['new_ftpaccount_by_customer']['subject']), $replace_arr)); + + $def_language = $customer['def_language']; + $result_stmt = Database::prepare(" + SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` + WHERE `adminid` = :adminid + AND `language` = :lang + AND `templategroup`='mails' + AND `varname`='new_ftpaccount_by_customer_mailbody'"); + Database::pexecute($result_stmt, array( + "adminid" => $customer['adminid'], + "lang" => $def_language + )); + $result = $result_stmt->fetch(PDO::FETCH_ASSOC); + $mail_body = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $this->lng['mails']['new_ftpaccount_by_customer']['mailbody']), $replace_arr)); + + $_mailerror = false; + try { + $mail->Subject = $mail_subject; + $mail->AltBody = $mail_body; + $mail->MsgHTML(str_replace("\n", "
    ", $mail_body)); + $mail->AddAddress($customer['email'], getCorrectUserSalutation($customer)); + $mail->Send(); + } catch (phpmailerException $e) { + $mailerr_msg = $e->errorMessage(); + $_mailerror = true; + } catch (Exception $e) { + $mailerr_msg = $e->getMessage(); + $_mailerror = true; + } + + if ($_mailerror) { + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_ERR, "[API] Error sending mail: " . $mailerr_msg); + standard_error('errorsendingmail', $customer['email']); + } + + $mail->ClearAddresses(); + } + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_WARNING, "[API] added ftp-user '" . $username . "'"); + + $json_result = Ftps::getLocal($this->getUserData(), array( + 'username' => $username + ))->get(); + $result = json_decode($json_result, true)['data']; + return $this->response(200, "successfull", $result); + } + } + throw new Exception("No more resources available", 406); + } /** * return a ftp-user entry by either id or username diff --git a/lib/classes/api/commands/class.Mysqls.php b/lib/classes/api/commands/class.Mysqls.php index ad2ea24c..2b4c6649 100644 --- a/lib/classes/api/commands/class.Mysqls.php +++ b/lib/classes/api/commands/class.Mysqls.php @@ -79,7 +79,7 @@ class Mysqls extends ApiCommand implements ResourceEntity // get customer id $customer_id = $this->getParam('customer_id'); $json_result = Customers::getLocal($this->getUserData(), array( - 'id' => $result['customerid'] + 'id' => $customer_id ))->get(); $customer = json_decode($json_result, true)['data']; // check whether the customer has enough resources to get the database added @@ -215,7 +215,12 @@ class Mysqls extends ApiCommand implements ResourceEntity $this->mail->ClearAddresses(); } $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_WARNING, "[API] added mysql-database '" . $username . "'"); - return $this->response(200, "successfull", $params); + + $json_result = Mysqls::getLocal($this->getUserData(), array( + 'dbname' => $username + ))->get(); + $result = json_decode($json_result, true)['data']; + return $this->response(200, "successfull", $result); } throw new Exception("No more resources available", 406); } From 55ec20be10d0030b53541791cb974094be9aef46 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 26 Feb 2018 15:55:49 +0100 Subject: [PATCH 079/746] use Ftps.add in webinterface Signed-off-by: Michael Kaufmann (d00p) --- customer_ftp.php | 168 +----------------------- lib/classes/api/commands/class.Ftps.php | 2 +- 2 files changed, 7 insertions(+), 163 deletions(-) diff --git a/customer_ftp.php b/customer_ftp.php index bd6cbe11..474ea057 100644 --- a/customer_ftp.php +++ b/customer_ftp.php @@ -104,169 +104,13 @@ if ($page == 'overview') { } } elseif ($action == 'add') { if ($userinfo['ftps_used'] < $userinfo['ftps'] || $userinfo['ftps'] == '-1') { - if (isset($_POST['send']) - && $_POST['send'] == 'send') { - $description = validate($_POST['ftp_description'], 'description'); - // @FIXME use a good path-validating regex here (refs #1231) - $path = validate($_POST['path'], 'path'); - $password = validate($_POST['ftp_password'], 'password'); - $password = validatePassword($password); - $shell = "/bin/false"; - if (Settings::Get('system.allow_customer_shell') == '1') { - $shell = isset($_POST['shell']) ? validate($_POST['shell'], 'shell') : '/bin/false'; - } - - $sendinfomail = isset($_POST['sendinfomail']) ? 1 : 0; - if ($sendinfomail != 1) { - $sendinfomail = 0; - } - - if (Settings::Get('customer.ftpatdomain') == '1') { - $ftpusername = validate($_POST['ftp_username'], 'username', '/^[a-zA-Z0-9][a-zA-Z0-9\-_]+\$?$/'); - if ($ftpusername == '') { - standard_error(array('stringisempty', 'username')); - } - $ftpdomain = $idna_convert->encode(validate($_POST['ftp_domain'], 'domain')); - $ftpdomain_check_stmt = Database::prepare("SELECT `id`, `domain`, `customerid` FROM `" . TABLE_PANEL_DOMAINS . "` - WHERE `domain` = :domain - AND `customerid` = :customerid" - ); - Database::pexecute($ftpdomain_check_stmt, array("domain" => $ftpdomain, "customerid" => $userinfo['customerid'])); - $ftpdomain_check = $ftpdomain_check_stmt->fetch(PDO::FETCH_ASSOC); - - if ($ftpdomain_check['domain'] != $ftpdomain) { - standard_error('maindomainnonexist', $domain); - } - $username = $ftpusername . "@" . $ftpdomain; - } else { - $username = $userinfo['loginname'] . Settings::Get('customer.ftpprefix') . (intval($userinfo['ftp_lastaccountnumber']) + 1); - } - - $username_check_stmt = Database::prepare("SELECT * FROM `" . TABLE_FTP_USERS . "` - WHERE `username` = :username" - ); - Database::pexecute($username_check_stmt, array("username" => $username)); - $username_check = $username_check_stmt->fetch(PDO::FETCH_ASSOC); - - if (!empty($username_check) && $username_check['username'] = $username) { - standard_error('usernamealreadyexists', $username); - } elseif ($password == '') { - standard_error(array('stringisempty', 'mypassword')); - } elseif ($path == '') { - standard_error('patherror'); - } elseif ($username == $password) { - standard_error('passwordshouldnotbeusername'); - } else { - $path = makeCorrectDir($userinfo['documentroot'] . '/' . $path); - - $cryptPassword = makeCryptPassword($password); - - $stmt = Database::prepare("INSERT INTO `" . TABLE_FTP_USERS . "` - (`customerid`, `username`, `description`, `password`, `homedir`, `login_enabled`, `uid`, `gid`, `shell`) - VALUES (:customerid, :username, :description, :password, :homedir, 'y', :guid, :guid, :shell)" - ); - $params = array( - "customerid" => $userinfo['customerid'], - "username" => $username, - "description" => $description, - "password" => $cryptPassword, - "homedir" => $path, - "guid" => $userinfo['guid'], - "shell" => $shell - ); - Database::pexecute($stmt, $params); - - $result_stmt = Database::prepare("SELECT `bytes_in_used` FROM `" . TABLE_FTP_QUOTATALLIES . "` - WHERE `name` = :name" - ); - Database::pexecute($result_stmt, array("name" => $userinfo['loginname'])); - - while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { - $stmt = Database::prepare("INSERT INTO `" . TABLE_FTP_QUOTATALLIES . "` - (`name`, `quota_type`, `bytes_in_used`, `bytes_out_used`, `bytes_xfer_used`, `files_in_used`, `files_out_used`, `files_xfer_used`) - VALUES (:name, 'user', :bytes_in_used, '0', '0', '0', '0', '0')" - ); - Database::pexecute($stmt, array("name" => $username, "bytes_in_used" => $row['bytes_in_used'])); - } - - $stmt = Database::prepare("UPDATE `" . TABLE_FTP_GROUPS . "` - SET `members` = CONCAT_WS(',',`members`, :username) - WHERE `customerid`= :customerid - AND `gid`= :guid" - ); - $params = array( - "username" => $username, - "customerid" => $userinfo['customerid'], - "guid" => $userinfo['guid'] - ); - Database::pexecute($stmt, $params); - - $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` - SET `ftps_used` = `ftps_used` + 1, - `ftp_lastaccountnumber` = `ftp_lastaccountnumber` + 1 - WHERE `customerid` = :customerid" - ); - Database::pexecute($stmt, array("customerid" => $userinfo['customerid'])); - - $log->logAction(USR_ACTION, LOG_INFO, "added ftp-account '" . $username . " (" . $path . ")'"); - inserttask(5); - - if ($sendinfomail == 1) { - $replace_arr = array( - 'SALUTATION' => getCorrectUserSalutation($userinfo), - 'CUST_NAME' => getCorrectUserSalutation($userinfo), // < keep this for compatibility - 'USR_NAME' => $username, - 'USR_PASS' => $password, - 'USR_PATH' => makeCorrectDir(str_replace($userinfo['documentroot'], "/", $path)) - ); - - $def_language = $userinfo['def_language']; - $result_stmt = Database::prepare("SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` - WHERE `adminid` = :adminid - AND `language` = :lang - AND `templategroup`='mails' - AND `varname`='new_ftpaccount_by_customer_subject'" - ); - Database::pexecute($result_stmt, array("adminid" => $userinfo['adminid'], "lang" => $def_language)); - $result = $result_stmt->fetch(PDO::FETCH_ASSOC); - $mail_subject = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $lng['mails']['new_ftpaccount_by_customer']['subject']), $replace_arr)); - - $def_language = $userinfo['def_language']; - $result_stmt = Database::prepare("SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` - WHERE `adminid` = :adminid - AND `language` = :lang - AND `templategroup`='mails' - AND `varname`='new_ftpaccount_by_customer_mailbody'" - ); - Database::pexecute($result_stmt, array("adminid" => $userinfo['adminid'], "lang" => $def_language)); - $result = $result_stmt->fetch(PDO::FETCH_ASSOC); - $mail_body = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $lng['mails']['new_ftpaccount_by_customer']['mailbody']), $replace_arr)); - - $_mailerror = false; - try { - $mail->Subject = $mail_subject; - $mail->AltBody = $mail_body; - $mail->MsgHTML(str_replace("\n", "
    ", $mail_body)); - $mail->AddAddress($userinfo['email'], getCorrectUserSalutation($userinfo)); - $mail->Send(); - } catch(phpmailerException $e) { - $mailerr_msg = $e->errorMessage(); - $_mailerror = true; - } catch (Exception $e) { - $mailerr_msg = $e->getMessage(); - $_mailerror = true; - } - - if ($_mailerror) { - $log->logAction(USR_ACTION, LOG_ERR, "Error sending mail: " . $mailerr_msg); - standard_error('errorsendingmail', $userinfo['email']); - } - - $mail->ClearAddresses(); - } - - redirectTo($filename, array('page' => $page, 's' => $s)); + if (isset($_POST['send']) && $_POST['send'] == 'send') { + try { + Ftps::getLocal($userinfo, $_POST)->add(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } + redirectTo($filename, array('page' => $page, 's' => $s)); } else { $pathSelect = makePathfield($userinfo['documentroot'], $userinfo['guid'], $userinfo['guid'], '/'); diff --git a/lib/classes/api/commands/class.Ftps.php b/lib/classes/api/commands/class.Ftps.php index 595a5f78..b9a0ea34 100644 --- a/lib/classes/api/commands/class.Ftps.php +++ b/lib/classes/api/commands/class.Ftps.php @@ -24,7 +24,7 @@ class Ftps extends ApiCommand implements ResourceEntity * @param string $ftp_password * password for the created database and database-user * @param string $path - * destination path + * destination path relative to the customers-homedir * @param string $ftp_description * optional, description for ftp-user * @param bool $sendinfomail From 257855b43bfd01bf06c7df465aaf2a136aed7b7c Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 26 Feb 2018 16:00:54 +0100 Subject: [PATCH 080/746] fix typo in field-value for ApiCommand::getUserDetail() Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Ftps.php | 2 +- lib/classes/api/commands/class.Mysqls.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/classes/api/commands/class.Ftps.php b/lib/classes/api/commands/class.Ftps.php index b9a0ea34..7f9ba35b 100644 --- a/lib/classes/api/commands/class.Ftps.php +++ b/lib/classes/api/commands/class.Ftps.php @@ -93,7 +93,7 @@ class Ftps extends ApiCommand implements ResourceEntity throw new Exception("Customer has no more resources available", 406); } } else { - $customer_id = $this->getUserDetail('customer_id'); + $customer_id = $this->getUserDetail('customerid'); $customer = $this->getUserData(); } diff --git a/lib/classes/api/commands/class.Mysqls.php b/lib/classes/api/commands/class.Mysqls.php index 2b4c6649..5811477f 100644 --- a/lib/classes/api/commands/class.Mysqls.php +++ b/lib/classes/api/commands/class.Mysqls.php @@ -87,7 +87,7 @@ class Mysqls extends ApiCommand implements ResourceEntity throw new Exception("Customer has no more resources available", 406); } } else { - $customer_id = $this->getUserDetail('customer_id'); + $customer_id = $this->getUserDetail('customerid'); } $newdb_params = array( @@ -389,7 +389,7 @@ class Mysqls extends ApiCommand implements ResourceEntity throw new Exception("Customer has no more resources available", 406); } } else { - $customer_id = $this->getUserDetail('customer_id'); + $customer_id = $this->getUserDetail('customerid'); } if ($password != '') { @@ -585,7 +585,7 @@ class Mysqls extends ApiCommand implements ResourceEntity $customer_id = $customer['customer_id']; } else { $mysql_used = $this->getUserDetail('mysqls_used'); - $customer_id = $this->getUserDetail('customer_id'); + $customer_id = $this->getUserDetail('customerid'); } // reduce mysql-usage-counter $resetaccnumber = ($mysql_used == '1') ? " , `mysql_lastaccountnumber` = '0' " : ''; From c093783904da43f1903da91df52f87db541e1e56 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 26 Feb 2018 16:02:09 +0100 Subject: [PATCH 081/746] forgot the mailobject :P Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Ftps.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/classes/api/commands/class.Ftps.php b/lib/classes/api/commands/class.Ftps.php index 7f9ba35b..863e3c7f 100644 --- a/lib/classes/api/commands/class.Ftps.php +++ b/lib/classes/api/commands/class.Ftps.php @@ -251,11 +251,11 @@ class Ftps extends ApiCommand implements ResourceEntity $_mailerror = false; try { - $mail->Subject = $mail_subject; - $mail->AltBody = $mail_body; - $mail->MsgHTML(str_replace("\n", "
    ", $mail_body)); - $mail->AddAddress($customer['email'], getCorrectUserSalutation($customer)); - $mail->Send(); + $this->mail->Subject = $mail_subject; + $this->mail->AltBody = $mail_body; + $this->mail->MsgHTML(str_replace("\n", "
    ", $mail_body)); + $this->mail->AddAddress($customer['email'], getCorrectUserSalutation($customer)); + $this->mail->Send(); } catch (phpmailerException $e) { $mailerr_msg = $e->errorMessage(); $_mailerror = true; @@ -269,7 +269,7 @@ class Ftps extends ApiCommand implements ResourceEntity standard_error('errorsendingmail', $customer['email']); } - $mail->ClearAddresses(); + $this->mail->ClearAddresses(); } $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_WARNING, "[API] added ftp-user '" . $username . "'"); From 4fad0def1e817edba28c67ddf5405ed5d176f17f Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 26 Feb 2018 18:02:18 +0100 Subject: [PATCH 082/746] fix mailer variable and fix typo in Ftps.add and Ftps.delete Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Ftps.php | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/classes/api/commands/class.Ftps.php b/lib/classes/api/commands/class.Ftps.php index 863e3c7f..226de773 100644 --- a/lib/classes/api/commands/class.Ftps.php +++ b/lib/classes/api/commands/class.Ftps.php @@ -251,11 +251,11 @@ class Ftps extends ApiCommand implements ResourceEntity $_mailerror = false; try { - $this->mail->Subject = $mail_subject; - $this->mail->AltBody = $mail_body; - $this->mail->MsgHTML(str_replace("\n", "
    ", $mail_body)); - $this->mail->AddAddress($customer['email'], getCorrectUserSalutation($customer)); - $this->mail->Send(); + $this->mailer()->Subject = $mail_subject; + $this->mailer()->AltBody = $mail_body; + $this->mailer()->MsgHTML(str_replace("\n", "
    ", $mail_body)); + $this->mailer()->AddAddress($customer['email'], getCorrectUserSalutation($customer)); + $this->mailer()->Send(); } catch (phpmailerException $e) { $mailerr_msg = $e->errorMessage(); $_mailerror = true; @@ -266,10 +266,10 @@ class Ftps extends ApiCommand implements ResourceEntity if ($_mailerror) { $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_ERR, "[API] Error sending mail: " . $mailerr_msg); - standard_error('errorsendingmail', $customer['email']); + standard_error('errorsendingmail', $customer['email'], true); } - $this->mail->ClearAddresses(); + $this->mailer()->ClearAddresses(); } $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_WARNING, "[API] added ftp-user '" . $username . "'"); @@ -475,7 +475,7 @@ class Ftps extends ApiCommand implements ResourceEntity $stmt = Database::prepare("DELETE FROM `" . TABLE_FTP_QUOTATALLIES . "` WHERE `name` = :name"); Database::pexecute($stmt, array( "name" => $result['username'] - ), true, tue); + ), true, true); // remove user itself $stmt = Database::prepare(" @@ -496,9 +496,7 @@ class Ftps extends ApiCommand implements ResourceEntity "username" => "," . $result['username'], "customerid" => $customer_data['customerid'] ), true, true); - - $log->logAction(USR_ACTION, LOG_INFO, "deleted ftp-account '" . $result['username'] . "'"); - + // refs #293 if ($delete_userfiles == 1) { inserttask('8', $customer_data['loginname'], $result['homedir']); From 4d89f614e33582408a1e7261a2aea7a810d12d0e Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 26 Feb 2018 18:08:43 +0100 Subject: [PATCH 083/746] remove unnecessary checks as getParam() validates the existance already Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Customers.php | 25 ++++---------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index 40823efd..2d1dce24 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -208,13 +208,8 @@ class Customers extends ApiCommand implements ResourceEntity if (((($this->getUserDetail('diskspace_used') + $diskspace) > $this->getUserDetail('diskspace')) && ($this->getUserDetail('diskspace') / 1024) != '-1') || ((($this->getUserDetail('mysqls_used') + $mysqls) > $this->getUserDetail('mysqls')) && $this->getUserDetail('mysqls') != '-1') || ((($this->getUserDetail('emails_used') + $emails) > $this->getUserDetail('emails')) && $this->getUserDetail('emails') != '-1') || ((($this->getUserDetail('email_accounts_used') + $email_accounts) > $this->getUserDetail('email_accounts')) && $this->getUserDetail('email_accounts') != '-1') || ((($this->getUserDetail('email_forwarders_used') + $email_forwarders) > $this->getUserDetail('email_forwarders')) && $this->getUserDetail('email_forwarders') != '-1') || ((($this->getUserDetail('email_quota_used') + $email_quota) > $this->getUserDetail('email_quota')) && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') || ((($this->getUserDetail('ftps_used') + $ftps) > $this->getUserDetail('ftps')) && $this->getUserDetail('ftps') != '-1') || ((($this->getUserDetail('tickets_used') + $tickets) > $this->getUserDetail('tickets')) && $this->getUserDetail('tickets') != '-1') || ((($this->getUserDetail('subdomains_used') + $subdomains) > $this->getUserDetail('subdomains')) && $this->getUserDetail('subdomains') != '-1') || (($diskspace / 1024) == '-1' && ($this->getUserDetail('diskspace') / 1024) != '-1') || ($mysqls == '-1' && $this->getUserDetail('mysqls') != '-1') || ($emails == '-1' && $this->getUserDetail('emails') != '-1') || ($email_accounts == '-1' && $this->getUserDetail('email_accounts') != '-1') || ($email_forwarders == '-1' && $this->getUserDetail('email_forwarders') != '-1') || ($email_quota == '-1' && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') || ($ftps == '-1' && $this->getUserDetail('ftps') != '-1') || ($tickets == '-1' && $this->getUserDetail('tickets') != '-1') || ($subdomains == '-1' && $this->getUserDetail('subdomains') != '-1')) { standard_error('youcantallocatemorethanyouhave', '', true); } - - if ($email == '') { - standard_error(array( - 'stringisempty', - 'emailadd' - ), '', true); - } elseif (! validateEmail($email)) { + + if (! validateEmail($email)) { standard_error('emailiswrong', $email, true); } else { @@ -687,7 +682,8 @@ class Customers extends ApiCommand implements ResourceEntity $email = $this->getParam('email', true, $idna_convert->decode($result['email'])); $name = $this->getParam('name', true, $result['name']); $firstname = $this->getParam('firstname', true, $result['firstname']); - $company = $this->getParam('company', true, $result['company']); + $company_required = (! empty($name) && empty($firstname)) || (empty($name) && ! empty($firstname)) || (empty($name) && empty($firstname)); + $company = $this->getParam('company', ($company_required ? false : true), $result['company']); $street = $this->getParam('street', true, $result['street']); $zipcode = $this->getParam('zipcode', true, $result['zipcode']); $city = $this->getParam('city', true, $result['city']); @@ -767,18 +763,7 @@ class Customers extends ApiCommand implements ResourceEntity standard_error('youcantallocatemorethanyouhave', '', true); } - // Either $name and $firstname or the $company must be inserted - if ($name == '' && $company == '') { - standard_error(array( - 'stringisempty', - 'myname' - ), '', true); - } elseif ($firstname == '' && $company == '') { - standard_error(array( - 'stringisempty', - 'myfirstname' - ), '', true); - } elseif ($email == '') { + if ($email == '') { standard_error(array( 'stringisempty', 'emailadd' From 9a4359e010acacf745e354eff3e1737e5f815c3a Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 26 Feb 2018 19:56:03 +0100 Subject: [PATCH 084/746] fix IpsAndPorts.delete Signed-off-by: Michael Kaufmann (d00p) --- .../api/commands/class.IpsAndPorts.php | 68 +++++++++---------- lng/english.lng.php | 2 +- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/lib/classes/api/commands/class.IpsAndPorts.php b/lib/classes/api/commands/class.IpsAndPorts.php index a2bb599f..81976d39 100644 --- a/lib/classes/api/commands/class.IpsAndPorts.php +++ b/lib/classes/api/commands/class.IpsAndPorts.php @@ -372,7 +372,12 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity inserttask('4'); $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] changed IP/port from '" . $result['ip'] . ":" . $result['port'] . "' to '" . $ip . ":" . $port . "'"); - return $this->response(200, "successfull", $upd_data); + + $json_result = IpsAndPorts::getLocal($this->getUserData(), array( + 'id' => $result['id'] + ))->get(); + $result = json_decode($json_result, true)['data']; + return $this->response(200, "successfull", $result); } } throw new Exception("Not allowed to execute given command.", 403); @@ -397,7 +402,7 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity 'id' => $id ))->get(); $result = json_decode($json_result, true)['data']; - + $result_checkdomain_stmt = Database::prepare(" SELECT `id_domain` as `id` FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_ipandports` = :id "); @@ -405,9 +410,12 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity 'id' => $id ), true, true); - if ($result_checkdomain['id'] == '') { + if (empty($result_checkdomain)) { if (! in_array($result['id'], explode(',', Settings::Get('system.defaultip'))) && ! in_array($result['id'], explode(',', Settings::Get('system.defaultsslip')))) { - + + // check whether there is the same IP with a different port + // in case this ip-address is the system.ipaddress and therefore + // when there is one - we have an alternative $result_sameipotherport_stmt = Database::prepare(" SELECT `id` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `ip` = :ip AND `id` <> :id"); @@ -417,37 +425,29 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity )); if (($result['ip'] != Settings::Get('system.ipaddress')) || ($result['ip'] == Settings::Get('system.ipaddress') && $result_sameipotherport['id'] != '')) { - $result_stmt = Database::prepare(" - SELECT `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` - WHERE `id` = :id"); - $result = Database::pexecute_first($result_stmt, array( + + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_PANEL_IPSANDPORTS . "` + WHERE `id` = :id + "); + Database::pexecute($del_stmt, array( 'id' => $id - )); - if ($result['ip'] != '') { - - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_PANEL_IPSANDPORTS . "` - WHERE `id` = :id - "); - Database::pexecute($del_stmt, array( - 'id' => $id - )); - - // also, remove connections to domains (multi-stack) - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_ipandports` = :id - "); - Database::pexecute($del_stmt, array( - 'id' => $id - )); - - inserttask('1'); - // Using nameserver, insert a task which rebuilds the server config - inserttask('4'); - - $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] deleted IP/port '" . $result['ip'] . ":" . $result['port'] . "'"); - return $this->response(200, "successfull", $result); - } + ), true, true); + + // also, remove connections to domains (multi-stack) + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_ipandports` = :id + "); + Database::pexecute($del_stmt, array( + 'id' => $id + ), true, true); + + inserttask('1'); + // Using nameserver, insert a task which rebuilds the server config + inserttask('4'); + + $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] deleted IP/port '" . $result['ip'] . ":" . $result['port'] . "'"); + return $this->response(200, "successfull", $result); } else { standard_error('cantdeletesystemip', '', true); } diff --git a/lng/english.lng.php b/lng/english.lng.php index b0f87d35..1fa929f3 100644 --- a/lng/english.lng.php +++ b/lng/english.lng.php @@ -388,7 +388,7 @@ $lng['mysql']['description'] = 'Here you can create and change your MySQL-databa $lng['serversettings']['paging']['title'] = 'Entries per page'; $lng['serversettings']['paging']['description'] = 'How many entries shall be shown on one page? (0 = disable paging)'; $lng['error']['ipstillhasdomains'] = 'The IP/Port combination you want to delete still has domains assigned to it, please reassign those to other IP/Port combinations before deleting this IP/Port combination.'; -$lng['error']['cantdeletedefaultip'] = 'You cannot delete the default reseller IP/Port combination, please make another IP/Port combination default for resellers before deleting this IP/Port combination.'; +$lng['error']['cantdeletedefaultip'] = 'You cannot delete the default IP/Port combination, please make another IP/Port combination default for before deleting this IP/Port combination.'; $lng['error']['cantdeletesystemip'] = 'You cannot delete the last system IP, either create a new IP/Port combination for the system IP or change the system IP.'; $lng['error']['myipaddress'] = '\'IP\''; $lng['error']['myport'] = '\'Port\''; From 2599f61b3213cbf3b8683e8aa0e97c963496bcd7 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 26 Feb 2018 22:35:04 +0100 Subject: [PATCH 085/746] add jenkins build.xml and more; added first unit-tests Signed-off-by: Michael Kaufmann (d00p) --- build.xml | 233 +++++++++++++++ phpcs.xml | 15 + phpdox.xml | 13 + phpmd.xml | 24 ++ phpunit.xml | 39 +++ tests/Admins/AdminsTest.php | 238 +++++++++++++++ tests/Customers/CustomersTest.php | 407 ++++++++++++++++++++++++++ tests/Ftps/FtpsTest.php | 107 +++++++ tests/Global/FroxlorRpcTest.php | 117 ++++++++ tests/IpsAndPorts/IpsAndPortsTest.php | 276 +++++++++++++++++ tests/bootstrap.php | 111 +++++++ 11 files changed, 1580 insertions(+) create mode 100644 build.xml create mode 100644 phpcs.xml create mode 100644 phpdox.xml create mode 100644 phpmd.xml create mode 100644 phpunit.xml create mode 100644 tests/Admins/AdminsTest.php create mode 100644 tests/Customers/CustomersTest.php create mode 100644 tests/Ftps/FtpsTest.php create mode 100644 tests/Global/FroxlorRpcTest.php create mode 100644 tests/IpsAndPorts/IpsAndPortsTest.php create mode 100644 tests/bootstrap.php diff --git a/build.xml b/build.xml new file mode 100644 index 00000000..99f202d8 --- /dev/null +++ b/build.xml @@ -0,0 +1,233 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 00000000..757d1e74 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,15 @@ + + + PSR2 with tabs instead of spaces. + + + + + + + + + + + + diff --git a/phpdox.xml b/phpdox.xml new file mode 100644 index 00000000..813dbd4a --- /dev/null +++ b/phpdox.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/phpmd.xml b/phpmd.xml new file mode 100644 index 00000000..0f1aae49 --- /dev/null +++ b/phpmd.xml @@ -0,0 +1,24 @@ + + + + froxlor ruleset. + + + + + + + + + + + + + + + + + diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 00000000..89f83835 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,39 @@ + + + + + + + tests/Global + tests/Admins + tests/IpsAndPorts + tests/Customers + tests/Ftps + + + + + + + + + + + + + ./lib/classes/api + + ./lib/classes/api/api_includes.inc.php + ./lib/classes/api/interface.ResourceEntity.php + + + + + diff --git a/tests/Admins/AdminsTest.php b/tests/Admins/AdminsTest.php new file mode 100644 index 00000000..15c81e3c --- /dev/null +++ b/tests/Admins/AdminsTest.php @@ -0,0 +1,238 @@ + 'reseller', + 'email' => 'testreseller@froxlor.org', + 'name' => 'Testreseller', + 'admin_password' => 'h0lYmo1y', + 'diskspace' => - 1, + 'traffic' => - 1, + 'customers' => 15, + 'domains' => 15, + 'subdomains' => 15, + 'emails' => - 1, + 'email_accounts' => 15, + 'email_forwarders' => 15, + 'email_imap' => 1, + 'email_pop3' => 0, + 'ftps' => 15, + 'tickets' => 15, + 'mysqls' => 15, + 'sendpassword' => 0, + 'phpenabled' => 1, + 'ip' => array() + ]; + + $json_result = Admins::getLocal($admin_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $admin_id = $result['adminid']; + + // get admin and check results + $json_result = Admins::getLocal($admin_userdata, array( + 'id' => $admin_id + ))->get(); + $result = json_decode($json_result, true)['data']; + + $this->assertEquals('reseller', $result['loginname']); + $this->assertEquals('testreseller@froxlor.org', $result['email']); + $this->assertEquals(0, $result['customers_see_all']); + } + + public function testAdminAdminsAddNotAllowed() + { + global $admin_userdata; + $testadmin_userdata = $admin_userdata; + $testadmin_userdata['adminsession'] = 0; + + $this->expectExceptionCode(403); + $this->expectExceptionMessage("Not allowed to execute given command."); + Admins::getLocal($testadmin_userdata, array())->add(); + } + + public function testAdminAdminsGetNotFound() + { + global $admin_userdata; + + $this->expectExceptionCode(404); + $this->expectExceptionMessage("Admin with loginname 'unknown' could not be found"); + // get unknown admin + Admins::getLocal($admin_userdata, array( + 'loginname' => 'unknown' + ))->get(); + } + + public function testAdminAdminsList() + { + global $admin_userdata; + + $json_result = Admins::getLocal($admin_userdata)->list(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(2, $result['count']); + } + + public function testResellerAdminsGet() + { + global $admin_userdata; + // get reseller + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller' + ))->get(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + + // try to read superadmin with an access-less reseller account + $this->expectException(Exception::class); + $this->expectExceptionCode(403); + + $json_result = Admins::getLocal($reseller_userdata, array( + 'loginname' => 'admin' + ))->get(); + } + + public function testResellerAdminsListNotAllowed() + { + global $admin_userdata; + // get reseller + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller' + ))->get(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + + $this->expectExceptionCode(403); + $this->expectExceptionMessage("Not allowed to execute given command."); + + Admins::getLocal($reseller_userdata)->list(); + } + + public function testAdminAdminsUnlock() + { + global $admin_userdata; + // update reseller to have correct test-data + Database::query("UPDATE `" . TABLE_PANEL_ADMINS . "` SET `loginfail_count` = '5' WHERE `loginname` = 'reseller'"); + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller' + ))->unlock(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(0, $result['loginfail_count']); + } + + public function testAdminAdminsUnlockNotAllowed() + { + global $admin_userdata; + $testadmin_userdata = $admin_userdata; + $testadmin_userdata['adminsession'] = 0; + + $this->expectExceptionCode(403); + $this->expectExceptionMessage("Not allowed to execute given command."); + Admins::getLocal($testadmin_userdata, array( + 'loginname' => 'admin' + ))->unlock(); + } + + public function testAdminAdminsDeleteMyOwn() + { + global $admin_userdata; + $this->expectExceptionMessage("You cannot delete yourself for security reasons."); + Admins::getLocal($admin_userdata, array( + 'loginname' => 'admin' + ))->delete(); + } + + public function testAdminAdminsDeleteReseller() + { + global $admin_userdata; + + // add test reseller + $data = [ + 'new_loginname' => 'resellertest', + 'email' => 'testreseller@froxlor.org', + 'name' => 'Testreseller', + 'admin_password' => 'h0lYmo1y' + ]; + + $json_result = Admins::getLocal($admin_userdata, $data)->add(); + + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'resellertest' + ))->delete(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('resellertest', $result['loginname']); + } + + public function testResellerAdminsDelete() + { + global $admin_userdata; + // get reseller + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller' + ))->get(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + + $this->expectExceptionCode(403); + $this->expectExceptionMessage("Not allowed to execute given command."); + Admins::getLocal($reseller_userdata, array( + 'loginname' => 'admin' + ))->delete(); + } + + public function testAdminAdminsEditMyOwn() + { + global $admin_userdata; + // update admin to have correct test-data + Database::query("UPDATE `" . TABLE_PANEL_ADMINS . "` SET `theme` = 'Froxlor', `def_language` = 'Deutsch' WHERE `loginname` = 'admin'"); + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'admin', + 'theme' => '', + 'def_language' => 'English' + ))->update(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('Sparkle', $result['theme']); + $this->assertEquals('English', $result['def_language']); + } + + public function testAdminAdminsEdit() + { + global $admin_userdata; + // update admin to have correct test-data + Database::query("UPDATE `" . TABLE_PANEL_ADMINS . "` SET `deactivated` = '0' WHERE `loginname` = 'reseller'"); + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller', + 'deactivated' => '1' + ))->update(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(1, $result['deactivated']); + + // reactivate + Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller', + 'deactivated' => '0' + ))->update(); + } + + public function testAdminsAdminsEditNotAllowed() + { + global $admin_userdata; + $testadmin_userdata = $admin_userdata; + $testadmin_userdata['adminsession'] = 0; + + $this->expectExceptionCode(403); + $this->expectExceptionMessage("Not allowed to execute given command."); + Admins::getLocal($testadmin_userdata, array( + 'loginname' => 'admin' + ))->update(); + } +} diff --git a/tests/Customers/CustomersTest.php b/tests/Customers/CustomersTest.php new file mode 100644 index 00000000..6e2c354c --- /dev/null +++ b/tests/Customers/CustomersTest.php @@ -0,0 +1,407 @@ + 'test1', + 'email' => 'test@froxlor.org', + 'firstname' => 'Test', + 'name' => 'Testman', + 'customernumber' => 1337, + 'diskspace' => - 1, + 'traffic' => - 1, + 'subdomains' => 15, + 'emails' => - 1, + 'email_accounts' => 15, + 'email_forwarders' => 15, + 'email_imap' => 1, + 'email_pop3' => 0, + 'ftps' => 15, + 'tickets' => 15, + 'mysqls' => 15, + 'createstdsubdomain' => 1, + 'new_customer_password' => 'h0lYmo1y', + 'sendpassword' => 1, + 'phpenabled' => 1, + 'store_defaultindex' => 1, + 'custom_notes' => 'secret', + 'custom_notes_show' => 0, + 'gender' => 5, + 'allowed_phpconfigs' => array(1) + ]; + + $json_result = Customers::getLocal($admin_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $customer_id = $result['customerid']; + + // get customer and check results + $json_result = Customers::getLocal($admin_userdata, array( + 'id' => $customer_id + ))->get(); + $result = json_decode($json_result, true)['data']; + + $this->assertEquals(1, $result['customerid']); + $this->assertEquals('test@froxlor.org', $result['email']); + $this->assertEquals(1337, $result['customernumber']); + $this->assertEquals(15, $result['subdomains']); + $this->assertEquals('secret', $result['custom_notes']); + } + + public function testAdminCustomersAddEmptyMail() + { + global $admin_userdata; + + $data = [ + 'new_loginname' => 'test2', + 'email' => ' ', + 'firstname' => 'Test2', + 'name' => 'Testman2' + ]; + + $this->expectExceptionMessage('Requested parameter "email" is empty where it should not be for "Customers:add"'); + Customers::getLocal($admin_userdata, $data)->add(); + } + + public function testAdminCustomersAddInvalidMail() + { + global $admin_userdata; + + $data = [ + 'new_loginname' => 'test2', + 'email' => 'test.froxlor.org', + 'firstname' => 'Test2', + 'name' => 'Testman2' + ]; + + $this->expectExceptionMessage("Email-address test.froxlor.org contains invalid characters or is incomplete"); + Customers::getLocal($admin_userdata, $data)->add(); + } + + /** + * @depends testAdminCustomersAdd + */ + public function testAdminCustomersList() + { + global $admin_userdata; + + $json_result = Customers::getLocal($admin_userdata)->list(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(1, $result['count']); + } + + /** + * @depends testAdminCustomersAdd + */ + public function testResellerCustomersList() + { + global $admin_userdata; + // get reseller + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller' + ))->get(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + $json_result = Customers::getLocal($reseller_userdata)->list(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(0, $result['count']); + } + + /** + * @depends testAdminCustomersAdd + */ + public function testCustomerCustomersList() + { + global $admin_userdata; + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'id' => 1 + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $this->expectExceptionCode(403); + $this->expectExceptionMessage("Not allowed to execute given command."); + + $json_result = Customers::getLocal($customer_userdata)->list(); + + } + + /** + * @depends testAdminCustomersAdd + */ + public function testCustomerCustomersGet() + { + global $admin_userdata; + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'id' => 1 + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $json_result = Customers::getLocal($customer_userdata, array( + 'id' => $customer_userdata['customerid'] + ))->get(); + $result = json_decode($json_result, true)['data']; + + $this->assertEquals(1, $result['customerid']); + $this->assertEquals('test@froxlor.org', $result['email']); + $this->assertEquals(1337, $result['customernumber']); + $this->assertEquals(15, $result['subdomains']); + $this->assertEquals('Sparkle', $result['theme']); + $this->assertEquals('', $result['custom_notes']); + } + + public function testAdminCustomersGetNotFound() + { + global $admin_userdata; + $this->expectExceptionCode(404); + $this->expectExceptionMessage("Customer with id #999 could not be found"); + Customers::getLocal($admin_userdata, array( + 'id' => 999 + ))->get(); + } + + /** + * @depends testAdminCustomersAdd + */ + public function testCustomerCustomersGetForeign() + { + global $admin_userdata; + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'id' => 1 + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $this->expectException(Exception::class); + $this->expectExceptionCode(401); + + Customers::getLocal($customer_userdata, array( + 'id' => 2 + ))->get(); + } + + /** + * @depends testAdminCustomersAdd + */ + public function testAdminCustomerUpdateDeactivate() + { + global $admin_userdata; + // get customer + Customers::getLocal($admin_userdata, array( + 'id' => 1, + 'deactivated' => 1 + ))->update(); + + // get customer and check results + $json_result = Customers::getLocal($admin_userdata, array( + 'id' => 1 + ))->get(); + $result = json_decode($json_result, true)['data']; + + $this->assertEquals(1, $result['customerid']); + $this->assertEquals(1, $result['deactivated']); + // standard-subdomains will be removed on deactivation + $this->assertEquals(0, $result['standardsubdomain']); + } + + /** + * @depends testAdminCustomersAdd + */ + public function testCustomerCustomersGetWhenDeactivated() + { + global $admin_userdata; + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'id' => 1 + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $this->expectException(Exception::class); + $this->expectExceptionCode(406); + $this->expectExceptionMessage("Account suspended"); + + Customers::getLocal($customer_userdata, array( + 'id' => $customer_userdata['customerid'] + ))->get(); + } + + /** + * @depends testAdminCustomersAdd + */ + public function testCustomerCustomersUpdate() + { + global $admin_userdata; + + // reactivate customer + // get customer + Customers::getLocal($admin_userdata, array( + 'id' => 1, + 'deactivated' => 0 + ))->update(); + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'id' => 1 + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + Customers::getLocal($customer_userdata, array( + 'id' => $customer_userdata['customerid'], + 'def_language' => 'English', + 'theme' => 'Froxlor', + 'new_customer_password' => 'h0lYmo1y2' + ))->update(); + + $json_result = Customers::getLocal($customer_userdata, array( + 'id' => $customer_userdata['customerid'] + ))->get(); + $result = json_decode($json_result, true)['data']; + + $this->assertEquals('Froxlor', $result['theme']); + $this->assertEquals('English', $result['def_language']); + } + + /** + * @depends testAdminCustomersAdd + */ + public function testResellerCustomersUpdateAllocateMore() + { + global $admin_userdata; + // get reseller + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller' + ))->get(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + + $this->expectExceptionMessage("You cannot allocate more resources than you own for yourself."); + // add new customer + $data = [ + 'new_loginname' => 'test2', + 'email' => 'test2@froxlor.org', + 'firstname' => 'Test', + 'name' => 'Testman', + 'customernumber' => 1338, + 'subdomains' => -1, + 'new_customer_password' => 'h0lYmo1y' + ]; + Customers::getLocal($reseller_userdata, $data)->add(); + } + + public function testCustomerCustomersDelete() + { + global $admin_userdata; + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $this->expectExceptionCode(403); + $this->expectExceptionMessage("Not allowed to execute given command."); + Customers::getLocal($customer_userdata, array('loginname' => 'test1'))->delete(); + } + + public function testResellerCustomersDeleteNotOwned() + { + global $admin_userdata; + // get reseller + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller' + ))->get(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + $this->expectExceptionCode(404); + Customers::getLocal($reseller_userdata, array('loginname' => 'test1'))->delete(); + } + + public function testAdminCustomersDelete() + { + global $admin_userdata; + // add new customer + $data = [ + 'new_loginname' => 'test2', + 'email' => 'test2@froxlor.org', + 'firstname' => 'Test', + 'name' => 'Testman', + 'customernumber' => 1338, + 'new_customer_password' => 'h0lYmo1y' + ]; + Customers::getLocal($admin_userdata, $data)->add(); + $json_result = Customers::getLocal($admin_userdata, array('loginname' => 'test2'))->delete(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('test2', $result['loginname']); + } + + public function testAdminCustomersUnlock() + { + global $admin_userdata; + // update customer to have correct test-data + Database::query("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `loginfail_count` = '5' WHERE `loginname` = 'test1'"); + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->unlock(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(0, $result['loginfail_count']); + } + + public function testAdminCustomersUnlockNotAllowed() + { + global $admin_userdata; + $testadmin_userdata = $admin_userdata; + $testadmin_userdata['adminsession'] = 0; + + $this->expectExceptionCode(403); + $this->expectExceptionMessage("Not allowed to execute given command."); + Customers::getLocal($testadmin_userdata, array( + 'loginname' => 'test1' + ))->unlock(); + } + + public function testAdminCustomersMoveNotAllowed() + { + global $admin_userdata; + $testadmin_userdata = $admin_userdata; + $testadmin_userdata['change_serversettings'] = 0; + + $this->expectExceptionCode(403); + $this->expectExceptionMessage("Not allowed to execute given command."); + Customers::getLocal($testadmin_userdata, array( + 'loginname' => 'test1', + 'adminid' => 1 + ))->move(); + } + + public function testAdminCustomersMoveTargetIsSource() + { + global $admin_userdata; + $this->expectExceptionCode(406); + $this->expectExceptionMessage("Cannot move customer to the same admin/reseller as he currently is assigned to"); + Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1', + 'adminid' => 1 + ))->move(); + } + + public function testAdminCustomersMove() + { + global $admin_userdata; + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1', + 'adminid' => 2 + ))->move(); + $result = json_decode($json_result, true)['data']; + + $this->assertEquals(2, $result['adminid']); + } + +} diff --git a/tests/Ftps/FtpsTest.php b/tests/Ftps/FtpsTest.php new file mode 100644 index 00000000..2b2165ee --- /dev/null +++ b/tests/Ftps/FtpsTest.php @@ -0,0 +1,107 @@ + 1 + ))->get(); + $result = json_decode($json_result, true)['data']; + + // should be the ftp user of the first added customr 'test1' + $this->assertEquals('test1', $result['username']); + } + + public function testCustomerFtpsGetId() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'id' => 1 + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $json_result = Ftps::getLocal($customer_userdata, array( + 'id' => 1 + ))->get(); + $result = json_decode($json_result, true)['data']; + + // should be the ftp user of the first added customr 'test1' + $this->assertEquals('test1', $result['username']); + } + + public function testCustomerFtpsGetOtherId() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'id' => 1 + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $this->expectExceptionCode(404); + + Ftps::getLocal($customer_userdata, array( + 'id' => 10 + ))->get(); + } + + public function testAdminFtpsList() + { + global $admin_userdata; + + $json_result = Ftps::getLocal($admin_userdata)->list(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(1, $result['count']); + } + + public function testAdminFtpsListSpecificCustomer() + { + global $admin_userdata; + + $json_result = Ftps::getLocal($admin_userdata, array('loginname' => 'test1'))->list(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(1, $result['count']); + $this->assertEquals('test1', $result['list'][0]['username']); + } + + public function testResellerFtpsList() + { + global $admin_userdata; + // get reseller + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller' + ))->get(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + $json_result = Ftps::getLocal($reseller_userdata)->list(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(1, $result['count']); + $this->assertEquals('test1', $result['list'][0]['username']); + } + + public function testCustomerFtpsList() + { + global $admin_userdata; + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $json_result = Ftps::getLocal($customer_userdata)->list(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(1, $result['count']); + $this->assertEquals('test1', $result['list'][0]['username']); + } +} diff --git a/tests/Global/FroxlorRpcTest.php b/tests/Global/FroxlorRpcTest.php new file mode 100644 index 00000000..6befd9c4 --- /dev/null +++ b/tests/Global/FroxlorRpcTest.php @@ -0,0 +1,117 @@ +expectExceptionCode(400); + $this->expectExceptionMessage("Invalid request header"); + FroxlorRPC::validateRequest(array()); + } + + public function testNoCredentialsGiven() + { + $this->expectExceptionCode(400); + $this->expectExceptionMessage("No authorization credentials given"); + FroxlorRPC::validateRequest(array( + 'header' => 'asd' + )); + } + + public function testValidateAuthInvalid() + { + $this->expectExceptionCode(403); + $this->expectExceptionMessage("Invalid authorization credentials"); + FroxlorRPC::validateRequest(array( + 'header' => [ + 'apikey' => 'asd', + 'secret' => 'asd' + ] + )); + } + + public function testValidateAuthAllowFromInvalid() + { + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + Database::query("UPDATE `api_keys` SET `allowed_from` = '123.123.123.123';"); + $this->expectExceptionCode(403); + $this->expectExceptionMessage("Invalid authorization credentials"); + FroxlorRPC::validateRequest(array( + 'header' => [ + 'apikey' => 'test', + 'secret' => 'test' + ] + )); + } + + public function testInvalidRequestBody() + { + Database::query("UPDATE `api_keys` SET `allowed_from` = '';"); + $this->expectExceptionCode(400); + $this->expectExceptionMessage("Invalid request body"); + FroxlorRPC::validateRequest(array( + 'header' => [ + 'apikey' => 'test', + 'secret' => 'test' + ] + )); + } + + public function testNoCommandGiven() + { + $this->expectExceptionCode(400); + $this->expectExceptionMessage("No command given"); + FroxlorRPC::validateRequest(array( + 'header' => [ + 'apikey' => 'test', + 'secret' => 'test' + ], + 'body' => 'asd' + )); + } + + public function testInvalidCommandGiven() + { + $this->expectExceptionCode(400); + $this->expectExceptionMessage("Invalid command"); + FroxlorRPC::validateRequest(array( + 'header' => [ + 'apikey' => 'test', + 'secret' => 'test' + ], + 'body' => ['command' => 'Froxlor'] + )); + } + + public function testUnknownCommandGiven() + { + $this->expectExceptionCode(400); + $this->expectExceptionMessage("Unknown command"); + FroxlorRPC::validateRequest(array( + 'header' => [ + 'apikey' => 'test', + 'secret' => 'test' + ], + 'body' => ['command' => 'SomeModule.cmd'] + )); + } + + public function testCommandOk() + { + $result = FroxlorRPC::validateRequest(array( + 'header' => [ + 'apikey' => 'test', + 'secret' => 'test' + ], + 'body' => ['command' => 'Froxlor.listFunctions'] + )); + $this->assertEquals('Froxlor', $result['command']['class']); + $this->assertEquals('listFunctions', $result['command']['method']); + $this->assertNull($result['params']); + } +} diff --git a/tests/IpsAndPorts/IpsAndPortsTest.php b/tests/IpsAndPorts/IpsAndPortsTest.php new file mode 100644 index 00000000..95590a17 --- /dev/null +++ b/tests/IpsAndPorts/IpsAndPortsTest.php @@ -0,0 +1,276 @@ +list(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(1, $result['count']); + $this->assertEquals('82.149.225.46', $result['list'][0]['ip']); + } + + public function testResellerIpsAndPortsListHasNone() + { + global $admin_userdata; + // update reseller to allow no ip access + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller', + 'ipaddress' => array() + ))->update(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + $this->expectExceptionCode(403); + $this->expectExceptionMessage("Not allowed to execute given command."); + $json_result = IpsAndPorts::getLocal($reseller_userdata)->list(); + } + + public function testAdminIpsAndPortsAdd() + { + global $admin_userdata; + $data = [ + 'ip' => '82.149.225.47' + ]; + $json_result = IpsAndPorts::getLocal($admin_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(2, $result['id']); + $this->assertEquals(80, $result['port']); + } + + /** + * @depends testAdminIpsAndPortsAdd + */ + public function testAdminIpsAndPortsAddExists() + { + global $admin_userdata; + $this->expectExceptionMessage("This IP/Port combination already exists."); + $data = [ + 'ip' => '82.149.225.47' + ]; + IpsAndPorts::getLocal($admin_userdata, $data)->add(); + } + + public function testAdminIpsAndPortsAddIpv6() + { + global $admin_userdata; + $data = [ + 'ip' => '2a01:440:1:12:82:149:225:46', + 'docroot' => '/var/www/html' + ]; + $json_result = IpsAndPorts::getLocal($admin_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(3, $result['id']); + $this->assertEquals('/var/www/html/', $result['docroot']); + } + + public function testAdminIpsAndPortsGetNotFound() + { + global $admin_userdata; + $this->expectExceptionCode(404); + $this->expectExceptionMessage("IP/port with id #999 could not be found"); + IpsAndPorts::getLocal($admin_userdata, array('id' => 999))->get(); + } + + /** + * @depends testAdminIpsAndPortsAdd + */ + public function testResellerIpsAndPortsList() + { + global $admin_userdata; + // update reseller to allow ip access to ip id #2 + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller', + 'ipaddress' => array(2) + ))->update(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + $json_result = IpsAndPorts::getLocal($reseller_userdata)->list(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(1, $result['count']); + $this->assertEquals('82.149.225.47', $result['list'][0]['ip']); + } + + /** + * @depends testResellerIpsAndPortsList + */ + public function testResellerIpsAndPortsGet() + { + global $admin_userdata; + // update reseller to allow ip access to ip id #2 + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller' + ))->get(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + $json_result = IpsAndPorts::getLocal($reseller_userdata, array('id' => 2))->get(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('82.149.225.47', $result['ip']); + } + + /** + * @depends testResellerIpsAndPortsList + */ + public function testResellerIpsAndPortsGetRestrictedNotOwned() + { + global $admin_userdata; + // update reseller to allow ip access to ip id #2 + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller' + ))->get(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + $this->expectExceptionCode(405); + $this->expectExceptionMessage("You cannot access this resource"); + IpsAndPorts::getLocal($reseller_userdata, array('id' => 1))->get(); + } + + public function testResellerIpsAndPortsAdd() + { + global $admin_userdata; + // update reseller to allow ip access to ip id #2 + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller' + ))->get(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + $this->expectExceptionCode(403); + $this->expectExceptionMessage("Not allowed to execute given command."); + $data = [ + 'ip' => '82.149.225.48' + ]; + IpsAndPorts::getLocal($reseller_userdata, $data)->add(); + } + + public function testCustomerIpsAndPortsGetNotAllowed() + { + global $admin_userdata; + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $this->expectExceptionCode(403); + $this->expectExceptionMessage("Not allowed to execute given command."); + IpsAndPorts::getLocal($customer_userdata, array('id' => 1))->get(); + } + + public function testAdminIpsAndPortsEdit() + { + global $admin_userdata; + $data = [ + 'id' => 1, + 'listen_statement' => 1, + 'docroot' => '/var/www/html' + ]; + $json_result = IpsAndPorts::getLocal($admin_userdata, $data)->update(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(1, $result['listen_statement']); + $this->assertEquals('/var/www/html/', $result['docroot']); + } + + public function testResellerIpsAndPortsEditNotAllowed() + { + global $admin_userdata; + // get reseller + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller' + ))->get(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + $data = [ + 'id' => 1, + 'listen_statement' => 0 + ]; + $this->expectExceptionCode(405); + $this->expectExceptionMessage("You cannot access this resource"); + IpsAndPorts::getLocal($reseller_userdata, $data)->update(); + } + + public function testCustomerIpsAndPortsEditNotAllowed() + { + global $admin_userdata; + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $data = [ + 'id' => 1, + 'listen_statement' => 0 + ]; + $this->expectExceptionCode(403); + $this->expectExceptionMessage("Not allowed to execute given command."); + IpsAndPorts::getLocal($customer_userdata, $data)->update(); + } + + public function testAdminIpsAndPortsEditCantChangeSystemIp() + { + global $admin_userdata; + $data = [ + 'id' => 1, + 'ip' => '123.123.123.123' + ]; + $this->expectExceptionMessage("You cannot change the last system IP, either create another new IP/Port combination for the system IP or change the system IP."); + $json_result = IpsAndPorts::getLocal($admin_userdata, $data)->update(); + } + + public function testResellerIpsAndPortsEditNoDuplicate() + { + global $admin_userdata; + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller' + ))->get(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + $data = [ + 'id' => 2, + 'ip' => '82.149.225.46' + ]; + $this->expectExceptionMessage("This IP/Port combination already exists."); + IpsAndPorts::getLocal($reseller_userdata, $data)->update(); + } + + public function testAdminIpsAndPortsDeleteCantDeleteDefaultIp() + { + global $admin_userdata; + $data = [ + 'id' => 1 + ]; + $this->expectExceptionMessage("You cannot delete the default IP/Port combination, please make another IP/Port combination default for before deleting this IP/Port combination."); + IpsAndPorts::getLocal($admin_userdata, $data)->delete(); + } + + public function testAdminIpsAndPortsDelete() + { + global $admin_userdata; + $data = [ + 'id' => 2 + ]; + $json_result = IpsAndPorts::getLocal($admin_userdata, $data)->delete(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('82.149.225.47', $result['ip']); + } + + public function testResellerIpsAndPortsDeleteNotAllowed() + { + global $admin_userdata; + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller' + ))->get(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + $data = [ + 'id' => 1 + ]; + $this->expectExceptionCode(403); + $this->expectExceptionMessage("Not allowed to execute given command."); + IpsAndPorts::getLocal($reseller_userdata, $data)->delete(); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 00000000..31001195 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,111 @@ + Date: Mon, 26 Feb 2018 22:39:06 +0100 Subject: [PATCH 086/746] use correct order Signed-off-by: Michael Kaufmann (d00p) --- phpunit.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit.xml b/phpunit.xml index 89f83835..a924f199 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -10,8 +10,8 @@ tests/Global tests/Admins - tests/IpsAndPorts tests/Customers + tests/IpsAndPorts tests/Ftps From 309b70c0f84db9eac80ac4f294fe7da3133a6901 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 27 Feb 2018 08:23:19 +0100 Subject: [PATCH 087/746] completed Ftps-ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- customer_ftp.php | 71 +--------- lib/classes/api/commands/class.Ftps.php | 123 ++++++++++++++-- lib/classes/api/commands/class.Mysqls.php | 1 - tests/Ftps/FtpsTest.php | 163 ++++++++++++++++++++++ 4 files changed, 281 insertions(+), 77 deletions(-) diff --git a/customer_ftp.php b/customer_ftp.php index 474ea057..b2131b07 100644 --- a/customer_ftp.php +++ b/customer_ftp.php @@ -171,74 +171,11 @@ if ($page == 'overview') { if (isset($result['username']) && $result['username'] != '') { if (isset($_POST['send']) && $_POST['send'] == 'send') { - // @FIXME use a good path-validating regex here (refs #1231) - $path = validate($_POST['path'], 'path'); - - $shell = "/bin/false"; - if (Settings::Get('system.allow_customer_shell') == '1') { - $shell = isset($_POST['shell']) ? validate($_POST['shell'], 'shell') : '/bin/false'; + try { + Ftps::getLocal($userinfo, $_POST)->update(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - - $_setnewpass = false; - if (isset($_POST['ftp_password']) && $_POST['ftp_password'] != '') { - $password = validate($_POST['ftp_password'], 'password'); - $password = validatePassword($password); - $_setnewpass = true; - } - - if ($_setnewpass) { - if ($password == '') { - standard_error(array('stringisempty', 'mypassword')); - } elseif ($result['username'] == $password) { - standard_error('passwordshouldnotbeusername'); - } - $log->logAction(USR_ACTION, LOG_INFO, "updated ftp-account password for '" . $result['username'] . "'"); - $cryptPassword = makeCryptPassword($password); - - $stmt = Database::prepare("UPDATE `" . TABLE_FTP_USERS . "` - SET `password` = :password - WHERE `customerid` = :customerid - AND `id` = :id" - ); - Database::pexecute($stmt, array("customerid" => $userinfo['customerid'], "id" => $id, "password" => $cryptPassword)); - } - - if ($path != '') { - $path = makeCorrectDir($userinfo['documentroot'] . '/' . $path); - - if ($path != $result['homedir']) { - if (!file_exists($path)) { - // it's the task for "new ftp" but that will - // create all directories and correct their permissions - inserttask(5); - } - - $log->logAction(USR_ACTION, LOG_INFO, "updated ftp-account homdir for '" . $result['username'] . "'"); - - $stmt = Database::prepare("UPDATE `" . TABLE_FTP_USERS . "` - SET `homedir` = :homedir - WHERE `customerid` = :customerid - AND `id` = :id" - ); - $params = array( - "homedir" => $path, - "customerid" => $userinfo['customerid'], - "id" => $id - ); - Database::pexecute($stmt, $params); - } - } - - $log->logAction(USR_ACTION, LOG_INFO, "edited ftp-account '" . $result['username'] . "'"); - inserttask(5); - $description = validate($_POST['ftp_description'], 'description'); - $stmt = Database::prepare("UPDATE `" . TABLE_FTP_USERS . "` - SET `description` = :desc, `shell` = :shell - WHERE `customerid` = :customerid - AND `id` = :id" - ); - Database::pexecute($stmt, array("desc" => $description, "shell" => $shell, "customerid" => $userinfo['customerid'], "id" => $id)); - redirectTo($filename, array('page' => $page, 's' => $s)); } else { if (strpos($result['homedir'], $userinfo['documentroot']) === 0) { diff --git a/lib/classes/api/commands/class.Ftps.php b/lib/classes/api/commands/class.Ftps.php index 226de773..a723080e 100644 --- a/lib/classes/api/commands/class.Ftps.php +++ b/lib/classes/api/commands/class.Ftps.php @@ -80,7 +80,7 @@ class Ftps extends ApiCommand implements ResourceEntity } $params = array(); - // get needed customer info to reduce the mysql-usage-counter by one + // get needed customer info to reduce the ftp-user-counter by one if ($this->isAdmin()) { // get customer id $customer_id = $this->getParam('customer_id'); @@ -88,7 +88,7 @@ class Ftps extends ApiCommand implements ResourceEntity 'id' => $customer_id ))->get(); $customer = json_decode($json_result, true)['data']; - // check whether the customer has enough resources to get the database added + // check whether the customer has enough resources to get the ftp-user added if ($customer['ftps_used'] >= $customer['ftps'] && $customer['ftps'] != '-1') { throw new Exception("Customer has no more resources available", 406); } @@ -133,11 +133,6 @@ class Ftps extends ApiCommand implements ResourceEntity if (! empty($username_check) && $username_check['username'] = $username) { standard_error('usernamealreadyexists', $username, true); - } elseif ($password == '') { - standard_error(array( - 'stringisempty', - 'mypassword' - ), '', true); } elseif ($username == $password) { standard_error('passwordshouldnotbeusername', '', true); } else { @@ -346,7 +341,117 @@ class Ftps extends ApiCommand implements ResourceEntity } public function update() - {} + { + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'ftp')) { + throw new Exception("You cannot access this resource", 405); + } + + $id = $this->getParam('id', true, 0); + $un_optional = ($id <= 0 ? false : true); + $username = $this->getParam('username', $un_optional, ''); + + $json_result = Ftps::getLocal($this->getUserData(), array( + 'id' => $id, + 'username' => $username + ))->get(); + $result = json_decode($json_result, true)['data']; + $id = $result['id']; + + // parameters + $path = $this->getParam('path', true, ''); + $password = $this->getParam('ftp_password', true, ''); + $description = $this->getParam('ftp_description', true, $result['description']); + $shell = $this->getParam('shell', true, $result['shell']); + + // validation + $password = validate($password, 'password', '', '', array(), true); + $description = validate(trim($description), 'description', '', '', array(), true); + + if (Settings::Get('system.allow_customer_shell') == '1') { + $shell = validate(trim($shell), 'shell', '', '', array(), true); + } else { + $shell = "/bin/false"; + } + + $params = array(); + // get needed customer info to reduce the ftp-user-counter by one + if ($this->isAdmin()) { + // get customer id + $customer_id = $this->getParam('customer_id'); + $json_result = Customers::getLocal($this->getUserData(), array( + 'id' => $customer_id + ))->get(); + $customer = json_decode($json_result, true)['data']; + } else { + $customer_id = $this->getUserDetail('customerid'); + $customer = $this->getUserData(); + } + + // password update? + if ($password != '') { + // validate password + $password = validatePassword($password, true); + + if ($password == $result['username']) { + standard_error('passwordshouldnotbeusername', '', true); + } + $cryptPassword = makeCryptPassword($password); + + $stmt = Database::prepare("UPDATE `" . TABLE_FTP_USERS . "` + SET `password` = :password + WHERE `customerid` = :customerid + AND `id` = :id + "); + Database::pexecute($stmt, array( + "customerid" => $customer['customerid'], + "id" => $id, + "password" => $cryptPassword + ), true, true); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] updated ftp-account password for '" . $result['username'] . "'"); + } + + // path update? + if ($path != '') { + $path = makeCorrectDir($customer['documentroot'] . '/' . $path); + + if ($path != $result['homedir']) { + $stmt = Database::prepare("UPDATE `" . TABLE_FTP_USERS . "` + SET `homedir` = :homedir + WHERE `customerid` = :customerid + AND `id` = :id + "); + Database::pexecute($stmt, array( + "homedir" => $path, + "customerid" => $customer['customerid'], + "id" => $id + ), true, true); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] updated ftp-account homdir for '" . $result['username'] . "'"); + } + } + // it's the task for "new ftp" but that will + // create all directories and correct their permissions + inserttask(5); + + $stmt = Database::prepare(" + UPDATE `" . TABLE_FTP_USERS . "` + SET `description` = :desc, `shell` = :shell + WHERE `customerid` = :customerid + AND `id` = :id + "); + Database::pexecute($stmt, array( + "desc" => $description, + "shell" => $shell, + "customerid" => $customer['customerid'], + "id" => $id + ), true, true); + + $json_result = Ftps::getLocal($this->getUserData(), array( + 'username' => $result['username'] + ))->get(); + $result = json_decode($json_result, true)['data']; + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_NOTICE, "[API] updated ftp-user '" . $result['username'] . "'"); + return $this->response(200, "successfull", $result); + } /** * list all ftp-users, if called from an admin, list all ftp-users of all customers you are allowed to view, or specify id or loginname for one specific customer @@ -496,7 +601,7 @@ class Ftps extends ApiCommand implements ResourceEntity "username" => "," . $result['username'], "customerid" => $customer_data['customerid'] ), true, true); - + // refs #293 if ($delete_userfiles == 1) { inserttask('8', $customer_data['loginname'], $result['homedir']); diff --git a/lib/classes/api/commands/class.Mysqls.php b/lib/classes/api/commands/class.Mysqls.php index 5811477f..0d043d6d 100644 --- a/lib/classes/api/commands/class.Mysqls.php +++ b/lib/classes/api/commands/class.Mysqls.php @@ -363,7 +363,6 @@ class Mysqls extends ApiCommand implements ResourceEntity // validation $password = validate($password, 'password', '', '', array(), true); - $password = validatePassword($password, true); $databasedescription = validate(trim($databasedescription), 'description', '', '', array(), true); // validate whether the dbserver exists diff --git a/tests/Ftps/FtpsTest.php b/tests/Ftps/FtpsTest.php index 2b2165ee..d9d185a3 100644 --- a/tests/Ftps/FtpsTest.php +++ b/tests/Ftps/FtpsTest.php @@ -21,6 +21,22 @@ class FtpsTest extends TestCase $this->assertEquals('test1', $result['username']); } + public function testResellerFtpsGetId() + { + global $admin_userdata; + // get reseller + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller' + ))->get(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + $json_result = Ftps::getLocal($reseller_userdata, array( + 'id' => 1 + ))->get(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('test1', $result['username']); + } + public function testCustomerFtpsGetId() { global $admin_userdata; @@ -104,4 +120,151 @@ class FtpsTest extends TestCase $this->assertEquals(1, $result['count']); $this->assertEquals('test1', $result['list'][0]['username']); } + + public function testCustomerFtpsAdd() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'ftp_password' => 'h4xXx0r', + 'path' => '/', + 'ftp_description' => 'testing', + 'sendinfomail' => 1 + ]; + $json_result = Ftps::getLocal($customer_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals($customer_userdata['documentroot'], $result['homedir']); + } + + public function testCustomerFtpsAddNoMoreResources() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data'];# + + $customer_userdata['ftps_used'] = 100; + + $this->expectExceptionCode(406); + $this->expectExceptionMessage('No more resources available'); + $json_result = Ftps::getLocal($customer_userdata)->add(); + } + + public function testAdminFtpsAddCustomerRequired() + { + global $admin_userdata; + + $data = [ + 'ftp_password' => 'h4xXx0r', + 'path' => '/', + 'ftp_description' => 'testing', + 'sendinfomail' => 1 + ]; + + $this->expectExceptionCode(404); + $this->expectExceptionMessage('Requested parameter "customer_id" could not be found for "Ftps:add"'); + $json_result = Ftps::getLocal($admin_userdata, $data)->add(); + } + + public function testCustomerFtpsEdit() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'username' => 'test1ftp1', + 'ftp_password' => 'h4xXx0r2', + 'path' => '/subfolder', + 'ftp_description' => 'testing2' + ]; + $json_result = Ftps::getLocal($customer_userdata, $data)->update(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals($customer_userdata['documentroot'].'subfolder/', $result['homedir']); + $this->assertEquals('testing2', $result['description']); + } + + public function testAdminFtpsEdit() + { + global $admin_userdata; + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $data = [ + 'username' => 'test1ftp1', + 'customer_id' => 1, + 'ftp_password' => 'h4xXx0r2', + 'path' => '/anotherfolder', + 'ftp_description' => 'testing3' + ]; + $json_result = Ftps::getLocal($admin_userdata, $data)->update(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals($customer_userdata['documentroot'].'anotherfolder/', $result['homedir']); + $this->assertEquals('testing3', $result['description']); + } + + public function testAdminFtpsAdd() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'customer_id' => $customer_userdata['customerid'], + 'ftp_password' => 'h4xXx0r', + 'path' => '/', + 'ftp_description' => 'testing', + 'sendinfomail' => 1 + ]; + $json_result = Ftps::getLocal($admin_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals($customer_userdata['documentroot'], $result['homedir']); + } + + public function testCustomerFtpsDelete() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $data = [ + 'username' => 'test1ftp1' + ]; + $json_result = Ftps::getLocal($customer_userdata, $data)->delete(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('test1ftp1', $result['username']); + } + + public function testAdminFtpsDelete() + { + global $admin_userdata; + $data = [ + 'username' => 'test1ftp2' + ]; + $json_result = Ftps::getLocal($admin_userdata, $data)->delete(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('test1ftp2', $result['username']); + } } From a2172329cd731cf97a35c6c5beaee5b7cf2ae8d5 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Tue, 27 Feb 2018 08:50:54 +0100 Subject: [PATCH 088/746] Update README.md added CI build-status --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 33cb17cb..87c802ec 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Build Status](http://services.nutime.de:8081/job/froxlor-0.10/lastBuild/buildStatus)](http://services.nutime.de:8081/job/froxlor-0.10/lastBuild) + # Froxlor The server administration software for your needs. From b46d3a3769315ac55fe672b7246d2d4f17a8b5d4 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Tue, 27 Feb 2018 08:59:49 +0100 Subject: [PATCH 089/746] Update README.md new test :) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 87c802ec..ffb2726e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](http://services.nutime.de:8081/job/froxlor-0.10/lastBuild/buildStatus)](http://services.nutime.de:8081/job/froxlor-0.10/lastBuild) +[![Build Status](http://services.nutime.de:8081/job/froxlor-0.10/badge/icon)](http://services.nutime.de:8081/job/froxlor-0.10/) # Froxlor From b097c19c0aa807f78d20bcc3b590fd8b4c27390b Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 27 Feb 2018 09:30:57 +0100 Subject: [PATCH 090/746] correct phpcs config Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Domains.php | 2 +- lib/classes/api/commands/class.Froxlor.php | 23 +++++++----- phpcs.xml | 11 +++--- tests/Customers/CustomersTest.php | 41 +++++++++++++--------- 4 files changed, 43 insertions(+), 34 deletions(-) diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index c9539d6d..b3e433e3 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -1555,7 +1555,7 @@ class Domains extends ApiCommand implements ResourceEntity // trigger when domain id for alias destination has changed: both for old and new destination triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $this->logger()); triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $this->logger()); - } else if ($result['wwwserveralias'] != $wwwserveralias || $result['letsencrypt'] != $letsencrypt) { + } elseif ($result['wwwserveralias'] != $wwwserveralias || $result['letsencrypt'] != $letsencrypt) { // or when wwwserveralias or letsencrypt was changed triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $this->logger()); } diff --git a/lib/classes/api/commands/class.Froxlor.php b/lib/classes/api/commands/class.Froxlor.php index 2f61e20c..d10a96b5 100644 --- a/lib/classes/api/commands/class.Froxlor.php +++ b/lib/classes/api/commands/class.Froxlor.php @@ -83,20 +83,24 @@ class Froxlor extends ApiCommand /** * * @todo import settings - * + * * @access admin */ public function importSettings() - {} + { + throw new Exception("Not available yet.", 501); + } /** * * @todo export settings to file - * + * * @access admin */ public function exportSettings() - {} + { + throw new Exception("Not available yet.", 501); + } /** * return a list of all settings @@ -202,7 +206,7 @@ class Froxlor extends ApiCommand array_push($functions, array_merge(array( 'module' => $module, 'function' => $func->name - ), $this->_getParamListFromDoc($module, $func->name))); + ), $this->getParamListFromDoc($module, $func->name))); } } } else { @@ -234,7 +238,7 @@ class Froxlor extends ApiCommand array_push($functions, array_merge(array( 'module' => $matches[1], 'function' => $func->name - ), $this->_getParamListFromDoc($matches[1], $func->name))); + ), $this->getParamListFromDoc($matches[1], $func->name))); } } } @@ -259,7 +263,7 @@ class Froxlor extends ApiCommand * @throws Exception * @return array|bool */ - private function _getParamListFromDoc($module = null, $function = null) + private function getParamListFromDoc($module = null, $function = null) { try { // set the module @@ -307,7 +311,8 @@ class Froxlor extends ApiCommand 'type' => $r[1], 'desc' => (isset($r[2]) ? trim($r[2]) : '') ); - } else if (! empty($c) && strpos($c, '@throws') === false) { + } + elseif (! empty($c) && strpos($c, '@throws') === false) { if (substr($c, 0, 3) == "/**") { continue; } @@ -331,7 +336,7 @@ class Froxlor extends ApiCommand } } } - $result['head'] =trim($result['head']); + $result['head'] = trim($result['head']); return $result; } catch (\ReflectionException $e) { return array(); diff --git a/phpcs.xml b/phpcs.xml index 757d1e74..e2a1b208 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -1,15 +1,12 @@ PSR2 with tabs instead of spaces. + + - - - - - - - + + diff --git a/tests/Customers/CustomersTest.php b/tests/Customers/CustomersTest.php index 6e2c354c..c90dd6c4 100644 --- a/tests/Customers/CustomersTest.php +++ b/tests/Customers/CustomersTest.php @@ -37,7 +37,9 @@ class CustomersTest extends TestCase 'custom_notes' => 'secret', 'custom_notes_show' => 0, 'gender' => 5, - 'allowed_phpconfigs' => array(1) + 'allowed_phpconfigs' => array( + 1 + ) ]; $json_result = Customers::getLocal($admin_userdata, $data)->add(); @@ -67,7 +69,7 @@ class CustomersTest extends TestCase 'firstname' => 'Test2', 'name' => 'Testman2' ]; - + $this->expectExceptionMessage('Requested parameter "email" is empty where it should not be for "Customers:add"'); Customers::getLocal($admin_userdata, $data)->add(); } @@ -127,12 +129,11 @@ class CustomersTest extends TestCase 'id' => 1 ))->get(); $customer_userdata = json_decode($json_result, true)['data']; - + $this->expectExceptionCode(403); $this->expectExceptionMessage("Not allowed to execute given command."); - + $json_result = Customers::getLocal($customer_userdata)->list(); - } /** @@ -248,7 +249,7 @@ class CustomersTest extends TestCase 'id' => 1, 'deactivated' => 0 ))->update(); - + // get customer $json_result = Customers::getLocal($admin_userdata, array( 'id' => 1 @@ -283,7 +284,7 @@ class CustomersTest extends TestCase ))->get(); $reseller_userdata = json_decode($json_result, true)['data']; $reseller_userdata['adminsession'] = 1; - + $this->expectExceptionMessage("You cannot allocate more resources than you own for yourself."); // add new customer $data = [ @@ -292,12 +293,12 @@ class CustomersTest extends TestCase 'firstname' => 'Test', 'name' => 'Testman', 'customernumber' => 1338, - 'subdomains' => -1, + 'subdomains' => - 1, 'new_customer_password' => 'h0lYmo1y' ]; Customers::getLocal($reseller_userdata, $data)->add(); } - + public function testCustomerCustomersDelete() { global $admin_userdata; @@ -308,7 +309,9 @@ class CustomersTest extends TestCase $customer_userdata = json_decode($json_result, true)['data']; $this->expectExceptionCode(403); $this->expectExceptionMessage("Not allowed to execute given command."); - Customers::getLocal($customer_userdata, array('loginname' => 'test1'))->delete(); + Customers::getLocal($customer_userdata, array( + 'loginname' => 'test1' + ))->delete(); } public function testResellerCustomersDeleteNotOwned() @@ -321,7 +324,9 @@ class CustomersTest extends TestCase $reseller_userdata = json_decode($json_result, true)['data']; $reseller_userdata['adminsession'] = 1; $this->expectExceptionCode(404); - Customers::getLocal($reseller_userdata, array('loginname' => 'test1'))->delete(); + Customers::getLocal($reseller_userdata, array( + 'loginname' => 'test1' + ))->delete(); } public function testAdminCustomersDelete() @@ -337,11 +342,13 @@ class CustomersTest extends TestCase 'new_customer_password' => 'h0lYmo1y' ]; Customers::getLocal($admin_userdata, $data)->add(); - $json_result = Customers::getLocal($admin_userdata, array('loginname' => 'test2'))->delete(); + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test2' + ))->delete(); $result = json_decode($json_result, true)['data']; $this->assertEquals('test2', $result['loginname']); } - + public function testAdminCustomersUnlock() { global $admin_userdata; @@ -353,7 +360,7 @@ class CustomersTest extends TestCase $result = json_decode($json_result, true)['data']; $this->assertEquals(0, $result['loginfail_count']); } - + public function testAdminCustomersUnlockNotAllowed() { global $admin_userdata; @@ -366,7 +373,7 @@ class CustomersTest extends TestCase 'loginname' => 'test1' ))->unlock(); } - + public function testAdminCustomersMoveNotAllowed() { global $admin_userdata; @@ -380,7 +387,7 @@ class CustomersTest extends TestCase 'adminid' => 1 ))->move(); } - + public function testAdminCustomersMoveTargetIsSource() { global $admin_userdata; @@ -403,5 +410,5 @@ class CustomersTest extends TestCase $this->assertEquals(2, $result['adminid']); } - } + From 392db944a2050a80c8cf009a34815bf70729dc2b Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 27 Feb 2018 10:34:37 +0100 Subject: [PATCH 091/746] started work in SubDomains-ApiCommand; minor fixes Signed-off-by: Michael Kaufmann (d00p) --- admin_domains.php | 4 +- customer_domains.php | 80 +---- lib/classes/api/commands/class.Mysqls.php | 4 +- lib/classes/api/commands/class.Subdomains.php | 301 ++++++++++++++++++ tests/Customers/CustomersTest.php | 1 - 5 files changed, 316 insertions(+), 74 deletions(-) create mode 100644 lib/classes/api/commands/class.Subdomains.php diff --git a/admin_domains.php b/admin_domains.php index 89c61f4b..47190b56 100644 --- a/admin_domains.php +++ b/admin_domains.php @@ -138,9 +138,7 @@ if ($page == 'domains' || $page == 'overview') { if (isset($_POST['send']) && $_POST['send'] == 'send' && $alias_check['count'] == 0) { try { - Domains::getLocal($userinfo, array_merge(array( - 'id' => $id - ), $_POST))->delete(); + Domains::getLocal($userinfo, $_POST)->delete(); } catch (Exception $e) { dynamic_error($e->getMessage()); } diff --git a/customer_domains.php b/customer_domains.php index 23bbb07f..fff60977 100644 --- a/customer_domains.php +++ b/customer_domains.php @@ -176,79 +176,25 @@ if ($page == 'overview') { eval("echo \"" . getTemplate("domains/domainlist") . "\";"); } elseif ($action == 'delete' && $id != 0) { - $stmt = Database::prepare("SELECT `id`, `customerid`, `domain`, `documentroot`, `isemaildomain`, `parentdomainid`, `aliasdomain` FROM `" . TABLE_PANEL_DOMAINS . "` - WHERE `customerid` = :customerid - AND `id` = :id" - ); - Database::pexecute($stmt, array("customerid" => $userinfo['customerid'], "id" => $id)); - $result = $stmt->fetch(PDO::FETCH_ASSOC); + try { + $json_result = SubDomains::getLocal($userinfo, array( + 'id' => $id + ))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; $alias_stmt = Database::prepare("SELECT COUNT(`id`) AS `count` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `aliasdomain` = :aliasdomain"); - Database::pexecute($alias_stmt, array("aliasdomain" => $id)); - $alias_check = $alias_stmt->fetch(PDO::FETCH_ASSOC); + $alias_check = Database::pexecute_first($alias_stmt, array("aliasdomain" => $id)); if (isset($result['parentdomainid']) && $result['parentdomainid'] != '0' && $alias_check['count'] == 0) { if (isset($_POST['send']) && $_POST['send'] == 'send') { - if ($result['isemaildomain'] == '1') { - $emails_stmt = Database::prepare("SELECT COUNT(`id`) AS `count` FROM `" . TABLE_MAIL_VIRTUAL . "` - WHERE `customerid` = :customerid - AND `domainid` = :domainid" - ); - Database::pexecute($emails_stmt, array("customerid" => $userinfo['customerid'], "domainid" => $id)); - $emails = $emails_stmt->fetch(PDO::FETCH_ASSOC); - - if ($emails['count'] != '0') { - standard_error('domains_cantdeletedomainwithemail'); - } + try { + SubDomains::getLocal($userinfo, $_POST)->delete(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - - triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $log); - - $log->logAction(USR_ACTION, LOG_INFO, "deleted subdomain '" . $idna_convert->decode($result['domain']) . "'"); - $stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_DOMAINS . "` WHERE - `customerid` = :customerid - AND `id` = :id" - ); - Database::pexecute($stmt, array("customerid" => $userinfo['customerid'], "id" => $id)); - - $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` - SET `subdomains_used` = `subdomains_used` - 1 - WHERE `customerid` = :customerid" - ); - Database::pexecute($stmt, array("customerid" => $userinfo['customerid'])); - - // remove connections to ips and domainredirects - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_DOMAINTOIP . "` - WHERE `id_domain` = :domainid" - ); - Database::pexecute($del_stmt, array('domainid' => $id)); - - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_PANEL_DOMAINREDIRECTS . "` - WHERE `did` = :domainid" - ); - Database::pexecute($del_stmt, array('domainid' => $id)); - - // remove certificate from domain_ssl_settings, fixes #1596 - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` - WHERE `domainid` = :domainid" - ); - Database::pexecute($del_stmt, array('domainid' => $id)); - - // remove possible existing DNS entries - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_DOMAIN_DNS . "` - WHERE `domain_id` = :domainid - "); - Database::pexecute($del_stmt, array('domainid' => $id)); - - inserttask('1'); - - // Using nameserver, insert a task which rebuilds the server config - inserttask('4'); - redirectTo($filename, array('page' => $page, 's' => $s)); } else { ask_yesno('domains_reallydelete', $filename, array('id' => $id, 'page' => $page, 'action' => $action), $idna_convert->decode($result['domain'])); diff --git a/lib/classes/api/commands/class.Mysqls.php b/lib/classes/api/commands/class.Mysqls.php index 0d043d6d..b9482284 100644 --- a/lib/classes/api/commands/class.Mysqls.php +++ b/lib/classes/api/commands/class.Mysqls.php @@ -283,9 +283,7 @@ class Mysqls extends ApiCommand implements ResourceEntity } } } else { - if ($id != $this->getUserDetail('customerid')) { - throw new Exception("You cannot access data of other customers", 401); - } elseif (Settings::IsInList('panel.customer_hide_options', 'mysql')) { + if (Settings::IsInList('panel.customer_hide_options', 'mysql')) { throw new Exception("You cannot access this resource", 405); } $result_stmt = Database::prepare(" diff --git a/lib/classes/api/commands/class.Subdomains.php b/lib/classes/api/commands/class.Subdomains.php new file mode 100644 index 00000000..8f88b676 --- /dev/null +++ b/lib/classes/api/commands/class.Subdomains.php @@ -0,0 +1,301 @@ + (2010-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package API + * @since 0.10.0 + * + */ +class Subdomains extends ApiCommand implements ResourceEntity +{ + + public function add() + {} + + /** + * return a subdomain entry by either id or domainname + * + * @param int $id + * optional, the domain-id + * @param string $domainname + * optional, the domainname + * + * @access admin, customer + * @throws Exception + * @return array + */ + public function get() + { + $id = $this->getParam('id', true, 0); + $dn_optional = ($id <= 0 ? false : true); + $domainname = $this->getParam('domainname', $dn_optional, ''); + + // convert possible idn domain to punycode + if (substr($domainname, 0, 4) != 'xn--') { + $idna_convert = new idna_convert_wrapper(); + $domainname = $idna_convert->encode($domainname); + } + + if ($this->isAdmin()) { + if ($this->getUserDetail('customers_see_all') != 1) { + // if it's a reseller or an admin who cannot see all customers, we need to check + // whether the database belongs to one of his customers + $json_result = Customers::getLocal($this->getUserData())->list(); + $custom_list_result = json_decode($json_result, true)['data']['list']; + $customer_ids = array(); + foreach ($custom_list_result as $customer) { + $customer_ids[] = $customer['customerid']; + } + if (count($customer_ids) > 0) { + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_DOMAINS . "` + WHERE " . ($id > 0 ? "`id` = :iddn" : "`domain` = :iddn") . " AND `customerid` IN (:customerids) + "); + $params = array( + 'iddn' => ($id <= 0 ? $domainname : $id), + 'customerids' => implode(", ", $customer_ids) + ); + } else { + throw new Exception("You do not have any customers yet", 406); + } + } else { + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_DOMAINS . "` + WHERE " . ($id > 0 ? "`id` = :iddn" : "`databasename` = :iddn")); + $params = array( + 'iddn' => ($id <= 0 ? $domainname : $id) + ); + } + } else { + if (Settings::IsInList('panel.customer_hide_options', 'domains')) { + throw new Exception("You cannot access this resource", 405); + } + $result_stmt = Database::prepare(" + SELECT `id`, `customerid`, `domain`, `documentroot`, `isemaildomain`, `parentdomainid`, `aliasdomain` FROM `" . TABLE_PANEL_DOMAINS . "` + WHERE `customerid`= :customerid AND " . ($id > 0 ? "`id` = :iddn" : "`databasename` = :iddn")); + $params = array( + 'customerid' => $this->getUserDetail('customerid'), + 'iddn' => ($id <= 0 ? $domainname : $id) + ); + } + $result = Database::pexecute_first($result_stmt, $params, true, true); + if ($result) { + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_NOTICE, "[API] get subdomain '" . $result['domain'] . "'"); + return $this->response(200, "successfull", $result); + } + $key = ($id > 0 ? "id #" . $id : "domainname '" . $domainname . "'"); + throw new Exception("Subdomain with " . $key . " could not be found", 404); + } + + public function update() + {} + + public function list() + { + if ($this->isAdmin()) { + // if we're an admin, list all databases of all the admins customers + // or optionally for one specific customer identified by id or loginname + $customerid = $this->getParam('customerid', true, 0); + $loginname = $this->getParam('loginname', true, ''); + + if (! empty($customer_id) || ! empty($loginname)) { + $json_result = Customers::getLocal($this->getUserData(), array( + 'id' => $customerid, + 'loginname' => $loginname + ))->get(); + $custom_list_result = array( + json_decode($json_result, true)['data'] + ); + } else { + $json_result = Customers::getLocal($this->getUserData())->list(); + $custom_list_result = json_decode($json_result, true)['data']['list']; + } + $customer_ids = array(); + $customer_stdsubs = array(); + foreach ($custom_list_result as $customer) { + $customer_ids[] = $customer['customerid']; + $customer_stdsubs[$customer['customerid']] = $customer['standardsubdomain']; + } + } else { + if (Settings::IsInList('panel.customer_hide_options', 'domains')) { + throw new Exception("You cannot access this resource", 405); + } + $customer_ids = array( + $this->getUserDetail('customerid') + ); + $customer_stdsubs = array( + $this->getUserDetail('customerid') => $this->getUserDetail('standardsubdomain') + ); + } + + // prepare select statement + $domains_stmt = Database::prepare(" + SELECT `d`.`id`, `d`.`customerid`, `d`.`domain`, `d`.`documentroot`, `d`.`isbinddomain`, `d`.`isemaildomain`, `d`.`caneditdomain`, `d`.`iswildcarddomain`, `d`.`parentdomainid`, `d`.`letsencrypt`, `d`.`termination_date`, `ad`.`id` AS `aliasdomainid`, `ad`.`domain` AS `aliasdomain`, `da`.`id` AS `domainaliasid`, `da`.`domain` AS `domainalias` + FROM `" . TABLE_PANEL_DOMAINS . "` `d` + LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` `ad` ON `d`.`aliasdomain`=`ad`.`id` + LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` `da` ON `da`.`aliasdomain`=`d`.`id` + WHERE `d`.`customerid`= :customerid + AND `d`.`email_only`='0' + AND `d`.`id` <> :standardsubdomain + "); + + $result = array(); + foreach ($customer_ids as $customer_id) { + Database::pexecute($domains_stmt, array( + "customerid" => $customer_id, + "standardsubdomain" => $customer_stdsubs[$customer_id] + ), true, true); + while ($row = $domains_stmt->fetch(PDO::FETCH_ASSOC)) { + $result[] = $row; + } + } + return $this->response(200, "successfull", array( + 'count' => count($result), + 'list' => $result + )); + } + + /** + * delete a subdomain by either id or domainname + * + * @param int $id + * optional, the domain-id + * @param string $domainname + * optional, the domainname + * + * @access admin, customer + * @throws Exception + * @return array + */ + public function delete() + { + $id = $this->getParam('id', true, 0); + $dn_optional = ($id <= 0 ? false : true); + $domainname = $this->getParam('domainname', $dn_optional, ''); + + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'domains')) { + throw new Exception("You cannot access this resource", 405); + } + + $json_result = SubDomains::getLocal($this->getUserData(), array( + 'id' => $id, + 'domainname' => $domainname + ))->get(); + $result = json_decode($json_result, true)['data']; + $id = $result['id']; + + // get needed customer info to reduce the subdomain-usage-counter by one + if ($this->isAdmin()) { + $json_result = Customers::getLocal($this->getUserData(), array( + 'id' => $result['customerid'] + ))->get(); + $customer = json_decode($json_result, true)['data']; + $subdomains_used = $customer['subdomains_used']; + $customer_id = $customer['customer_id']; + } else { + if ($result['caneditdomain'] == 0) { + throw new Exception("You cannot edit this resource", 405); + } + $subdomains_used = $this->getUserDetail('subdomains_used'); + $customer_id = $this->getUserDetail('customerid'); + } + + if ($result['isemaildomain'] == '1') { + // check for e-mail addresses + $emails_stmt = Database::prepare(" + SELECT COUNT(`id`) AS `count` FROM `" . TABLE_MAIL_VIRTUAL . "` + WHERE `customerid` = :customerid AND `domainid` = :domainid + "); + $emails = Database::pexecute_first($emails_stmt, array( + "customerid" => $customer_id, + "domainid" => $id + ), true, true); + + if ($emails['count'] != '0') { + standard_error('domains_cantdeletedomainwithemail', '', true); + } + } + + triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $this->logger()); + + // delete domain from table + $stmt = Database::prepare(" + DELETE FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `customerid` = :customerid AND `id` = :id + "); + Database::pexecute($stmt, array( + "customerid" => $customer_id, + "id" => $id + ), true, true); + + // remove connections to ips and domainredirects + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_DOMAINTOIP . "` + WHERE `id_domain` = :domainid + "); + Database::pexecute($del_stmt, array( + 'domainid' => $id + ), true, true); + + // remove redirect-codes + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_PANEL_DOMAINREDIRECTS . "` + WHERE `did` = :domainid + "); + Database::pexecute($del_stmt, array( + 'domainid' => $id + ), true, true); + + // remove certificate from domain_ssl_settings, fixes #1596 + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` + WHERE `domainid` = :domainid + "); + Database::pexecute($del_stmt, array( + 'domainid' => $id + ), true, true); + + // remove possible existing DNS entries + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_DOMAIN_DNS . "` + WHERE `domain_id` = :domainid + "); + Database::pexecute($del_stmt, array( + 'domainid' => $id + ), true, true); + + inserttask('1'); + // Using nameserver, insert a task which rebuilds the server config + inserttask('4'); + + // reduce subdomain-usage-counter + $stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_CUSTOMERS . "` + SET `subdomains_used` = `subdomains_used` - 1 + WHERE `customerid` = :customerid + "); + Database::pexecute($stmt, array( + "customerid" => $customer_id + ), true, true); + // update admin usage + $stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_ADMINS . "` + SET `subdomains_used` = `subdomains_used` - 1 + WHERE `adminid` = :adminid + "); + Database::pexecute($stmt, array( + "adminid" => ($this->isAdmin() ? $customer['adminid'] : $this->getUserDetail('adminid')) + ), true, true); + + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_WARNING, "[API] deleted subdomain '" . $result['domain'] . "'"); + return $this->response(200, "successfull", $result); + } +} diff --git a/tests/Customers/CustomersTest.php b/tests/Customers/CustomersTest.php index c90dd6c4..338c6bef 100644 --- a/tests/Customers/CustomersTest.php +++ b/tests/Customers/CustomersTest.php @@ -411,4 +411,3 @@ class CustomersTest extends TestCase $this->assertEquals(2, $result['adminid']); } } - From 6318e5514b61b21e8a9d2befbdd26bd9b8aa2cc8 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 27 Feb 2018 10:54:03 +0100 Subject: [PATCH 092/746] ignore some more checkstyle rules Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Subdomains.php | 8 ++++++-- phpcs.xml | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/classes/api/commands/class.Subdomains.php b/lib/classes/api/commands/class.Subdomains.php index 8f88b676..cfec3569 100644 --- a/lib/classes/api/commands/class.Subdomains.php +++ b/lib/classes/api/commands/class.Subdomains.php @@ -19,7 +19,9 @@ class Subdomains extends ApiCommand implements ResourceEntity { public function add() - {} + { + throw new Exception("Not available yet.", 501); + } /** * return a subdomain entry by either id or domainname @@ -97,7 +99,9 @@ class Subdomains extends ApiCommand implements ResourceEntity } public function update() - {} + { + throw new Exception("Not available yet.", 501); + } public function list() { diff --git a/phpcs.xml b/phpcs.xml index e2a1b208..032051b6 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -4,9 +4,13 @@ + + + + From a038e35e458a132edc2256e530c4c336db671a3a Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 27 Feb 2018 15:54:07 +0100 Subject: [PATCH 093/746] added Subdomains.add; minor fixes and enhancements Signed-off-by: Michael Kaufmann (d00p) --- customer_domains.php | 227 +------------ lib/classes/api/abstract.ApiCommand.php | 36 +- lib/classes/api/commands/class.Admins.php | 36 +- lib/classes/api/commands/class.Customers.php | 34 +- lib/classes/api/commands/class.Froxlor.php | 2 +- lib/classes/api/commands/class.Ftps.php | 42 +-- lib/classes/api/commands/class.Mysqls.php | 46 +-- lib/classes/api/commands/class.Subdomains.php | 311 +++++++++++++++++- 8 files changed, 421 insertions(+), 313 deletions(-) diff --git a/customer_domains.php b/customer_domains.php index fff60977..e48c48f1 100644 --- a/customer_domains.php +++ b/customer_domains.php @@ -205,229 +205,12 @@ if ($page == 'overview') { } elseif ($action == 'add') { if ($userinfo['subdomains_used'] < $userinfo['subdomains'] || $userinfo['subdomains'] == '-1') { if (isset($_POST['send']) && $_POST['send'] == 'send') { - - if (substr($_POST['subdomain'], 0, 4) == 'xn--') { - standard_error('domain_nopunycode'); - } - - $subdomain = $idna_convert->encode(preg_replace(array('/\:(\d)+$/', '/^https?\:\/\//'), '', validate($_POST['subdomain'], 'subdomain', '', 'subdomainiswrong'))); - $domain = $_POST['domain']; - $domain_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_DOMAINS . "` - WHERE `domain` = :domain - AND `customerid` = :customerid - AND `parentdomainid` = '0' - AND `email_only` = '0' - AND `caneditdomain` = '1'" - ); - $domain_check = Database::pexecute_first($domain_stmt, array("domain" => $domain, "customerid" => $userinfo['customerid'])); - - $completedomain = $subdomain . '.' . $domain; - - if (Settings::Get('system.validate_domain') && ! validateDomain($completedomain)) { - standard_error(array( - 'stringiswrong', - 'mydomain' - )); - } - - if ($completedomain == Settings::Get('system.hostname')) { - standard_error('admin_domain_emailsystemhostname'); - } - - $completedomain_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_DOMAINS . "` - WHERE `domain` = :domain - AND `customerid` = :customerid - AND `email_only` = '0' - AND `caneditdomain` = '1'" - ); - $completedomain_check = Database::pexecute_first($completedomain_stmt, array("domain" => $completedomain, "customerid" => $userinfo['customerid'])); - - $aliasdomain = intval($_POST['alias']); - $aliasdomain_check = array('id' => 0); - $_doredirect = false; - - if ($aliasdomain != 0) { - // also check ip/port combination to be the same, #176 - $aliasdomain_stmt = Database::prepare("SELECT `d`.`id` FROM `" . TABLE_PANEL_DOMAINS . "` `d` , `" . TABLE_PANEL_CUSTOMERS . "` `c` , `".TABLE_DOMAINTOIP."` `dip` - WHERE `d`.`aliasdomain` IS NULL - AND `d`.`id` = :id - AND `c`.`standardsubdomain` <> `d`.`id` - AND `d`.`customerid` = :customerid - AND `c`.`customerid` = `d`.`customerid` - AND `d`.`id` = `dip`.`id_domain` - AND `dip`.`id_ipandports` - IN (SELECT `id_ipandports` FROM `".TABLE_DOMAINTOIP."` - WHERE `id_domain` = :id ) - GROUP BY `d`.`domain` - ORDER BY `d`.`domain` ASC;" - ); - $aliasdomain_check = Database::pexecute_first($aliasdomain_stmt, array("id" => $aliasdomain, "customerid" => $userinfo['customerid'])); - triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $log); - } - - if (isset($_POST['url']) && $_POST['url'] != '' && validateUrl($_POST['url'])) { - $path = $_POST['url']; - $_doredirect = true; - } else { - $path = validate($_POST['path'], 'path'); - } - - if (!preg_match('/^https?\:\/\//', $path) || !validateUrl($path)) { - if (strstr($path, ":") !== FALSE) { - standard_error('pathmaynotcontaincolon'); - } - // If path is empty or '/' and 'Use domain name as default value for DocumentRoot path' is enabled in settings, - // set default path to subdomain or domain name - if ((($path == '') || ($path == '/')) && Settings::Get('system.documentroot_use_default_value') == 1) { - $path = makeCorrectDir($userinfo['documentroot'] . '/' . $completedomain); - } else { - $path = makeCorrectDir($userinfo['documentroot'] . '/' . $path); - } - } else { - $_doredirect = true; - } - - $openbasedir_path = '0'; - if (isset($_POST['openbasedir_path']) && $_POST['openbasedir_path'] == '1') { - $openbasedir_path = '1'; - } - - $ssl_redirect = '0'; - if (isset($_POST['ssl_redirect']) && $_POST['ssl_redirect'] == '1') { - // a ssl-redirect only works if there actually is a - // ssl ip/port assigned to the domain - if (domainHasSslIpPort($domain_check['id']) == true) { - $ssl_redirect = '1'; - $_doredirect = true; - } else { - standard_error('sslredirectonlypossiblewithsslipport'); - } - } - - $letsencrypt = '0'; - if (isset($_POST['letsencrypt']) && $_POST['letsencrypt'] == '1') { - // let's encrypt only works if there actually is a - // ssl ip/port assigned to the domain - if (domainHasSslIpPort($domain_check['id']) == true) { - $letsencrypt = '1'; - } else { - standard_error('letsencryptonlypossiblewithsslipport'); - } - } - - // Temporarily deactivate ssl_redirect until Let's Encrypt certificate was generated - if ($ssl_redirect > 0 && $letsencrypt == 1) { - $ssl_redirect = 2; - } - - // HSTS - $hsts_maxage = isset($_POST['hsts_maxage']) ? (int)$_POST['hsts_maxage'] : 0; - $hsts_sub = isset($_POST['hsts_sub']) && (int)$_POST['hsts_sub'] == 1 ? 1 : 0; - $hsts_preload = isset($_POST['hsts_preload']) && (int)$_POST['hsts_preload'] == 1 ? 1 : 0; - - if ($path == '') { - standard_error('patherror'); - } elseif ($subdomain == '') { - standard_error(array('stringisempty', 'domainname')); - } elseif ($subdomain == 'www' && $domain_check['wwwserveralias'] == '1') { - standard_error('wwwnotallowed'); - } elseif ($domain == '') { - standard_error('domaincantbeempty'); - } elseif (strtolower($completedomain_check['domain']) == strtolower($completedomain)) { - standard_error('domainexistalready', $completedomain); - } elseif (strtolower($domain_check['domain']) != strtolower($domain)) { - standard_error('maindomainnonexist', $domain); - } elseif ($aliasdomain_check['id'] != $aliasdomain) { - standard_error('domainisaliasorothercustomer'); - } else { - // get the phpsettingid from parentdomain, #107 - $phpsid_stmt = Database::prepare("SELECT `phpsettingid` FROM `".TABLE_PANEL_DOMAINS."` - WHERE `id` = :id" - ); - Database::pexecute($phpsid_stmt, array("id" => $domain_check['id'])); - $phpsid_result = $phpsid_stmt->fetch(PDO::FETCH_ASSOC); - - if (!isset($phpsid_result['phpsettingid']) || (int)$phpsid_result['phpsettingid'] <= 0) { - // assign default config - $phpsid_result['phpsettingid'] = 1; - } - // check whether the customer has chosen its own php-config - if (isset($_POST['phpsettingid']) && intval($_POST['phpsettingid']) != $phpsid_result['phpsettingid']) { - $phpsid_result['phpsettingid'] = intval($_POST['phpsettingid']); - } - - $stmt = Database::prepare("INSERT INTO `" . TABLE_PANEL_DOMAINS . "` SET - `customerid` = :customerid, - `domain` = :domain, - `documentroot` = :documentroot, - `aliasdomain` = :aliasdomain, - `parentdomainid` = :parentdomainid, - `wwwserveralias` = :wwwserveralias, - `isemaildomain` = :isemaildomain, - `iswildcarddomain` = :iswildcarddomain, - `phpenabled` = :phpenabled, - `openbasedir` = :openbasedir, - `openbasedir_path` = :openbasedir_path, - `speciallogfile` = :speciallogfile, - `specialsettings` = :specialsettings, - `ssl_redirect` = :ssl_redirect, - `phpsettingid` = :phpsettingid, - `letsencrypt` = :letsencrypt, - `hsts` = :hsts, - `hsts_sub` = :hsts_sub, - `hsts_preload` = :hsts_preload" - ); - $params = array( - "customerid" => $userinfo['customerid'], - "domain" => $completedomain, - "documentroot" => $path, - "aliasdomain" => $aliasdomain != 0 ? $aliasdomain : null, - "parentdomainid" => $domain_check['id'], - "wwwserveralias" => $domain_check['wwwserveralias'] == '1' ? '1' : '0', - "iswildcarddomain" => $domain_check['iswildcarddomain'] == '1' ? '1' : '0', - "isemaildomain" => $domain_check['subcanemaildomain'] == '3' ? '1' : '0', - "openbasedir" => $domain_check['openbasedir'], - "openbasedir_path" => $openbasedir_path, - "phpenabled" => $domain_check['phpenabled'], - "speciallogfile" => $domain_check['speciallogfile'], - "specialsettings" => $domain_check['specialsettings'], - "ssl_redirect" => $ssl_redirect, - "phpsettingid" => $phpsid_result['phpsettingid'], - "letsencrypt" => $letsencrypt, - "hsts" => $hsts_maxage, - "hsts_sub" => $hsts_sub, - "hsts_preload" => $hsts_preload - ); - Database::pexecute($stmt, $params); - - if ($_doredirect) { - $did = Database::lastInsertId(); - $redirect = isset($_POST['redirectcode']) ? (int)$_POST['redirectcode'] : Settings::Get('customredirect.default'); - addRedirectToDomain($did, $redirect); - } - - $stmt = Database::prepare("INSERT INTO `".TABLE_DOMAINTOIP."` - (`id_domain`, `id_ipandports`) - SELECT LAST_INSERT_ID(), `id_ipandports` - FROM `".TABLE_DOMAINTOIP."` - WHERE `id_domain` = :id_domain" - ); - Database::pexecute($stmt, array("id_domain" => $domain_check['id'])); - - $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` - SET `subdomains_used` = `subdomains_used` + 1 - WHERE `customerid` = :customerid" - ); - Database::pexecute($stmt, array("customerid" => $userinfo['customerid'])); - - $log->logAction(USR_ACTION, LOG_INFO, "added subdomain '" . $completedomain . "'"); - inserttask('1'); - - // Using nameserver, insert a task which rebuilds the server config - inserttask('4'); - - redirectTo($filename, array('page' => $page, 's' => $s)); + try { + SubDomains::getLocal($userinfo, $_POST)->add(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } + redirectTo($filename, array('page' => $page, 's' => $s)); } else { $stmt = Database::prepare("SELECT `id`, `domain`, `documentroot`, `ssl_redirect`,`isemaildomain`,`letsencrypt` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `customerid` = :customerid diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index c11938bb..34d62e3b 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -339,25 +339,25 @@ abstract class ApiCommand */ private function getModFunctionString() { - $_c = get_called_class(); + $_class = get_called_class(); $level = 2; if (version_compare(PHP_VERSION, "5.4.0", "<")) { - $t = debug_backtrace(); + $trace = debug_backtrace(); } else { - $t = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); } while (true) { - $c = $t[$level]['class']; - $f = $t[$level]['function']; - if ($c != get_called_class()) { + $class = $trace[$level]['class']; + $func = $trace[$level]['function']; + if ($class != $_class) { $level ++; if ($level > 5) { break; } continue; } - return $c . ':' . $f; + return $class . ':' . $func; } } @@ -426,6 +426,28 @@ abstract class ApiCommand return $json_response; } + /** + * increase/decrease a resource field for customers/admins + * + * @param string $table + * @param string $keyfield + * @param int $key + * @param string $op + * @param string $resource + * @param string $extra + */ + protected static function updateResourceUsage($table = null, $keyfield = null, $key = null, $op = '+', $resource = null, $extra = null) + { + $stmt = Database::prepare(" + UPDATE `" . $table . "` + SET `" . $resource . "` = `" . $resource . "` " . $op . " 1 " . $extra . " + WHERE `" . $keyfield . "` = :key + "); + Database::pexecute($stmt, array( + 'key' => $key + ), true, true); + } + /** * read user data from database by api-request-header fields * diff --git a/lib/classes/api/commands/class.Admins.php b/lib/classes/api/commands/class.Admins.php index f5780ea3..11361603 100644 --- a/lib/classes/api/commands/class.Admins.php +++ b/lib/classes/api/commands/class.Admins.php @@ -124,7 +124,7 @@ class Admins extends ApiCommand implements ResourceEntity $tickets_see_all = $this->getParam('tickets_see_all', true, 0); $caneditphpsettings = $this->getParam('caneditphpsettings', true, 0); $change_serversettings = $this->getParam('change_serversettings', true, 0); - $ipaddress = $this->getParam('ipaddress', true, -1); + $ipaddress = $this->getParam('ipaddress', true, - 1); // validation $name = validate($name, 'name', '', '', array(), true); @@ -244,7 +244,7 @@ class Admins extends ApiCommand implements ResourceEntity 'tickets' => $tickets, 'tickets_see_all' => $tickets_see_all, 'mysqls' => $mysqls, - 'ip' => empty($ipaddress) ? "" : (is_array($ipaddress) && $ipaddress > 0 ? json_encode($ipaddress) : -1), + 'ip' => empty($ipaddress) ? "" : (is_array($ipaddress) && $ipaddress > 0 ? json_encode($ipaddress) : - 1), 'theme' => $_theme, 'custom_notes' => $custom_notes, 'custom_notes_show' => $custom_notes_show @@ -354,7 +354,7 @@ class Admins extends ApiCommand implements ResourceEntity $change_serversettings = $result['change_serversettings']; $diskspace = $result['diskspace']; $traffic = $result['traffic']; - $ipaddress = ($result['ip'] != -1 ? json_decode($result['ip'], true) : -1); + $ipaddress = ($result['ip'] != - 1 ? json_decode($result['ip'], true) : - 1); } else { $deactivated = $this->getParam('deactivated', true, $result['deactivated']); @@ -377,7 +377,7 @@ class Admins extends ApiCommand implements ResourceEntity $tickets_see_all = $this->getParam('tickets_see_all', true, $result['tickets_see_all']); $caneditphpsettings = $this->getParam('caneditphpsettings', true, $result['caneditphpsettings']); $change_serversettings = $this->getParam('change_serversettings', true, $result['change_serversettings']); - $ipaddress = $this->getParam('ipaddress', true, ($result['ip'] != -1 ? json_decode($result['ip'], true) : -1)); + $ipaddress = $this->getParam('ipaddress', true, ($result['ip'] != - 1 ? json_decode($result['ip'], true) : - 1)); $diskspace = $diskspace * 1024; $traffic = $traffic * 1024 * 1024; @@ -512,7 +512,7 @@ class Admins extends ApiCommand implements ResourceEntity 'tickets' => $tickets, 'tickets_see_all' => $tickets_see_all, 'mysqls' => $mysqls, - 'ip' => empty($ipaddress) ? "" : (is_array($ipaddress) && $ipaddress > 0 ? json_encode($ipaddress) : -1), + 'ip' => empty($ipaddress) ? "" : (is_array($ipaddress) && $ipaddress > 0 ? json_encode($ipaddress) : - 1), 'deactivated' => $deactivated, 'custom_notes' => $custom_notes, 'custom_notes_show' => $custom_notes_show, @@ -684,4 +684,30 @@ class Admins extends ApiCommand implements ResourceEntity } throw new Exception("Not allowed to execute given command.", 403); } + + /** + * increase resource-usage + * + * @param int $customerid + * @param string $resource + * @param string $extra + * optional, default empty + */ + public static function increaseUsage($adminid = 0, $resource = null, $extra = '') + { + self::updateResourceUsage(TABLE_PANEL_ADMINS, 'adminid', $adminid, '+', $resource, $extra); + } + + /** + * decrease resource-usage + * + * @param int $customerid + * @param string $resource + * @param string $extra + * optional, default empty + */ + public static function decreaseUsage($adminid = 0, $resource = null, $extra = '') + { + self::updateResourceUsage(TABLE_PANEL_ADMINS, 'adminid', $adminid, '-', $resource, $extra); + } } diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index 2d1dce24..d2a102af 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -76,7 +76,7 @@ class Customers extends ApiCommand implements ResourceEntity $result_stmt = Database::prepare(" SELECT `c`.*, `a`.`loginname` AS `adminname` FROM `" . TABLE_PANEL_CUSTOMERS . "` `c`, `" . TABLE_PANEL_ADMINS . "` `a` - WHERE " . ($id > 0 ? "`c`.`customerid` = :idln" : "`c`.`loginname` = :idln") . ($this->getUserDetail('customers_see_all') ? '' : " AND `c`.`adminid` = :adminid"). " AND `c`.`adminid` = `a`.`adminid`"); + WHERE " . ($id > 0 ? "`c`.`customerid` = :idln" : "`c`.`loginname` = :idln") . ($this->getUserDetail('customers_see_all') ? '' : " AND `c`.`adminid` = :adminid") . " AND `c`.`adminid` = `a`.`adminid`"); $params = array( 'idln' => ($id <= 0 ? $loginname : $id) ); @@ -208,7 +208,7 @@ class Customers extends ApiCommand implements ResourceEntity if (((($this->getUserDetail('diskspace_used') + $diskspace) > $this->getUserDetail('diskspace')) && ($this->getUserDetail('diskspace') / 1024) != '-1') || ((($this->getUserDetail('mysqls_used') + $mysqls) > $this->getUserDetail('mysqls')) && $this->getUserDetail('mysqls') != '-1') || ((($this->getUserDetail('emails_used') + $emails) > $this->getUserDetail('emails')) && $this->getUserDetail('emails') != '-1') || ((($this->getUserDetail('email_accounts_used') + $email_accounts) > $this->getUserDetail('email_accounts')) && $this->getUserDetail('email_accounts') != '-1') || ((($this->getUserDetail('email_forwarders_used') + $email_forwarders) > $this->getUserDetail('email_forwarders')) && $this->getUserDetail('email_forwarders') != '-1') || ((($this->getUserDetail('email_quota_used') + $email_quota) > $this->getUserDetail('email_quota')) && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') || ((($this->getUserDetail('ftps_used') + $ftps) > $this->getUserDetail('ftps')) && $this->getUserDetail('ftps') != '-1') || ((($this->getUserDetail('tickets_used') + $tickets) > $this->getUserDetail('tickets')) && $this->getUserDetail('tickets') != '-1') || ((($this->getUserDetail('subdomains_used') + $subdomains) > $this->getUserDetail('subdomains')) && $this->getUserDetail('subdomains') != '-1') || (($diskspace / 1024) == '-1' && ($this->getUserDetail('diskspace') / 1024) != '-1') || ($mysqls == '-1' && $this->getUserDetail('mysqls') != '-1') || ($emails == '-1' && $this->getUserDetail('emails') != '-1') || ($email_accounts == '-1' && $this->getUserDetail('email_accounts') != '-1') || ($email_forwarders == '-1' && $this->getUserDetail('email_forwarders') != '-1') || ($email_quota == '-1' && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') || ($ftps == '-1' && $this->getUserDetail('ftps') != '-1') || ($tickets == '-1' && $this->getUserDetail('tickets') != '-1') || ($subdomains == '-1' && $this->getUserDetail('subdomains') != '-1')) { standard_error('youcantallocatemorethanyouhave', '', true); } - + if (! validateEmail($email)) { standard_error('emailiswrong', $email, true); } else { @@ -755,10 +755,10 @@ class Customers extends ApiCommand implements ResourceEntity } if ($this->isAdmin()) { - + $diskspace = $diskspace * 1024; $traffic = $traffic * 1024 * 1024; - + if (((($this->getUserDetail('diskspace_used') + $diskspace - $result['diskspace']) > $this->getUserDetail('diskspace')) && ($this->getUserDetail('diskspace') / 1024) != '-1') || ((($this->getUserDetail('mysqls_used') + $mysqls - $result['mysqls']) > $this->getUserDetail('mysqls')) && $this->getUserDetail('mysqls') != '-1') || ((($this->getUserDetail('emails_used') + $emails - $result['emails']) > $this->getUserDetail('emails')) && $this->getUserDetail('emails') != '-1') || ((($this->getUserDetail('email_accounts_used') + $email_accounts - $result['email_accounts']) > $this->getUserDetail('email_accounts')) && $this->getUserDetail('email_accounts') != '-1') || ((($this->getUserDetail('email_forwarders_used') + $email_forwarders - $result['email_forwarders']) > $this->getUserDetail('email_forwarders')) && $this->getUserDetail('email_forwarders') != '-1') || ((($this->getUserDetail('email_quota_used') + $email_quota - $result['email_quota']) > $this->getUserDetail('email_quota')) && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') || ((($this->getUserDetail('ftps_used') + $ftps - $result['ftps']) > $this->getUserDetail('ftps')) && $this->getUserDetail('ftps') != '-1') || ((($this->getUserDetail('tickets_used') + $tickets - $result['tickets']) > $this->getUserDetail('tickets')) && $this->getUserDetail('tickets') != '-1') || ((($this->getUserDetail('subdomains_used') + $subdomains - $result['subdomains']) > $this->getUserDetail('subdomains')) && $this->getUserDetail('subdomains') != '-1') || (($diskspace / 1024) == '-1' && ($this->getUserDetail('diskspace') / 1024) != '-1') || ($mysqls == '-1' && $this->getUserDetail('mysqls') != '-1') || ($emails == '-1' && $this->getUserDetail('emails') != '-1') || ($email_accounts == '-1' && $this->getUserDetail('email_accounts') != '-1') || ($email_forwarders == '-1' && $this->getUserDetail('email_forwarders') != '-1') || ($email_quota == '-1' && $this->getUserDetail('email_quota') != '-1' && Settings::Get('system.mail_quota_enabled') == '1') || ($ftps == '-1' && $this->getUserDetail('ftps') != '-1') || ($tickets == '-1' && $this->getUserDetail('tickets') != '-1') || ($subdomains == '-1' && $this->getUserDetail('subdomains') != '-1')) { standard_error('youcantallocatemorethanyouhave', '', true); } @@ -1533,4 +1533,30 @@ class Customers extends ApiCommand implements ResourceEntity } throw new Exception("Not allowed to execute given command.", 403); } + + /** + * increase resource-usage + * + * @param int $customerid + * @param string $resource + * @param string $extra + * optional, default empty + */ + public static function increaseUsage($customerid = 0, $resource = null, $extra = '') + { + self::updateResourceUsage(TABLE_PANEL_CUSTOMERS, 'customerid', $customerid, '+', $resource, $extra); + } + + /** + * decrease resource-usage + * + * @param int $customerid + * @param string $resource + * @param string $extra + * optional, default empty + */ + public static function decreaseUsage($customerid = 0, $resource = null, $extra = '') + { + self::updateResourceUsage(TABLE_PANEL_CUSTOMERS, 'customerid', $customerid, '-', $resource, $extra); + } } diff --git a/lib/classes/api/commands/class.Froxlor.php b/lib/classes/api/commands/class.Froxlor.php index d10a96b5..3e902101 100644 --- a/lib/classes/api/commands/class.Froxlor.php +++ b/lib/classes/api/commands/class.Froxlor.php @@ -311,7 +311,7 @@ class Froxlor extends ApiCommand 'type' => $r[1], 'desc' => (isset($r[2]) ? trim($r[2]) : '') ); - } + } // check throws-section elseif (! empty($c) && strpos($c, '@throws') === false) { if (substr($c, 0, 3) == "/**") { continue; diff --git a/lib/classes/api/commands/class.Ftps.php b/lib/classes/api/commands/class.Ftps.php index a723080e..c98a226c 100644 --- a/lib/classes/api/commands/class.Ftps.php +++ b/lib/classes/api/commands/class.Ftps.php @@ -183,26 +183,14 @@ class Ftps extends ApiCommand implements ResourceEntity "guid" => $customer['guid'] ); Database::pexecute($stmt, $params, true, true); + + // update customer usage + Customers::increaseUsage($customer_id, 'ftps_used'); + Customers::increaseUsage($customer_id, 'ftp_lastaccountnumber'); - $stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_CUSTOMERS . "` - SET `ftps_used` = `ftps_used` + 1, - `ftp_lastaccountnumber` = `ftp_lastaccountnumber` + 1 - WHERE `customerid` = :customerid - "); - Database::pexecute($stmt, array( - "customerid" => $customer_id - ), true, true); - - $stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_ADMINS . "` - SET `ftps_used` = `ftps_used` + 1 - WHERE `adminid` = :adminid - "); - Database::pexecute($stmt, array( - "adminid" => $customer['adminid'] - ), true, true); - + // update admin usage + Admins::increaseUsage($customer['adminid'], 'ftps_used'); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] added ftp-account '" . $username . " (" . $path . ")'"); inserttask(5); @@ -614,21 +602,9 @@ class Ftps extends ApiCommand implements ResourceEntity // decrease ftp-user usage for customer $resetaccnumber = ($customer_data['ftps_used'] == '1') ? " , `ftp_lastaccountnumber`='0'" : ''; - $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` - SET `ftps_used` = `ftps_used` - 1 $resetaccnumber - WHERE `customerid` = :customerid"); - Database::pexecute($stmt, array( - "customerid" => $customer_data['customerid'] - ), true, true); + Customers::decreaseUsage($customer_id, 'ftps_used', $resetaccnumber); // update admin usage - $stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_ADMINS . "` - SET `mysqls_used` = `mysqls_used` - 1 - WHERE `adminid` = :adminid - "); - Database::pexecute($stmt, array( - "adminid" => ($this->isAdmin() ? $customer_data['adminid'] : $this->getUserDetail('adminid')) - ), true, true); + Admins::decreaseUsage(($this->isAdmin() ? $customer_data['adminid'] : $this->getUserDetail('adminid')), 'ftps_used'); $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_WARNING, "[API] deleted ftp-user '" . $result['username'] . "'"); return $this->response(200, "successfull", $result); diff --git a/lib/classes/api/commands/class.Mysqls.php b/lib/classes/api/commands/class.Mysqls.php index b9482284..8022562d 100644 --- a/lib/classes/api/commands/class.Mysqls.php +++ b/lib/classes/api/commands/class.Mysqls.php @@ -62,14 +62,7 @@ class Mysqls extends ApiCommand implements ResourceEntity if (! isset($sql_root) || ! is_array($sql_root)) { throw new ErrorException("Database server with index #" . $dbserver . " is unknown", 404); } - - if ($password == '') { - standard_error(array( - 'stringisempty', - 'mysql_password' - ), '', true); - } - + if ($sendinfomail != 1) { $sendinfomail = 0; } @@ -123,24 +116,11 @@ class Mysqls extends ApiCommand implements ResourceEntity $params['id'] = $databaseid; // update customer usage - $stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_CUSTOMERS . "` - SET `mysqls_used` = `mysqls_used` + 1, `mysql_lastaccountnumber` = `mysql_lastaccountnumber` + 1 - WHERE `customerid` = :customerid - "); - Database::pexecute($stmt, array( - "customerid" => ($this->isAdmin() ? $customer['customerid'] : $this->getUserDetail('customerid')) - ), true, true); + Customers::increaseUsage(($this->isAdmin() ? $customer['customerid'] : $this->getUserDetail('customerid')), 'mysqls_used'); + Customers::increaseUsage(($this->isAdmin() ? $customer['customerid'] : $this->getUserDetail('customerid')), 'mysql_lastaccountnumber'); // update admin usage - $stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_ADMINS . "` - SET `mysqls_used` = `mysqls_used` + 1 - WHERE `adminid` = :adminid - "); - Database::pexecute($stmt, array( - "adminid" => $this->getUserDetail('adminid') - ), true, true); + Admins::increaseUsage($this->getUserDetail('adminid'), 'mysqls_used'); // send info-mail? if ($sendinfomail == 1) { @@ -586,23 +566,9 @@ class Mysqls extends ApiCommand implements ResourceEntity } // reduce mysql-usage-counter $resetaccnumber = ($mysql_used == '1') ? " , `mysql_lastaccountnumber` = '0' " : ''; - $stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_CUSTOMERS . "` - SET `mysqls_used` = `mysqls_used` - 1 " . $resetaccnumber . " - WHERE `customerid` = :customerid - "); - Database::pexecute($stmt, array( - "customerid" => $customer_id - ), true, true); + Customers::decreaseUsage($customer_id, 'mysqls_used', $resetaccnumber); // update admin usage - $stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_ADMINS . "` - SET `mysqls_used` = `mysqls_used` - 1 - WHERE `adminid` = :adminid - "); - Database::pexecute($stmt, array( - "adminid" => ($this->isAdmin() ? $customer['adminid'] : $this->getUserDetail('adminid')), - ), true, true); + Admins::decreaseUsage(($this->isAdmin() ? $customer['adminid'] : $this->getUserDetail('adminid')), 'mysqls_used'); $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_WARNING, "[API] deleted database '" . $result['databasename'] . "'"); return $this->response(200, "successfull", $result); diff --git a/lib/classes/api/commands/class.Subdomains.php b/lib/classes/api/commands/class.Subdomains.php index cfec3569..7b496e78 100644 --- a/lib/classes/api/commands/class.Subdomains.php +++ b/lib/classes/api/commands/class.Subdomains.php @@ -18,9 +18,318 @@ class Subdomains extends ApiCommand implements ResourceEntity { + /** + * add a new subdomain + * + * @param string $subdomain + * part before domain.tld to create as subdomain + * @param string $domain + * domainname of main-domain + * @param int $alias + * optional, domain-id of a domain that the new domain should be an alias of + * @param string $path + * optional, destination path relative to the customers-homedir, default is customers-homedir + * @param string $url + * optional, overwrites path value with an URL to generate a redirect, alternatively use the path parameter also for URLs + * @param string $openbasedir_path + * optional, either 0 for customers-homedir or 1 for domains-docroot + * @param int $phpsettingid + * optional, php-settings-id, if empty the $domain value is used + * @param int $redirectcode + * optional, redirect-code-id from TABLE_PANEL_REDIRECTCODES + * @param bool $ssl_redirect + * optional, whether to generate a https-redirect or not, default false; requires SSL to be enabled + * @param bool $letsencrypt + * optional, whether to generate a Let's Encrypt certificate for this domain, default false; requires SSL to be enabled + * @param int $customer_id + * required when called as admin, not needed when called as customer + * + * @access admin, customer + * @throws Exception + * @return array + */ public function add() { - throw new Exception("Not available yet.", 501); + if ($this->getUserDetail('subdomains_used') < $this->getUserDetail('subdomains') || $this->getUserDetail('subdomains') == '-1') { + // parameters + $subdomain = $this->getParam('subdomain'); + $domain = $this->getParam('domain'); + + // optional parameters + $aliasdomain = $this->getParam('alias', true, 0); + $path = $this->getParam('path', true, ''); + $url = $this->getParam('url', true, ''); + $openbasedir_path = $this->getParam('openbasedir_path', true, 1); + $phpsettingid = $this->getParam('phpsettingid', true, 0); + $redirectcode = $this->getParam('redirectcode', true, Settings::Get('customredirect.default')); + if (Settings::Get('system.use_ssl')) { + $ssl_redirect = $this->getParam('ssl_redirect', true, 0); + $letsencrypt = $this->getParam('letsencrypt', true, 0); + $hsts_maxage = $this->getParam('hsts_maxage', true, 0); + $hsts_sub = $this->getParam('hsts_sub', true, 0); + $hsts_preload = $this->getParam('hsts_preload', true, 0); + } else { + $ssl_redirect = 0; + $letsencrypt = 0; + $hsts_maxage = 0; + $hsts_sub = 0; + $hsts_preload = 0; + } + + // get needed customer info to reduce the mysql-usage-counter by one + if ($this->isAdmin()) { + // get customer id + $customer_id = $this->getParam('customer_id'); + $json_result = Customers::getLocal($this->getUserData(), array( + 'id' => $customer_id + ))->get(); + $customer = json_decode($json_result, true)['data']; + // check whether the customer has enough resources to get the subdomain added + if ($customer['subdomains_used'] >= $customer['subdomains'] && $customer['subdomains'] != '-1') { + throw new Exception("Customer has no more resources available", 406); + } + } else { + $customer_id = $this->getUserDetail('customerid'); + $customer = $this->getUserData(); + } + + // validation + if (substr($subdomain, 0, 4) == 'xn--') { + standard_error('domain_nopunycode', '', true); + } + + $idna_convert = new idna_convert_wrapper(); + $subdomain = $idna_convert->encode(preg_replace(array( + '/\:(\d)+$/', + '/^https?\:\/\//' + ), '', validate($subdomain, 'subdomain', '', 'subdomainiswrong', array(), true))); + + // merge the two parts together + $completedomain = $subdomain . '.' . $domain; + + if (Settings::Get('system.validate_domain') && ! validateDomain($completedomain)) { + standard_error(array( + 'stringiswrong', + 'mydomain' + ), '', true); + } + if ($completedomain == Settings::Get('system.hostname')) { + standard_error('admin_domain_emailsystemhostname', '', true); + } + + // check whether the domain already exists + try { + $json_result = SubDomains::getLocal($this->getUserData(), array( + 'domainname' => $completedomain + ))->get(); + // no exception so far - domain exists + standard_error('domainexistalready', $completedomain, true); + } catch (Exception $e) { + // all good, domain does not exist + } + + // alias domain checked? + if ($aliasdomain != 0) { + // also check ip/port combination to be the same, #176 + $aliasdomain_stmt = Database::prepare(" + SELECT `d`.`id` FROM `" . TABLE_PANEL_DOMAINS . "` `d` , `" . TABLE_PANEL_CUSTOMERS . "` `c` , `" . TABLE_DOMAINTOIP . "` `dip` + WHERE `d`.`aliasdomain` IS NULL + AND `d`.`id` = :id + AND `c`.`standardsubdomain` <> `d`.`id` + AND `d`.`customerid` = :customerid + AND `c`.`customerid` = `d`.`customerid` + AND `d`.`id` = `dip`.`id_domain` + AND `dip`.`id_ipandports` + IN (SELECT `id_ipandports` FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :id ) + GROUP BY `d`.`domain` + ORDER BY `d`.`domain` ASC + "); + $aliasdomain_check = Database::pexecute_first($aliasdomain_stmt, array( + "id" => $aliasdomain, + "customerid" => $customer_id + ), true, true); + if ($aliasdomain_check['id'] != $aliasdomain) { + standard_error('domainisaliasorothercustomer', '', true); + } + triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $this->logger()); + } + + // check whether an URL was specified + $_doredirect = false; + if (! empty($url) && validateUrl($url)) { + $path = $url; + $_doredirect = true; + } else { + $path = validate($path, 'path', '', '', array(), true); + } + + // check whether path is a real path + if (! preg_match('/^https?\:\/\//', $path) || ! validateUrl($path)) { + if (strstr($path, ":") !== false) { + standard_error('pathmaynotcontaincolon', '', true); + } + // If path is empty or '/' and 'Use domain name as default value for DocumentRoot path' is enabled in settings, + // set default path to subdomain or domain name + if ((($path == '') || ($path == '/')) && Settings::Get('system.documentroot_use_default_value') == 1) { + $path = makeCorrectDir($customer['documentroot'] . '/' . $completedomain); + } else { + $path = makeCorrectDir($customer['documentroot'] . '/' . $path); + } + } else { + // no it's not, create a redirect + $_doredirect = true; + } + + if ($openbasedir_path != 1) { + $openbasedir_path = 0; + } + + // get main domain for various checks + $domain_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_DOMAINS . "` + WHERE `domain` = :domain + AND `customerid` = :customerid + AND `parentdomainid` = '0' + AND `email_only` = '0' + AND `caneditdomain` = '1' + "); + $domain_check = Database::pexecute_first($domain_stmt, array( + "domain" => $domain, + "customerid" => $customer['customerid'] + ), true, true); + + if (! $domain_check) { + // the given main-domain + standard_error('maindomainnonexist', $domain, true); + } elseif ($subdomain == 'www' && $domain_check['wwwserveralias'] == '1') { + // you cannot add 'www' as subdomain when the maindomain generates a www-alias + standard_error('wwwnotallowed', '', true); + } elseif (strtolower($completedomain_check['domain']) == strtolower($completedomain)) { + // the domain does already exist as main-domain + standard_error('domainexistalready', $completedomain, true); + } + + if ($ssl_redirect != 0) { + // a ssl-redirect only works if there actually is a + // ssl ip/port assigned to the domain + if (domainHasSslIpPort($domain_check['id']) == true) { + $ssl_redirect = '1'; + $_doredirect = true; + } else { + standard_error('sslredirectonlypossiblewithsslipport', '', true); + } + } + + if ($letsencrypt != 0) { + // let's encrypt only works if there actually is a + // ssl ip/port assigned to the domain + if (domainHasSslIpPort($domain_check['id']) == true) { + $letsencrypt = '1'; + } else { + standard_error('letsencryptonlypossiblewithsslipport', '', true); + } + } + + // Temporarily deactivate ssl_redirect until Let's Encrypt certificate was generated + if ($ssl_redirect > 0 && $letsencrypt == 1) { + $ssl_redirect = 2; + } + + // get the phpsettingid from parentdomain, #107 + $phpsid_stmt = Database::prepare(" + SELECT `phpsettingid` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `id` = :id + "); + $phpsid_result = Database::pexecute_first($phpsid_stmt, array( + "id" => $domain_check['id'] + ), true, true); + + if (! isset($phpsid_result['phpsettingid']) || (int) $phpsid_result['phpsettingid'] <= 0) { + // assign default config + $phpsid_result['phpsettingid'] = 1; + } + // check whether the customer has chosen its own php-config + if ($phpsettingid > 0 && $phpsettingid != $phpsid_result['phpsettingid']) { + $phpsid_result['phpsettingid'] = intval($phpsettingid); + } + + // acutall insert domain + $stmt = Database::prepare(" + INSERT INTO `" . TABLE_PANEL_DOMAINS . "` SET + `customerid` = :customerid, + `domain` = :domain, + `documentroot` = :documentroot, + `aliasdomain` = :aliasdomain, + `parentdomainid` = :parentdomainid, + `wwwserveralias` = :wwwserveralias, + `isemaildomain` = :isemaildomain, + `iswildcarddomain` = :iswildcarddomain, + `phpenabled` = :phpenabled, + `openbasedir` = :openbasedir, + `openbasedir_path` = :openbasedir_path, + `speciallogfile` = :speciallogfile, + `specialsettings` = :specialsettings, + `ssl_redirect` = :ssl_redirect, + `phpsettingid` = :phpsettingid, + `letsencrypt` = :letsencrypt, + `hsts` = :hsts, + `hsts_sub` = :hsts_sub, + `hsts_preload` = :hsts_preload + "); + $params = array( + "customerid" => $customer['customerid'], + "domain" => $completedomain, + "documentroot" => $path, + "aliasdomain" => $aliasdomain != 0 ? $aliasdomain : null, + "parentdomainid" => $domain_check['id'], + "wwwserveralias" => $domain_check['wwwserveralias'] == '1' ? '1' : '0', + "iswildcarddomain" => $domain_check['iswildcarddomain'] == '1' ? '1' : '0', + "isemaildomain" => $domain_check['subcanemaildomain'] == '3' ? '1' : '0', + "openbasedir" => $domain_check['openbasedir'], + "openbasedir_path" => $openbasedir_path, + "phpenabled" => $domain_check['phpenabled'], + "speciallogfile" => $domain_check['speciallogfile'], + "specialsettings" => $domain_check['specialsettings'], + "ssl_redirect" => $ssl_redirect, + "phpsettingid" => $phpsid_result['phpsettingid'], + "letsencrypt" => $letsencrypt, + "hsts" => $hsts_maxage, + "hsts_sub" => $hsts_sub, + "hsts_preload" => $hsts_preload + ); + Database::pexecute($stmt, $params, true, true); + $subdomain_id = Database::lastInsertId(); + + $stmt = Database::prepare(" + INSERT INTO `" . TABLE_DOMAINTOIP . "` + (`id_domain`, `id_ipandports`) + SELECT LAST_INSERT_ID(), `id_ipandports` + FROM `" . TABLE_DOMAINTOIP . "` + WHERE `id_domain` = :id_domain + "); + Database::pexecute($stmt, array( + "id_domain" => $domain_check['id'] + )); + + if ($_doredirect) { + addRedirectToDomain($subdomain_id, $redirectcode); + } + + inserttask('1'); + // Using nameserver, insert a task which rebuilds the server config + inserttask('4'); + + Customers::increaseUsage($customer['customerid'], 'subdomains_used'); + Admins::increaseUsage(($this->isAdmin() ? $customer['adminid'] : $this->getUserDetail('adminid')), 'subdomains_used'); + + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] added subdomain '" . $completedomain . "'"); + + $json_result = Subdomains::getLocal($this->getUserData(), array( + 'id' => $subdomain_id + ))->get(); + $result = json_decode($json_result, true)['data']; + return $this->response(200, "successfull", $result); + } + throw new Exception("No more resources available", 406); } /** From 3e0b55141602215eda49de59f1b45be2b504769e Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 27 Feb 2018 15:56:05 +0100 Subject: [PATCH 094/746] fix copy'n'paste fail Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Ftps.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/classes/api/commands/class.Ftps.php b/lib/classes/api/commands/class.Ftps.php index c98a226c..42cf0705 100644 --- a/lib/classes/api/commands/class.Ftps.php +++ b/lib/classes/api/commands/class.Ftps.php @@ -602,7 +602,7 @@ class Ftps extends ApiCommand implements ResourceEntity // decrease ftp-user usage for customer $resetaccnumber = ($customer_data['ftps_used'] == '1') ? " , `ftp_lastaccountnumber`='0'" : ''; - Customers::decreaseUsage($customer_id, 'ftps_used', $resetaccnumber); + Customers::decreaseUsage($customer_data['customerid'], 'ftps_used', $resetaccnumber); // update admin usage Admins::decreaseUsage(($this->isAdmin() ? $customer_data['adminid'] : $this->getUserDetail('adminid')), 'ftps_used'); From 75bc6d32ab10030b2879870c779e93de6b395ac0 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 27 Feb 2018 17:10:52 +0100 Subject: [PATCH 095/746] minor fixes in SubDomains.add; first Unit-Tests for SubDomains-ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- ...ss.Subdomains.php => class.SubDomains.php} | 31 +++++---- phpunit.xml | 1 + tests/SubDomains/SubDomainsTest.php | 64 +++++++++++++++++++ 3 files changed, 84 insertions(+), 12 deletions(-) rename lib/classes/api/commands/{class.Subdomains.php => class.SubDomains.php} (97%) create mode 100644 tests/SubDomains/SubDomainsTest.php diff --git a/lib/classes/api/commands/class.Subdomains.php b/lib/classes/api/commands/class.SubDomains.php similarity index 97% rename from lib/classes/api/commands/class.Subdomains.php rename to lib/classes/api/commands/class.SubDomains.php index 7b496e78..cade0a69 100644 --- a/lib/classes/api/commands/class.Subdomains.php +++ b/lib/classes/api/commands/class.SubDomains.php @@ -15,7 +15,7 @@ * @since 0.10.0 * */ -class Subdomains extends ApiCommand implements ResourceEntity +class SubDomains extends ApiCommand implements ResourceEntity { /** @@ -31,7 +31,7 @@ class Subdomains extends ApiCommand implements ResourceEntity * optional, destination path relative to the customers-homedir, default is customers-homedir * @param string $url * optional, overwrites path value with an URL to generate a redirect, alternatively use the path parameter also for URLs - * @param string $openbasedir_path + * @param int $openbasedir_path * optional, either 0 for customers-homedir or 1 for domains-docroot * @param int $phpsettingid * optional, php-settings-id, if empty the $domain value is used @@ -116,18 +116,25 @@ class Subdomains extends ApiCommand implements ResourceEntity if ($completedomain == Settings::Get('system.hostname')) { standard_error('admin_domain_emailsystemhostname', '', true); } - + // check whether the domain already exists - try { - $json_result = SubDomains::getLocal($this->getUserData(), array( - 'domainname' => $completedomain - ))->get(); + $completedomain_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_DOMAINS . "` + WHERE `domain` = :domain + AND `customerid` = :customerid + AND `email_only` = '0' + AND `caneditdomain` = '1' + "); + $completedomain_check = Database::pexecute_first($completedomain_stmt, array( + "domain" => $completedomain, + "customerid" => $customer_id + ), true, true); + + if ($completedomain_check) { // no exception so far - domain exists standard_error('domainexistalready', $completedomain, true); - } catch (Exception $e) { - // all good, domain does not exist } - + // alias domain checked? if ($aliasdomain != 0) { // also check ip/port combination to be the same, #176 @@ -381,7 +388,7 @@ class Subdomains extends ApiCommand implements ResourceEntity } else { $result_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_DOMAINS . "` - WHERE " . ($id > 0 ? "`id` = :iddn" : "`databasename` = :iddn")); + WHERE " . ($id > 0 ? "`id` = :iddn" : "`domainname` = :iddn")); $params = array( 'iddn' => ($id <= 0 ? $domainname : $id) ); @@ -392,7 +399,7 @@ class Subdomains extends ApiCommand implements ResourceEntity } $result_stmt = Database::prepare(" SELECT `id`, `customerid`, `domain`, `documentroot`, `isemaildomain`, `parentdomainid`, `aliasdomain` FROM `" . TABLE_PANEL_DOMAINS . "` - WHERE `customerid`= :customerid AND " . ($id > 0 ? "`id` = :iddn" : "`databasename` = :iddn")); + WHERE `customerid`= :customerid AND " . ($id > 0 ? "`id` = :iddn" : "`domainname` = :iddn")); $params = array( 'customerid' => $this->getUserDetail('customerid'), 'iddn' => ($id <= 0 ? $domainname : $id) diff --git a/phpunit.xml b/phpunit.xml index a924f199..aac6ff31 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -11,6 +11,7 @@ tests/Global tests/Admins tests/Customers + tests/SubDomains tests/IpsAndPorts tests/Ftps diff --git a/tests/SubDomains/SubDomainsTest.php b/tests/SubDomains/SubDomainsTest.php new file mode 100644 index 00000000..6388dc21 --- /dev/null +++ b/tests/SubDomains/SubDomainsTest.php @@ -0,0 +1,64 @@ + 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'subdomain' => 'xn--asd', + 'domain' => 'unknown.froxlor.org' + ]; + $this->expectExceptionMessage('You must not specify punycode (IDNA). The domain will automatically be converted'); + SubDomains::getLocal($customer_userdata, $data)->add(); + } + + public function testCustomerSubDomainsAddMainDomainUnknown() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'subdomain' => 'wohoo', + 'domain' => 'unknown.froxlor.org' + ]; + $this->expectExceptionMessage('The main-domain unknown.froxlor.org does not exist.'); + SubDomains::getLocal($customer_userdata, $data)->add(); + } + + public function testCustomerSubDomainsAddInvalidDomain() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'subdomain' => '#+?', + 'domain' => 'unknown.froxlor.org' + ]; + $this->expectExceptionMessage("Wrong Input in Field 'Domain'"); + SubDomains::getLocal($customer_userdata, $data)->add(); + } +} From 2bf5e90a7746e56742becf503d861aabacf29fb0 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 27 Feb 2018 18:07:43 +0100 Subject: [PATCH 096/746] add testsuite parameter to phpunit to respect our required test-order; minor fixes in Domains- and SubDomains Command Signed-off-by: Michael Kaufmann (d00p) --- build.xml | 4 ++++ lib/classes/api/commands/class.Domains.php | 8 ++++++-- lib/classes/api/commands/class.SubDomains.php | 20 +++---------------- phpunit.xml | 3 ++- 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/build.xml b/build.xml index 99f202d8..10713128 100644 --- a/build.xml +++ b/build.xml @@ -194,6 +194,8 @@ taskname="phpunit"> + + @@ -205,6 +207,8 @@ + + diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index b3e433e3..c68099eb 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -706,7 +706,6 @@ class Domains extends ApiCommand implements ResourceEntity Database::pexecute($ins_stmt, $ins_data, true, true); $domainid = Database::lastInsertId(); $ins_data['id'] = $domainid; - $domain_ins_data = $ins_data; unset($ins_data); $upd_stmt = Database::prepare(" @@ -747,7 +746,12 @@ class Domains extends ApiCommand implements ResourceEntity inserttask('4'); $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] added domain '" . $domain . "'"); - return $this->response(200, "successfull", $domain_ins_data); + + $json_result = Domains::getLocal($this->getUserData(), array( + 'domainname' => $domain + ))->get(); + $result = json_decode($json_result, true)['data']; + return $this->response(200, "successfull", $result); } } throw new Exception("No more resources available", 406); diff --git a/lib/classes/api/commands/class.SubDomains.php b/lib/classes/api/commands/class.SubDomains.php index cade0a69..35d8e684 100644 --- a/lib/classes/api/commands/class.SubDomains.php +++ b/lib/classes/api/commands/class.SubDomains.php @@ -330,7 +330,7 @@ class SubDomains extends ApiCommand implements ResourceEntity $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] added subdomain '" . $completedomain . "'"); - $json_result = Subdomains::getLocal($this->getUserData(), array( + $json_result = SubDomains::getLocal($this->getUserData(), array( 'id' => $subdomain_id ))->get(); $result = json_decode($json_result, true)['data']; @@ -597,23 +597,9 @@ class SubDomains extends ApiCommand implements ResourceEntity inserttask('4'); // reduce subdomain-usage-counter - $stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_CUSTOMERS . "` - SET `subdomains_used` = `subdomains_used` - 1 - WHERE `customerid` = :customerid - "); - Database::pexecute($stmt, array( - "customerid" => $customer_id - ), true, true); + Customers::decreaseUsage($customer_id, 'subdomains_used'); // update admin usage - $stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_ADMINS . "` - SET `subdomains_used` = `subdomains_used` - 1 - WHERE `adminid` = :adminid - "); - Database::pexecute($stmt, array( - "adminid" => ($this->isAdmin() ? $customer['adminid'] : $this->getUserDetail('adminid')) - ), true, true); + Admins::decreaseUsage(($this->isAdmin() ? $customer['adminid'] : $this->getUserDetail('adminid')), 'subdomains_used'); $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_WARNING, "[API] deleted subdomain '" . $result['domain'] . "'"); return $this->response(200, "successfull", $result); diff --git a/phpunit.xml b/phpunit.xml index aac6ff31..391c3a47 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -11,8 +11,9 @@ tests/Global tests/Admins tests/Customers - tests/SubDomains tests/IpsAndPorts + tests/Domains + tests/SubDomains tests/Ftps From 02616d3080af1e922fdeb747d1cb6690a52224f2 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 1 Mar 2018 16:46:47 +0100 Subject: [PATCH 097/746] minor fixes and first tests for Domains-ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- install/scripts/config-services.php | 1 - lib/classes/api/commands/class.Domains.php | 8 -- .../function.getIpPortCombinations.php | 11 ++- tests/Domains/DomainsTest.php | 87 +++++++++++++++++++ 4 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 tests/Domains/DomainsTest.php diff --git a/install/scripts/config-services.php b/install/scripts/config-services.php index 1799ec4e..c0c84aec 100755 --- a/install/scripts/config-services.php +++ b/install/scripts/config-services.php @@ -69,7 +69,6 @@ class ConfigServicesCmd extends CmdLineHandler self::println("--daemon\t\tWhen running --apply you can specify a daemon. This will be the only service that gets configured"); self::println("\t\t\tExample: --apply=/path/to/my-config.json --daemon=apache24"); self::println(""); - self::println(""); self::println("--import-settings\tImport settings from another froxlor installation. This should be done prior to running --apply or alternatively in the same command together."); self::println("\t\t\tExample: --import-settings=/path/to/Froxlor_settings-[version]-[dbversion]-[date].json"); self::println(""); diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index c68099eb..5db78a46 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -183,10 +183,6 @@ class Domains extends ApiCommand implements ResourceEntity ))->get(); $customer = json_decode($json_result, true)['data']; - if (empty($customer) || $customer['customerid'] != $customerid) { - standard_error('customerdoesntexist', '', true); - } - if ($this->getUserDetail('customers_see_all') == '1') { $admin_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_ADMINS . "` @@ -194,10 +190,6 @@ class Domains extends ApiCommand implements ResourceEntity $admin = Database::pexecute_first($admin_stmt, array( 'adminid' => $adminid ), true, true); - - if (empty($admin) || $admin['adminid'] != $adminid) { - standard_error('admindoesntexist', '', true); - } } else { $adminid = $this->getUserDetail('adminid'); $admin = $this->getUserData(); diff --git a/lib/functions/froxlor/function.getIpPortCombinations.php b/lib/functions/froxlor/function.getIpPortCombinations.php index 1a0c75f6..b0a2346f 100644 --- a/lib/functions/froxlor/function.getIpPortCombinations.php +++ b/lib/functions/froxlor/function.getIpPortCombinations.php @@ -26,13 +26,12 @@ function getIpPortCombinations($ssl = false) { if ($userinfo['ip'] != '-1') { $admin_ip_stmt = Database::prepare(" - SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :ipid + SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = IN (:ipid) "); - $admin_ip = Database::pexecute_first($admin_ip_stmt, array('ipid' => $userinfo['ip'])); - - $additional_conditions_array[] = "`ip` = :adminip"; - $additional_conditions_params['adminip'] = $admin_ip['ip']; - $admin_ip = null; + $myips = implode(",", json_decode($userinfo['ip'], true)); + Database::pexecute($admin_ip_stmt, array('ipid' => $myips)); + $additional_conditions_array[] = "`ip` IN (:adminips)"; + $additional_conditions_params['adminips'] = $myips; } if ($ssl !== null) { diff --git a/tests/Domains/DomainsTest.php b/tests/Domains/DomainsTest.php new file mode 100644 index 00000000..a196c7ef --- /dev/null +++ b/tests/Domains/DomainsTest.php @@ -0,0 +1,87 @@ + 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $data = [ + 'domain' => 'test.local', + 'customerid' => $customer_userdata['customerid'] + ]; + $json_result = Domains::getLocal($admin_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals($customer_userdata['documentroot'].'test.local/', $result['documentroot']); + } + + /** + * @depends testAdminDomainsAdd + */ + public function testAdminDomainsList() + { + global $admin_userdata; + $json_result = Domains::getLocal($admin_userdata)->list(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(1, $result['count']); + $this->assertEquals('test.local', $result['list'][0]['domain']); + } + + public function testAdminDomainsAddSysHostname() + { + global $admin_userdata; + $data = [ + 'domain' => 'dev.froxlor.org', + 'customerid' => 1 + ]; + $this->expectExceptionMessage('The server-hostname cannot be used as customer-domain.'); + $json_result = Domains::getLocal($admin_userdata, $data)->add(); + } + + public function testAdminDomainsAddNoPunycode() + { + global $admin_userdata; + $data = [ + 'domain' => 'xn--asdasd.tld', + 'customerid' => 1 + ]; + $this->expectExceptionMessage('You must not specify punycode (IDNA). The domain will automatically be converted'); + $json_result = Domains::getLocal($admin_userdata, $data)->add(); + } + + public function testAdminDomainsAddInvalidDomain() + { + global $admin_userdata; + $data = [ + 'domain' => 'dom?*ain.tld', + 'customerid' => 1 + ]; + $this->expectExceptionMessage("Wrong Input in Field 'Domain'"); + $json_result = Domains::getLocal($admin_userdata, $data)->add(); + } + + /** + * @depends testAdminDomainsList + */ + public function testAdminDomainsDelete() + { + global $admin_userdata; + $data = [ + 'domainname' => 'test.local', + 'delete_mainsubdomains' => 1 + ]; + $json_result = Domains::getLocal($admin_userdata, $data)->delete(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('test.local', $result['domain']); + } +} From 601d16b17c7fa1f4c3de923762191a15bdf44cce Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 1 Mar 2018 19:41:57 +0100 Subject: [PATCH 098/746] minor fixes in Domains.add; added more tests Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Domains.php | 7 ++- tests/Domains/DomainsTest.php | 60 ++++++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index 5db78a46..b4cce7fe 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -114,7 +114,7 @@ class Domains extends ApiCommand implements ResourceEntity */ public function add() { - if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { + if ($this->isAdmin()) { if ($this->getUserDetail('domains_used') < $this->getUserDetail('domains') || $this->getUserDetail('domains') == '-1') { // parameters @@ -325,7 +325,10 @@ class Domains extends ApiCommand implements ResourceEntity } $ipandports = array(); - if (! empty($p_ipandport) && ! is_array($p_ipandports)) { + if (! empty($p_ipandports) && is_numeric($p_ipandports)) { + $p_ipandports = array($p_ipandports); + } + if (! empty($p_ipandports) && ! is_array($p_ipandports)) { $p_ipandports = unserialize($p_ipandports); } diff --git a/tests/Domains/DomainsTest.php b/tests/Domains/DomainsTest.php index a196c7ef..269c5ba4 100644 --- a/tests/Domains/DomainsTest.php +++ b/tests/Domains/DomainsTest.php @@ -37,6 +37,66 @@ class DomainsTest extends TestCase $this->assertEquals('test.local', $result['list'][0]['domain']); } + /** + * @depends testAdminDomainsAdd + */ + public function testResellerDomainsList() + { + global $admin_userdata; + // get reseller + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller' + ))->get(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + $json_result = Domains::getLocal($reseller_userdata)->list(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(0, $result['count']); + } + + public function testResellerDomainsAddWithCanEditPhpSettingsDefaultIp() + { + global $admin_userdata; + // get reseller + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller' + ))->get(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + $reseller_userdata['caneditphpsettings'] = 1; + $data = [ + 'domain' => 'test2.local', + 'customerid' => 1 + ]; + // the reseller is not allowed to use the default ip/port + $this->expectExceptionMessage("The ip/port combination you have chosen doesn't exist."); + Domains::getLocal($reseller_userdata, $data)->add(); + } + + public function testResellerDomainsAddWithCanEditPhpSettingsAllowedIp() + { + global $admin_userdata; + // first, allow reseller access to ip #3 + Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller', + 'ipaddress' => 3 + ))->update(); + // get reseller + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller' + ))->get(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + $data = [ + 'domain' => 'test2.local', + 'customerid' => 1, + 'ipandport' => 3 + ]; + $json_result = Domains::getLocal($reseller_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('test2.local', $result['domain']); + } + public function testAdminDomainsAddSysHostname() { global $admin_userdata; From aeb8655cc3f00075a2296486051d18186d7ac542 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 1 Mar 2018 19:59:27 +0100 Subject: [PATCH 099/746] fix sql query in Domains.get; minor fixes in Domains.update; first unit-test for Domains.update Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Domains.php | 19 +++++++++++++++++-- tests/Domains/DomainsTest.php | 17 ++++++++++++++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index b4cce7fe..89596c96 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -87,7 +87,7 @@ class Domains extends ApiCommand implements ResourceEntity FROM `" . TABLE_PANEL_DOMAINS . "` `d` LEFT JOIN `" . TABLE_PANEL_CUSTOMERS . "` `c` USING(`customerid`) WHERE `d`.`parentdomainid` = '0' - AND " . ($id > 0 ? "`d`.`id` = :iddn" : "`d`.`domain` = :iddn") . ($no_std_subdomain ? ' AND `d.`id` <> `c`.`standardsubdomain`' : '') . ($this->getUserDetail('customers_see_all') ? '' : " AND `d`.`adminid` = :adminid")); + AND " . ($id > 0 ? "`d`.`id` = :iddn" : "`d`.`domain` = :iddn") . ($no_std_subdomain ? ' AND `d`.`id` <> `c`.`standardsubdomain`' : '') . ($this->getUserDetail('customers_see_all') ? '' : " AND `d`.`adminid` = :adminid")); $params = array( 'iddn' => ($id <= 0 ? $domainname : $id) ); @@ -818,7 +818,7 @@ class Domains extends ApiCommand implements ResourceEntity $letsencrypt = $this->getParam('letsencrypt', true, $result['letsencrypt']); $p_ssl_ipandports = $this->getParam('ssl_ipandport', true, array()); $http2 = $this->getParam('http2', true, $result['http2']); - $hsts_maxage = $this->getParam('hsts_maxage', true, $result['hsts_maxage']); + $hsts_maxage = $this->getParam('hsts_maxage', true, $result['hsts']); $hsts_sub = $this->getParam('hsts_sub', true, $result['hsts_sub']); $hsts_preload = $this->getParam('hsts_preload', true, $result['hsts_preload']); $ocsp_stapling = $this->getParam('ocsp_stapling', true, $result['ocsp_stapling']); @@ -985,6 +985,9 @@ class Domains extends ApiCommand implements ResourceEntity } $ipandports = array(); + if (! empty($p_ipandports) && is_numeric($p_ipandports)) { + $p_ipandports = array($p_ipandports); + } if (! empty($p_ipandports) && ! is_array($p_ipandports)) { $p_ipandports = unserialize($p_ipandports); } @@ -1007,6 +1010,18 @@ class Domains extends ApiCommand implements ResourceEntity $ipandports[] = $ipandport; } } + } else { + // set currently used ip's + $ipsresult_stmt = Database::prepare(" + SELECT `id_ipandports` FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :id + "); + Database::pexecute($ipsresult_stmt, array( + 'id' => $result['id'] + )); + $usedips = array(); + while ($ipsresultrow = $ipsresult_stmt->fetch(PDO::FETCH_ASSOC)) { + $ipandports[] = $ipsresultrow['id_ipandports']; + } } if (Settings::Get('system.use_ssl') == '1' && ! empty($p_ssl_ipandports)) { diff --git a/tests/Domains/DomainsTest.php b/tests/Domains/DomainsTest.php index 269c5ba4..825e0af9 100644 --- a/tests/Domains/DomainsTest.php +++ b/tests/Domains/DomainsTest.php @@ -131,7 +131,22 @@ class DomainsTest extends TestCase } /** - * @depends testAdminDomainsList + * @depends testAdminDomainsAdd + */ + public function testAdminDomainsUpdate() + { + global $admin_userdata; + $data = [ + 'domainname' => 'test.local', + 'email_only' => 1 + ]; + $json_result = Domains::getLocal($admin_userdata, $data)->update(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(1, $result['email_only']); + } + + /** + * @depends testAdminDomainsUpdate */ public function testAdminDomainsDelete() { From 594512404f1fd20e2f7126c4aab5614c6871ca58 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Fri, 2 Mar 2018 15:24:46 +0100 Subject: [PATCH 100/746] optimized CustomersTest and DomainsTest; minor fixes in SubDomains-ApiCommand; added more tests for SubDomains-Command Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.SubDomains.php | 8 +- tests/Customers/CustomersTest.php | 8 -- tests/Domains/DomainsTest.php | 4 +- tests/SubDomains/SubDomainsTest.php | 114 ++++++++++++++++++ 4 files changed, 120 insertions(+), 14 deletions(-) diff --git a/lib/classes/api/commands/class.SubDomains.php b/lib/classes/api/commands/class.SubDomains.php index 35d8e684..e5dcbd71 100644 --- a/lib/classes/api/commands/class.SubDomains.php +++ b/lib/classes/api/commands/class.SubDomains.php @@ -388,7 +388,7 @@ class SubDomains extends ApiCommand implements ResourceEntity } else { $result_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_DOMAINS . "` - WHERE " . ($id > 0 ? "`id` = :iddn" : "`domainname` = :iddn")); + WHERE " . ($id > 0 ? "`id` = :iddn" : "`domain` = :iddn")); $params = array( 'iddn' => ($id <= 0 ? $domainname : $id) ); @@ -398,8 +398,8 @@ class SubDomains extends ApiCommand implements ResourceEntity throw new Exception("You cannot access this resource", 405); } $result_stmt = Database::prepare(" - SELECT `id`, `customerid`, `domain`, `documentroot`, `isemaildomain`, `parentdomainid`, `aliasdomain` FROM `" . TABLE_PANEL_DOMAINS . "` - WHERE `customerid`= :customerid AND " . ($id > 0 ? "`id` = :iddn" : "`domainname` = :iddn")); + SELECT `id`, `customerid`, `domain`, `documentroot`, `isemaildomain`, `parentdomainid`, `aliasdomain`, `caneditdomain` FROM `" . TABLE_PANEL_DOMAINS . "` + WHERE `customerid`= :customerid AND " . ($id > 0 ? "`id` = :iddn" : "`domain` = :iddn")); $params = array( 'customerid' => $this->getUserDetail('customerid'), 'iddn' => ($id <= 0 ? $domainname : $id) @@ -427,7 +427,7 @@ class SubDomains extends ApiCommand implements ResourceEntity $customerid = $this->getParam('customerid', true, 0); $loginname = $this->getParam('loginname', true, ''); - if (! empty($customer_id) || ! empty($loginname)) { + if (! empty($customerid) || ! empty($loginname)) { $json_result = Customers::getLocal($this->getUserData(), array( 'id' => $customerid, 'loginname' => $loginname diff --git a/tests/Customers/CustomersTest.php b/tests/Customers/CustomersTest.php index 338c6bef..b87145d1 100644 --- a/tests/Customers/CustomersTest.php +++ b/tests/Customers/CustomersTest.php @@ -44,14 +44,6 @@ class CustomersTest extends TestCase $json_result = Customers::getLocal($admin_userdata, $data)->add(); $result = json_decode($json_result, true)['data']; - $customer_id = $result['customerid']; - - // get customer and check results - $json_result = Customers::getLocal($admin_userdata, array( - 'id' => $customer_id - ))->get(); - $result = json_decode($json_result, true)['data']; - $this->assertEquals(1, $result['customerid']); $this->assertEquals('test@froxlor.org', $result['email']); $this->assertEquals(1337, $result['customernumber']); diff --git a/tests/Domains/DomainsTest.php b/tests/Domains/DomainsTest.php index 825e0af9..8cc00c4b 100644 --- a/tests/Domains/DomainsTest.php +++ b/tests/Domains/DomainsTest.php @@ -116,7 +116,7 @@ class DomainsTest extends TestCase 'customerid' => 1 ]; $this->expectExceptionMessage('You must not specify punycode (IDNA). The domain will automatically be converted'); - $json_result = Domains::getLocal($admin_userdata, $data)->add(); + Domains::getLocal($admin_userdata, $data)->add(); } public function testAdminDomainsAddInvalidDomain() @@ -127,7 +127,7 @@ class DomainsTest extends TestCase 'customerid' => 1 ]; $this->expectExceptionMessage("Wrong Input in Field 'Domain'"); - $json_result = Domains::getLocal($admin_userdata, $data)->add(); + Domains::getLocal($admin_userdata, $data)->add(); } /** diff --git a/tests/SubDomains/SubDomainsTest.php b/tests/SubDomains/SubDomainsTest.php index 6388dc21..26e0a542 100644 --- a/tests/SubDomains/SubDomainsTest.php +++ b/tests/SubDomains/SubDomainsTest.php @@ -8,6 +8,45 @@ use PHPUnit\Framework\TestCase; */ class SubDomainsTest extends TestCase { + public function testCustomerSubDomainsAdd() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'subdomain' => 'mysub', + 'domain' => 'test2.local' + ]; + $json_result = SubDomains::getLocal($customer_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('mysub.test2.local', $result['domain']); + } + + public function testResellerSubDomainsAdd() + { + global $admin_userdata; + // get reseller + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller' + ))->get(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + + $data = [ + 'subdomain' => 'mysub2', + 'domain' => 'test2.local', + 'customer_id' => 1 + ]; + $json_result = SubDomains::getLocal($reseller_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('mysub2.test2.local', $result['domain']); + } + public function testCustomerSubDomainsAddNoPunycode() { global $admin_userdata; @@ -61,4 +100,79 @@ class SubDomainsTest extends TestCase $this->expectExceptionMessage("Wrong Input in Field 'Domain'"); SubDomains::getLocal($customer_userdata, $data)->add(); } + + /** + * @depends testCustomerSubDomainsAdd + */ + public function testAdminSubDomainsGet() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'domainname' => 'mysub.test2.local' + ]; + $json_result = SubDomains::getLocal($admin_userdata, $data)->get(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('mysub.test2.local', $result['domain']); + $this->assertEquals(1, $result['customerid']); + } + + public function testCustomerSubDomainsList() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $json_result = SubDomains::getLocal($customer_userdata)->list(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(3, $result['count']); + } + + public function testResellerSubDomainsList() + { + global $admin_userdata; + // get reseller + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller' + ))->get(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + $json_result = SubDomains::getLocal($reseller_userdata)->list(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(3, $result['count']); + } + + public function testAdminSubDomainsListWithCustomer() + { + global $admin_userdata; + $json_result = SubDomains::getLocal($admin_userdata, ['loginname' => 'test1'])->list(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(3, $result['count']); + } + + /** + * @depends testCustomerSubDomainsList + */ + public function testCustomerSubDomainsDelete() + { + global $admin_userdata; + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $json_result = SubDomains::getLocal($customer_userdata, ['domainname' => 'mysub.test2.local'])->delete(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('mysub.test2.local', $result['domain']); + $this->assertEquals($customer_userdata['customerid'], $result['customerid']); + } } From cadb6618ec2b8da2f2a194090e3aa9588defa1c4 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Fri, 2 Mar 2018 17:22:47 +0100 Subject: [PATCH 101/746] list() is a reserved php keyword, changed ApiCommand::list() to ApiCommand::listing() Signed-off-by: Michael Kaufmann (d00p) --- admin_phpsettings.php | 4 ++-- lib/classes/api/commands/class.Admins.php | 2 +- lib/classes/api/commands/class.Customers.php | 2 +- lib/classes/api/commands/class.Domains.php | 2 +- lib/classes/api/commands/class.FpmDaemons.php | 2 +- lib/classes/api/commands/class.Ftps.php | 6 +++--- lib/classes/api/commands/class.IpsAndPorts.php | 2 +- lib/classes/api/commands/class.Mysqls.php | 6 +++--- lib/classes/api/commands/class.PhpSettings.php | 2 +- lib/classes/api/commands/class.SubDomains.php | 6 +++--- lib/classes/api/interface.ResourceEntity.php | 2 +- phpdox.xml | 4 ++-- tests/Admins/AdminsTest.php | 4 ++-- tests/Customers/CustomersTest.php | 6 +++--- tests/Domains/DomainsTest.php | 4 ++-- tests/Ftps/FtpsTest.php | 8 ++++---- tests/IpsAndPorts/IpsAndPortsTest.php | 6 +++--- tests/SubDomains/SubDomainsTest.php | 6 +++--- 18 files changed, 37 insertions(+), 37 deletions(-) diff --git a/admin_phpsettings.php b/admin_phpsettings.php index 6eee297a..6192fb42 100644 --- a/admin_phpsettings.php +++ b/admin_phpsettings.php @@ -30,7 +30,7 @@ if ($page == 'overview') { if ($action == '') { try { - $json_result = PhpSettings::getLocal($userinfo)->list(); + $json_result = PhpSettings::getLocal($userinfo)->listing(); } catch (Exception $e) { dynamic_error($e->getMessage()); } @@ -173,7 +173,7 @@ if ($page == 'overview') { if ($action == '') { try { - $json_result = FpmDaemons::getLocal($userinfo)->list(); + $json_result = FpmDaemons::getLocal($userinfo)->listing(); } catch (Exception $e) { dynamic_error($e->getMessage()); } diff --git a/lib/classes/api/commands/class.Admins.php b/lib/classes/api/commands/class.Admins.php index 11361603..6a6b8300 100644 --- a/lib/classes/api/commands/class.Admins.php +++ b/lib/classes/api/commands/class.Admins.php @@ -25,7 +25,7 @@ class Admins extends ApiCommand implements ResourceEntity * @throws Exception * @return array count|list */ - public function list() + public function listing() { if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] list admins"); diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index d2a102af..4191a835 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -24,7 +24,7 @@ class Customers extends ApiCommand implements ResourceEntity * @access admin * @return array count|list */ - public function list() + public function listing() { if ($this->isAdmin()) { $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] list customers"); diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index 89596c96..3ea17c58 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -25,7 +25,7 @@ class Domains extends ApiCommand implements ResourceEntity * @throws Exception * @return array count|list */ - public function list() + public function listing() { if ($this->isAdmin()) { $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] list domains"); diff --git a/lib/classes/api/commands/class.FpmDaemons.php b/lib/classes/api/commands/class.FpmDaemons.php index fd7b30fa..cd04990d 100644 --- a/lib/classes/api/commands/class.FpmDaemons.php +++ b/lib/classes/api/commands/class.FpmDaemons.php @@ -25,7 +25,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity * @throws Exception * @return array count|list */ - public function list() + public function listing() { if ($this->isAdmin()) { $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] list fpm-daemons"); diff --git a/lib/classes/api/commands/class.Ftps.php b/lib/classes/api/commands/class.Ftps.php index 42cf0705..f2909440 100644 --- a/lib/classes/api/commands/class.Ftps.php +++ b/lib/classes/api/commands/class.Ftps.php @@ -289,7 +289,7 @@ class Ftps extends ApiCommand implements ResourceEntity if ($this->getUserDetail('customers_see_all') == false) { // if it's a reseller or an admin who cannot see all customers, we need to check // whether the database belongs to one of his customers - $json_result = Customers::getLocal($this->getUserData())->list(); + $json_result = Customers::getLocal($this->getUserData())->listing(); $custom_list_result = json_decode($json_result, true)['data']['list']; $customer_ids = array(); foreach ($custom_list_result as $customer) { @@ -453,7 +453,7 @@ class Ftps extends ApiCommand implements ResourceEntity * @throws Exception * @return array count|list */ - public function list() + public function listing() { if ($this->isAdmin()) { // if we're an admin, list all ftp-users of all the admins customers @@ -470,7 +470,7 @@ class Ftps extends ApiCommand implements ResourceEntity json_decode($json_result, true)['data'] ); } else { - $json_result = Customers::getLocal($this->getUserData())->list(); + $json_result = Customers::getLocal($this->getUserData())->listing(); $custom_list_result = json_decode($json_result, true)['data']['list']; } $customer_ids = array(); diff --git a/lib/classes/api/commands/class.IpsAndPorts.php b/lib/classes/api/commands/class.IpsAndPorts.php index 81976d39..f02ded1c 100644 --- a/lib/classes/api/commands/class.IpsAndPorts.php +++ b/lib/classes/api/commands/class.IpsAndPorts.php @@ -25,7 +25,7 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity * @throws Exception * @return array count|list */ - public function list() + public function listing() { if ($this->isAdmin() && ($this->getUserDetail('change_serversettings') || ! empty($this->getUserDetail('ip')))) { $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] list ips and ports"); diff --git a/lib/classes/api/commands/class.Mysqls.php b/lib/classes/api/commands/class.Mysqls.php index 8022562d..f783b34d 100644 --- a/lib/classes/api/commands/class.Mysqls.php +++ b/lib/classes/api/commands/class.Mysqls.php @@ -230,7 +230,7 @@ class Mysqls extends ApiCommand implements ResourceEntity if ($this->getUserDetail('customers_see_all') != 1) { // if it's a reseller or an admin who cannot see all customers, we need to check // whether the database belongs to one of his customers - $json_result = Customers::getLocal($this->getUserData())->list(); + $json_result = Customers::getLocal($this->getUserData())->listing(); $custom_list_result = json_decode($json_result, true)['data']['list']; $customer_ids = array(); foreach ($custom_list_result as $customer) { @@ -425,7 +425,7 @@ class Mysqls extends ApiCommand implements ResourceEntity * @throws Exception * @return array count|list */ - public function list() + public function listing() { $result = array(); $dbserver = $this->getParam('mysql_server', true, - 1); @@ -444,7 +444,7 @@ class Mysqls extends ApiCommand implements ResourceEntity json_decode($json_result, true)['data'] ); } else { - $json_result = Customers::getLocal($this->getUserData())->list(); + $json_result = Customers::getLocal($this->getUserData())->listing(); $custom_list_result = json_decode($json_result, true)['data']['list']; } $customer_ids = array(); diff --git a/lib/classes/api/commands/class.PhpSettings.php b/lib/classes/api/commands/class.PhpSettings.php index 6d26a560..cc930714 100644 --- a/lib/classes/api/commands/class.PhpSettings.php +++ b/lib/classes/api/commands/class.PhpSettings.php @@ -25,7 +25,7 @@ class PhpSettings extends ApiCommand implements ResourceEntity * @throws Exception * @return array count|list */ - public function list() + public function listing() { if ($this->isAdmin()) { $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] list php-configs"); diff --git a/lib/classes/api/commands/class.SubDomains.php b/lib/classes/api/commands/class.SubDomains.php index e5dcbd71..eb1ea0d7 100644 --- a/lib/classes/api/commands/class.SubDomains.php +++ b/lib/classes/api/commands/class.SubDomains.php @@ -367,7 +367,7 @@ class SubDomains extends ApiCommand implements ResourceEntity if ($this->getUserDetail('customers_see_all') != 1) { // if it's a reseller or an admin who cannot see all customers, we need to check // whether the database belongs to one of his customers - $json_result = Customers::getLocal($this->getUserData())->list(); + $json_result = Customers::getLocal($this->getUserData())->listing(); $custom_list_result = json_decode($json_result, true)['data']['list']; $customer_ids = array(); foreach ($custom_list_result as $customer) { @@ -419,7 +419,7 @@ class SubDomains extends ApiCommand implements ResourceEntity throw new Exception("Not available yet.", 501); } - public function list() + public function listing() { if ($this->isAdmin()) { // if we're an admin, list all databases of all the admins customers @@ -436,7 +436,7 @@ class SubDomains extends ApiCommand implements ResourceEntity json_decode($json_result, true)['data'] ); } else { - $json_result = Customers::getLocal($this->getUserData())->list(); + $json_result = Customers::getLocal($this->getUserData())->listing(); $custom_list_result = json_decode($json_result, true)['data']['list']; } $customer_ids = array(); diff --git a/lib/classes/api/interface.ResourceEntity.php b/lib/classes/api/interface.ResourceEntity.php index b10713a8..59d6c398 100644 --- a/lib/classes/api/interface.ResourceEntity.php +++ b/lib/classes/api/interface.ResourceEntity.php @@ -18,7 +18,7 @@ interface ResourceEntity { - public function list(); + public function listing(); public function get(); diff --git a/phpdox.xml b/phpdox.xml index 813dbd4a..a090c9ec 100644 --- a/phpdox.xml +++ b/phpdox.xml @@ -1,6 +1,6 @@ - - + + diff --git a/tests/Admins/AdminsTest.php b/tests/Admins/AdminsTest.php index 15c81e3c..c6a63308 100644 --- a/tests/Admins/AdminsTest.php +++ b/tests/Admins/AdminsTest.php @@ -77,7 +77,7 @@ class AdminsTest extends TestCase { global $admin_userdata; - $json_result = Admins::getLocal($admin_userdata)->list(); + $json_result = Admins::getLocal($admin_userdata)->listing(); $result = json_decode($json_result, true)['data']; $this->assertEquals(2, $result['count']); } @@ -114,7 +114,7 @@ class AdminsTest extends TestCase $this->expectExceptionCode(403); $this->expectExceptionMessage("Not allowed to execute given command."); - Admins::getLocal($reseller_userdata)->list(); + Admins::getLocal($reseller_userdata)->listing(); } public function testAdminAdminsUnlock() diff --git a/tests/Customers/CustomersTest.php b/tests/Customers/CustomersTest.php index b87145d1..3d7592c6 100644 --- a/tests/Customers/CustomersTest.php +++ b/tests/Customers/CustomersTest.php @@ -88,7 +88,7 @@ class CustomersTest extends TestCase { global $admin_userdata; - $json_result = Customers::getLocal($admin_userdata)->list(); + $json_result = Customers::getLocal($admin_userdata)->listing(); $result = json_decode($json_result, true)['data']; $this->assertEquals(1, $result['count']); } @@ -105,7 +105,7 @@ class CustomersTest extends TestCase ))->get(); $reseller_userdata = json_decode($json_result, true)['data']; $reseller_userdata['adminsession'] = 1; - $json_result = Customers::getLocal($reseller_userdata)->list(); + $json_result = Customers::getLocal($reseller_userdata)->listing(); $result = json_decode($json_result, true)['data']; $this->assertEquals(0, $result['count']); } @@ -125,7 +125,7 @@ class CustomersTest extends TestCase $this->expectExceptionCode(403); $this->expectExceptionMessage("Not allowed to execute given command."); - $json_result = Customers::getLocal($customer_userdata)->list(); + $json_result = Customers::getLocal($customer_userdata)->listing(); } /** diff --git a/tests/Domains/DomainsTest.php b/tests/Domains/DomainsTest.php index 8cc00c4b..ffdaf42f 100644 --- a/tests/Domains/DomainsTest.php +++ b/tests/Domains/DomainsTest.php @@ -31,7 +31,7 @@ class DomainsTest extends TestCase public function testAdminDomainsList() { global $admin_userdata; - $json_result = Domains::getLocal($admin_userdata)->list(); + $json_result = Domains::getLocal($admin_userdata)->listing(); $result = json_decode($json_result, true)['data']; $this->assertEquals(1, $result['count']); $this->assertEquals('test.local', $result['list'][0]['domain']); @@ -49,7 +49,7 @@ class DomainsTest extends TestCase ))->get(); $reseller_userdata = json_decode($json_result, true)['data']; $reseller_userdata['adminsession'] = 1; - $json_result = Domains::getLocal($reseller_userdata)->list(); + $json_result = Domains::getLocal($reseller_userdata)->listing(); $result = json_decode($json_result, true)['data']; $this->assertEquals(0, $result['count']); } diff --git a/tests/Ftps/FtpsTest.php b/tests/Ftps/FtpsTest.php index d9d185a3..f4dc2ed2 100644 --- a/tests/Ftps/FtpsTest.php +++ b/tests/Ftps/FtpsTest.php @@ -77,7 +77,7 @@ class FtpsTest extends TestCase { global $admin_userdata; - $json_result = Ftps::getLocal($admin_userdata)->list(); + $json_result = Ftps::getLocal($admin_userdata)->listing(); $result = json_decode($json_result, true)['data']; $this->assertEquals(1, $result['count']); } @@ -86,7 +86,7 @@ class FtpsTest extends TestCase { global $admin_userdata; - $json_result = Ftps::getLocal($admin_userdata, array('loginname' => 'test1'))->list(); + $json_result = Ftps::getLocal($admin_userdata, array('loginname' => 'test1'))->listing(); $result = json_decode($json_result, true)['data']; $this->assertEquals(1, $result['count']); $this->assertEquals('test1', $result['list'][0]['username']); @@ -101,7 +101,7 @@ class FtpsTest extends TestCase ))->get(); $reseller_userdata = json_decode($json_result, true)['data']; $reseller_userdata['adminsession'] = 1; - $json_result = Ftps::getLocal($reseller_userdata)->list(); + $json_result = Ftps::getLocal($reseller_userdata)->listing(); $result = json_decode($json_result, true)['data']; $this->assertEquals(1, $result['count']); $this->assertEquals('test1', $result['list'][0]['username']); @@ -115,7 +115,7 @@ class FtpsTest extends TestCase 'loginname' => 'test1' ))->get(); $customer_userdata = json_decode($json_result, true)['data']; - $json_result = Ftps::getLocal($customer_userdata)->list(); + $json_result = Ftps::getLocal($customer_userdata)->listing(); $result = json_decode($json_result, true)['data']; $this->assertEquals(1, $result['count']); $this->assertEquals('test1', $result['list'][0]['username']); diff --git a/tests/IpsAndPorts/IpsAndPortsTest.php b/tests/IpsAndPorts/IpsAndPortsTest.php index 95590a17..9b3ec579 100644 --- a/tests/IpsAndPorts/IpsAndPortsTest.php +++ b/tests/IpsAndPorts/IpsAndPortsTest.php @@ -11,7 +11,7 @@ class IpsAndPortsTest extends TestCase public function testAdminIpsAndPortsList() { global $admin_userdata; - $json_result = IpsAndPorts::getLocal($admin_userdata)->list(); + $json_result = IpsAndPorts::getLocal($admin_userdata)->listing(); $result = json_decode($json_result, true)['data']; $this->assertEquals(1, $result['count']); $this->assertEquals('82.149.225.46', $result['list'][0]['ip']); @@ -29,7 +29,7 @@ class IpsAndPortsTest extends TestCase $reseller_userdata['adminsession'] = 1; $this->expectExceptionCode(403); $this->expectExceptionMessage("Not allowed to execute given command."); - $json_result = IpsAndPorts::getLocal($reseller_userdata)->list(); + $json_result = IpsAndPorts::getLocal($reseller_userdata)->listing(); } public function testAdminIpsAndPortsAdd() @@ -91,7 +91,7 @@ class IpsAndPortsTest extends TestCase ))->update(); $reseller_userdata = json_decode($json_result, true)['data']; $reseller_userdata['adminsession'] = 1; - $json_result = IpsAndPorts::getLocal($reseller_userdata)->list(); + $json_result = IpsAndPorts::getLocal($reseller_userdata)->listing(); $result = json_decode($json_result, true)['data']; $this->assertEquals(1, $result['count']); $this->assertEquals('82.149.225.47', $result['list'][0]['ip']); diff --git a/tests/SubDomains/SubDomainsTest.php b/tests/SubDomains/SubDomainsTest.php index 26e0a542..1e77e43a 100644 --- a/tests/SubDomains/SubDomainsTest.php +++ b/tests/SubDomains/SubDomainsTest.php @@ -132,7 +132,7 @@ class SubDomainsTest extends TestCase 'loginname' => 'test1' ))->get(); $customer_userdata = json_decode($json_result, true)['data']; - $json_result = SubDomains::getLocal($customer_userdata)->list(); + $json_result = SubDomains::getLocal($customer_userdata)->listing(); $result = json_decode($json_result, true)['data']; $this->assertEquals(3, $result['count']); } @@ -146,7 +146,7 @@ class SubDomainsTest extends TestCase ))->get(); $reseller_userdata = json_decode($json_result, true)['data']; $reseller_userdata['adminsession'] = 1; - $json_result = SubDomains::getLocal($reseller_userdata)->list(); + $json_result = SubDomains::getLocal($reseller_userdata)->listing(); $result = json_decode($json_result, true)['data']; $this->assertEquals(3, $result['count']); } @@ -154,7 +154,7 @@ class SubDomainsTest extends TestCase public function testAdminSubDomainsListWithCustomer() { global $admin_userdata; - $json_result = SubDomains::getLocal($admin_userdata, ['loginname' => 'test1'])->list(); + $json_result = SubDomains::getLocal($admin_userdata, ['loginname' => 'test1'])->listing(); $result = json_decode($json_result, true)['data']; $this->assertEquals(3, $result['count']); } From abb94c9189186c4729b15d85c8ca830724f557ac Mon Sep 17 00:00:00 2001 From: JB1985 Date: Fri, 2 Mar 2018 17:28:30 +0100 Subject: [PATCH 102/746] Update german.lng.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Übersetzung Änderung von Lets Encrypt --- lng/german.lng.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lng/german.lng.php b/lng/german.lng.php index 0924c1ac..d5b1ef2e 100644 --- a/lng/german.lng.php +++ b/lng/german.lng.php @@ -1590,7 +1590,7 @@ $lng['admin']['mod_fcgid_umask']['title'] = 'Umask (Standard: 022)'; // Added for let's encrypt $lng['admin']['letsencrypt']['title'] = 'SSL Zertifikat erstellen (Let\'s Encrypt)'; $lng['admin']['letsencrypt']['description'] = 'Holt ein kostenloses Zertifikat von Let\'s Encrypt. Das Zertifikat wird automatisch erstellt und verlängert.
    ACHTUNG: Wenn Wildcards aktiviert sind, wird diese Option automatisch deaktiviert. Dieses Feature befindet sich noch im Test.'; -$lng['customer']['letsencrypt']['title'] = 'Benutze Let\'s Encrypt'; +$lng['customer']['letsencrypt']['title'] = 'SSL Zertifikat erstellen (Let\'s Encrypt)'; $lng['customer']['letsencrypt']['description'] = 'Holt ein kostenloses Zertifikat von Let\'s Encrypt. Das Zertifikat wird automatisch erstellt und verlängert.
    ACHTUNG: Dieses Feature befindet sich noch im Test.'; $lng['error']['sslredirectonlypossiblewithsslipport'] = 'Die Nutzung von Let\'s Encrypt ist nur möglich, wenn die Domain mindestens eine IP/Port - Kombination mit aktiviertem SSL zugewiesen hat.'; $lng['error']['nowildcardwithletsencrypt'] = 'Let\'s Encrypt kann in ACME v1 nicht mit Wildcard-Domains umgehen. Bitte den ServerAlias auf WWW setzen oder deaktivieren'; From 1605d2af07ebd1003b29035ec967331a3cd06e1d Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Fri, 2 Mar 2018 18:25:06 +0100 Subject: [PATCH 103/746] enhance phpdox config Signed-off-by: Michael Kaufmann (d00p) --- build.xml | 2 +- phpdox.xml | 28 +++++++++++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/build.xml b/build.xml index 10713128..25d8b2ab 100644 --- a/build.xml +++ b/build.xml @@ -98,7 +98,7 @@ - + diff --git a/phpdox.xml b/phpdox.xml index a090c9ec..d191037d 100644 --- a/phpdox.xml +++ b/phpdox.xml @@ -1,10 +1,32 @@ - - + + - + + + + + + + + + + + + + + + + + + + From 20eaa7bc08b953cdd22af4aa320c54cefb4079ea Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Fri, 2 Mar 2018 19:36:56 +0100 Subject: [PATCH 104/746] fix missing sql-prepared-statement parameter, fixes #528 Signed-off-by: Michael Kaufmann (d00p) --- scripts/jobs/cron_letsencrypt.php | 1 + scripts/jobs/cron_letsencrypt_v2.php | 1 + 2 files changed, 2 insertions(+) diff --git a/scripts/jobs/cron_letsencrypt.php b/scripts/jobs/cron_letsencrypt.php index 98ee24ce..acfceae0 100644 --- a/scripts/jobs/cron_letsencrypt.php +++ b/scripts/jobs/cron_letsencrypt.php @@ -260,6 +260,7 @@ foreach ($certrows as $certrow) { 'ca' => $return['chain'], 'chain' => $return['chain'], 'csr' => $return['csr'], + 'fullchain' => $return['fullchain'], 'expirationdate' => date('Y-m-d H:i:s', $newcert['validTo_time_t']) )); diff --git a/scripts/jobs/cron_letsencrypt_v2.php b/scripts/jobs/cron_letsencrypt_v2.php index 6e27361b..c56a3e4e 100644 --- a/scripts/jobs/cron_letsencrypt_v2.php +++ b/scripts/jobs/cron_letsencrypt_v2.php @@ -264,6 +264,7 @@ foreach ($certrows as $certrow) { 'ca' => $return['chain'], 'chain' => $return['chain'], 'csr' => $return['csr'], + 'fullchain' => $return['fullchain'], 'expirationdate' => date('Y-m-d H:i:s', $newcert['validTo_time_t']) )); From 7e81b0bb5a0720f3a6450d87629a5411c7d3149f Mon Sep 17 00:00:00 2001 From: Michael Zangl Date: Sat, 3 Mar 2018 11:18:28 +0100 Subject: [PATCH 105/746] Froxlor installer: Use same file path for chmod. --- install/lib/class.FroxlorInstall.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/install/lib/class.FroxlorInstall.php b/install/lib/class.FroxlorInstall.php index 128a7104..1cbbd7ca 100644 --- a/install/lib/class.FroxlorInstall.php +++ b/install/lib/class.FroxlorInstall.php @@ -322,11 +322,12 @@ class FroxlorInstall $userdata .= "?>"; // test if we can store the userdata.inc.php in ../lib - if ($fp = @fopen(dirname(dirname(dirname(__FILE__))) . '/lib/userdata.inc.php', 'w')) { + $userdata_file = dirname(dirname(dirname(__FILE__))) . '/lib/userdata.inc.php'; + if ($fp = @fopen($userdata_file, 'w')) { $result = @fputs($fp, $userdata, strlen($userdata)); @fclose($fp); $content .= $this->_status_message('green', 'OK'); - chmod('../lib/userdata.inc.php', 0440); + chmod($userdata_file, 0440); } elseif ($fp = @fopen('/tmp/userdata.inc.php', 'w')) { $result = @fputs($fp, $userdata, strlen($userdata)); @fclose($fp); From 8642254175096d73b80edbf27774c673ee1f11f0 Mon Sep 17 00:00:00 2001 From: Michael Zangl Date: Sat, 3 Mar 2018 11:48:26 +0100 Subject: [PATCH 106/746] Fix invalidhostname error message. --- lng/english.lng.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lng/english.lng.php b/lng/english.lng.php index a5d33367..fe156bf9 100644 --- a/lng/english.lng.php +++ b/lng/english.lng.php @@ -1648,7 +1648,7 @@ $lng['admin']['usedmax'] = 'Used / Max'; $lng['admin']['used'] = 'Used'; $lng['mysql']['size'] = 'Size'; -$lng['error']['invalidhostname'] = 'Hostname can\'t be empty nor can it consist only of whitespaces'; +$lng['error']['invalidhostname'] = 'Hostname needs to be avalid domain. It can\'t be empty nor can it consist only of whitespaces'; $lng['traffic']['http'] = 'HTTP (MiB)'; $lng['traffic']['ftp'] = 'FTP (MiB)'; From 909c983aec4ce6b89d06014cbe36abaeec5490ae Mon Sep 17 00:00:00 2001 From: Michael Zangl Date: Sat, 3 Mar 2018 11:49:58 +0100 Subject: [PATCH 107/746] Fix invalidhostname error message (de). --- lng/german.lng.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lng/german.lng.php b/lng/german.lng.php index 0924c1ac..b2dbe82b 100644 --- a/lng/german.lng.php +++ b/lng/german.lng.php @@ -1375,7 +1375,7 @@ $lng['admin']['usedmax'] = 'Benutzt / Max.'; $lng['admin']['used'] = 'Benutzt'; $lng['mysql']['size'] = 'Datenbankgröße'; -$lng['error']['invalidhostname'] = 'Hostname darf nicht leer sein oder nur aus Leerzeichen bestehen'; +$lng['error']['invalidhostname'] = 'Hostname muss eine gültige Domain sein. Er darf weder leer sein noch nur aus Leerzeichen bestehen'; $lng['traffic']['http'] = 'HTTP (MiB)'; $lng['traffic']['ftp'] = 'FTP (MiB)'; From 826f1378d2be29e00bad5a44d0f20872b9121092 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sat, 3 Mar 2018 15:55:23 +0100 Subject: [PATCH 108/746] minor fixes to Domains.update + another unit-test Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Domains.php | 66 +++++++++++++++++++--- tests/Domains/DomainsTest.php | 21 +++++++ 2 files changed, 79 insertions(+), 8 deletions(-) diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index 3ea17c58..9b8e9c98 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -822,18 +822,63 @@ class Domains extends ApiCommand implements ResourceEntity $hsts_sub = $this->getParam('hsts_sub', true, $result['hsts_sub']); $hsts_preload = $this->getParam('hsts_preload', true, $result['hsts_preload']); $ocsp_stapling = $this->getParam('ocsp_stapling', true, $result['ocsp_stapling']); + + // count subdomain usage of source-domain + $subdomains_stmt = Database::prepare(" + SELECT COUNT(`id`) AS count FROM `" . TABLE_PANEL_DOMAINS . "` WHERE + `parentdomainid` = :resultid + "); + $subdomains = Database::pexecute_first($subdomains_stmt, array( + 'resultid' => $result['id'] + ), true, true); + $subdomains = $subdomains['count']; + + // count where this domain is alias domain + $alias_check_stmt = Database::prepare(" + SELECT COUNT(`id`) AS count FROM `" . TABLE_PANEL_DOMAINS . "` WHERE + `aliasdomain` = :resultid + "); + $alias_check = Database::pexecute_first($alias_check_stmt, array( + 'resultid' => $result['id'] + ), true, true); + $alias_check = $alias_check['count']; + // count where we are used in email-accounts + $domain_emails_result_stmt = Database::prepare(" + SELECT `email`, `email_full`, `destination`, `popaccountid` AS `number_email_forwarders` + FROM `" . TABLE_MAIL_VIRTUAL . "` WHERE `customerid` = :customerid AND `domainid` = :id + "); + Database::pexecute($domain_emails_result_stmt, array( + 'customerid' => $result['customerid'], + 'id' => $result['id'] + ), true, true); + + $emails = Database::num_rows(); + $email_forwarders = 0; + $email_accounts = 0; + + while ($domain_emails_row = $domain_emails_result_stmt->fetch(PDO::FETCH_ASSOC)) { + if ($domain_emails_row['destination'] != '') { + $domain_emails_row['destination'] = explode(' ', makeCorrectDestination($domain_emails_row['destination'])); + $email_forwarders += count($domain_emails_row['destination']); + if (in_array($domain_emails_row['email_full'], $domain_emails_row['destination'])) { + $email_forwarders -= 1; + $email_accounts ++; + } + } + } + // handle change of customer (move domain from customer to customer) if ($customerid > 0 && $customerid != $result['customerid'] && Settings::Get('panel.allow_domain_change_customer') == '1') { - + // check whether target customer has enough resources $customer_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `customerid` = :customerid AND (`subdomains_used` + :subdomains <= `subdomains` OR `subdomains` = '-1' ) AND (`emails_used` + :emails <= `emails` OR `emails` = '-1' ) AND (`email_forwarders_used` + :forwarders <= `email_forwarders` OR `email_forwarders` = '-1' ) - AND (`email_accounts_used` + :accounts <= `email_accounts` OR `email_accounts` = '-1' ) " . ($this->getUserDetail('customers_see_all') ? '' : " AND `adminid` = :adminid")); - + AND (`email_accounts_used` + :accounts <= `email_accounts` OR `email_accounts` = '-1' ) " . ($this->getUserDetail('customers_see_all') ? '' : " AND `adminid` = :adminid") + ); $params = array( 'customerid' => $customerid, 'subdomains' => $subdomains, @@ -844,13 +889,18 @@ class Domains extends ApiCommand implements ResourceEntity if ($this->getUserDetail('customers_see_all') == '0') { $params['adminid'] = $this->getUserDetail('adminid'); } - - $result = Database::pexecute_first($customer_stmt, $params, true, true); - if (empty($result) || $result['customerid'] != $customerid) { + $customer = Database::pexecute_first($customer_stmt, $params, true, true); + if (empty($customer) || $customer['customerid'] != $customerid) { standard_error('customerdoesntexist', '', true); } } else { $customerid = $result['customerid']; + + // get customer + $json_result = Customers::getLocal($this->getUserData(), array( + 'id' => $customerid + ))->get(); + $customer = json_decode($json_result, true)['data']; } // handle change of admin (move domain from admin to admin) @@ -923,9 +973,9 @@ class Domains extends ApiCommand implements ResourceEntity // If path is empty and 'Use domain name as default value for DocumentRoot path' is enabled in settings, // set default path to subdomain or domain name if (Settings::Get('system.documentroot_use_default_value') == 1) { - $documentroot = makeCorrectDir($result['documentroot'] . '/' . $result['domain']); + $documentroot = makeCorrectDir($customer['documentroot'] . '/' . $result['domain']); } else { - $documentroot = $result['documentroot']; + $documentroot = $customer['documentroot']; } } diff --git a/tests/Domains/DomainsTest.php b/tests/Domains/DomainsTest.php index ffdaf42f..67b0fae8 100644 --- a/tests/Domains/DomainsTest.php +++ b/tests/Domains/DomainsTest.php @@ -148,6 +148,26 @@ class DomainsTest extends TestCase /** * @depends testAdminDomainsUpdate */ + public function testAdminDomainsMoveButUnknownCustomer() + { + global $admin_userdata; + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $data = [ + 'domainname' => 'test.local', + 'customerid' => $customer_userdata['customerid'] + 1 + ]; + Settings::Set('panel.allow_domain_change_customer', 1); + $this->expectExceptionMessage("The customer you have chosen doesn't exist."); + Domains::getLocal($admin_userdata, $data)->update(); + } + + /** + * @depends testAdminDomainsMoveButUnknownCustomer + */ public function testAdminDomainsDelete() { global $admin_userdata; @@ -159,4 +179,5 @@ class DomainsTest extends TestCase $result = json_decode($json_result, true)['data']; $this->assertEquals('test.local', $result['domain']); } + } From 0d7afc5c24b1ad9943d2174a5ae88d58301f3e65 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sat, 3 Mar 2018 21:10:58 +0100 Subject: [PATCH 109/746] fixes in move-domain-to-another-customer functionality in Domains.update Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Domains.php | 23 ++++++++++++---- tests/Domains/DomainsTest.php | 32 +++++++++++++++++++++- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index 9b8e9c98..775cc8e6 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -201,7 +201,7 @@ class Domains extends ApiCommand implements ResourceEntity if (Settings::Get('system.documentroot_use_default_value') == 1) { $path_suffix = '/' . $domain; } - $documentroot = makeCorrectDir($customer['documentroot'] . $path_suffix); + $_documentroot = makeCorrectDir($customer['documentroot'] . $path_suffix); $registration_date = validate($registration_date, 'registration_date', '/^(19|20)\d\d[-](0[1-9]|1[012])[-](0[1-9]|[12][0-9]|3[01])$/', '', array( '0000-00-00', @@ -236,10 +236,10 @@ class Domains extends ApiCommand implements ResourceEntity // set default path to subdomain or domain name if ($documentroot != '') { if (substr($documentroot, 0, 1) != '/' && ! preg_match('/^https?\:\/\//', $documentroot)) { - $documentroot .= '/' . $documentroot; + $documentroot = $_documentroot . '/' . $documentroot; } - } elseif ($documentroot == '' && Settings::Get('system.documentroot_use_default_value') == 1) { - $documentroot = makeCorrectDir($customer['documentroot'] . '/' . $domain); + } else { + $documentroot = $_documentroot; } } else { $isbinddomain = '0'; @@ -251,6 +251,7 @@ class Domains extends ApiCommand implements ResourceEntity $dkim = '0'; $specialsettings = ''; $notryfiles = '0'; + $documentroot = $_documentroot; } if ($this->getUserDetail('caneditphpsettings') == '1' || $this->getUserDetail('change_serversettings') == '1') { @@ -968,7 +969,19 @@ class Domains extends ApiCommand implements ResourceEntity $specialsettings = validate(str_replace("\r\n", "\n", $specialsettings), 'specialsettings', '/^[^\0]*$/', '', array(), true); $documentroot = validate($documentroot, 'documentroot', '', '', array(), true); - + + // when moving customer and no path is specified, update would normally reuse the current document-root + // which would point to the wrong customer, therefore we will re-create that directory + if (!empty($documentroot) && $customerid > 0 && $customerid != $result['customerid'] && Settings::Get('panel.allow_domain_change_customer') == '1') { + if (Settings::Get('system.documentroot_use_default_value') == 1) { + $_documentroot = makeCorrectDir($customer['documentroot'] . '/' . $result['domain']); + } else { + $_documentroot = $customer['documentroot']; + } + // set the customers default docroot + $documentroot = $_documentroot; + } + if ($documentroot == '') { // If path is empty and 'Use domain name as default value for DocumentRoot path' is enabled in settings, // set default path to subdomain or domain name diff --git a/tests/Domains/DomainsTest.php b/tests/Domains/DomainsTest.php index 67b0fae8..c4ee05b8 100644 --- a/tests/Domains/DomainsTest.php +++ b/tests/Domains/DomainsTest.php @@ -165,8 +165,33 @@ class DomainsTest extends TestCase Domains::getLocal($admin_userdata, $data)->update(); } + public function testAdminDomainsMove() + { + global $admin_userdata; + // add new customer + $data = [ + 'new_loginname' => 'test3', + 'email' => 'test3@froxlor.org', + 'firstname' => 'Test', + 'name' => 'Testman', + 'customernumber' => 1339, + 'new_customer_password' => 'h0lYmo1y' + ]; + $json_result = Customers::getLocal($admin_userdata, $data)->add(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'domainname' => 'test.local', + 'customerid' => $customer_userdata['customerid'] + ]; + $json_result =Domains::getLocal($admin_userdata, $data)->update(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals($customer_userdata['customerid'], $result['customerid']); + $this->assertEquals($customer_userdata['documentroot'].'test.local/', $result['documentroot']); + } + /** - * @depends testAdminDomainsMoveButUnknownCustomer + * @depends testAdminDomainsMove */ public function testAdminDomainsDelete() { @@ -178,6 +203,11 @@ class DomainsTest extends TestCase $json_result = Domains::getLocal($admin_userdata, $data)->delete(); $result = json_decode($json_result, true)['data']; $this->assertEquals('test.local', $result['domain']); + + // remove customer again + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test3' + ))->delete(); } } From f30887e3c030fba89557040e010711ffa7021a95 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sat, 3 Mar 2018 21:26:37 +0100 Subject: [PATCH 110/746] removed unused local variables Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Admins.php | 1 - lib/classes/api/commands/class.Customers.php | 3 +-- lib/classes/api/commands/class.Domains.php | 4 ---- lib/classes/api/commands/class.Froxlor.php | 4 ++-- lib/classes/api/commands/class.Ftps.php | 8 +++----- lib/classes/api/commands/class.PhpSettings.php | 2 -- tests/Domains/DomainsTest.php | 18 +++++++++--------- 7 files changed, 15 insertions(+), 25 deletions(-) diff --git a/lib/classes/api/commands/class.Admins.php b/lib/classes/api/commands/class.Admins.php index 6a6b8300..5b61b50c 100644 --- a/lib/classes/api/commands/class.Admins.php +++ b/lib/classes/api/commands/class.Admins.php @@ -103,7 +103,6 @@ class Admins extends ApiCommand implements ResourceEntity $custom_notes = $this->getParam('custom_notes', true, ''); $custom_notes_show = $this->getParam('custom_notes_show', true, 0); $password = $this->getParam('admin_password', true, ''); - $sendpassword = $this->getParam('sendpassword', true, 0); $loginname = $this->getParam('new_loginname', true, ''); $diskspace = $this->getUlParam('diskspace', 'diskspace_ul', true, 0); diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index 4191a835..cf26d8aa 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -710,7 +710,6 @@ class Customers extends ApiCommand implements ResourceEntity $mysqls = $this->getUlParam('mysqls', 'mysqls_ul', true, $result['mysqls']); $createstdsubdomain = $this->getParam('createstdsubdomain', true, 0); $password = $this->getParam('new_customer_password', true, ''); - $sendpassword = $this->getParam('sendpassword', true, 0); $phpenabled = $this->getParam('phpenabled', true, $result['phpenabled']); $allowed_phpconfigs = $this->getParam('allowed_phpconfigs', true, json_decode($result['allowed_phpconfigs'], true)); $perlenabled = $this->getParam('perlenabled', true, $result['perlenabled']); @@ -1396,7 +1395,7 @@ class Customers extends ApiCommand implements ResourceEntity if ($tickets !== false && isset($tickets[0])) { foreach ($tickets as $ticket) { $now = time(); - $mainticket = ticket::getInstanceOf($userinfo, (int) $ticket); + $mainticket = ticket::getInstanceOf($result, (int) $ticket); $mainticket->Set('lastchange', $now, true, true); $mainticket->Set('lastreplier', '1', true, true); $mainticket->Set('status', '3', true, true); diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index 775cc8e6..fcd71ea6 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -786,7 +786,6 @@ class Domains extends ApiCommand implements ResourceEntity $id = $result['id']; // optional parameters - $p_domain = $this->getParam('domain', true, $result['domain']); $p_ipandports = $this->getParam('ipandport', true, array()); $customerid = intval($this->getParam('customerid', true, $result['customerid'])); $adminid = intval($this->getParam('adminid', true, $result['adminid'])); @@ -1081,15 +1080,12 @@ class Domains extends ApiCommand implements ResourceEntity Database::pexecute($ipsresult_stmt, array( 'id' => $result['id'] )); - $usedips = array(); while ($ipsresultrow = $ipsresult_stmt->fetch(PDO::FETCH_ASSOC)) { $ipandports[] = $ipsresultrow['id_ipandports']; } } if (Settings::Get('system.use_ssl') == '1' && ! empty($p_ssl_ipandports)) { - $ssl = 1; // if ssl is set and != 0, it can only be 1 - $ssl_ipandports = array(); if (! empty($p_ssl_ipandports) && ! is_array($p_ssl_ipandports)) { $p_ssl_ipandports = unserialize($p_ssl_ipandports); diff --git a/lib/classes/api/commands/class.Froxlor.php b/lib/classes/api/commands/class.Froxlor.php index 3e902101..9c68a666 100644 --- a/lib/classes/api/commands/class.Froxlor.php +++ b/lib/classes/api/commands/class.Froxlor.php @@ -63,7 +63,7 @@ class Froxlor extends ApiCommand // anzeige über version-status mit ggfls. formular // zum update schritt #1 -> download if ($isnewerversion == 1) { - $text = 'There is a newer version available: "' . $_version . '" (Your current version is: ' . $this->version . ')'; + $text = 'There is a newer version available: "' . $_version . '" (Your current version is: ' . $version_label . ')'; return $this->response(200, "successfull", array( 'message' => $text, 'link' => $version_link, @@ -217,7 +217,7 @@ class Froxlor extends ApiCommand // create RecursiveIteratorIterator $its = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path)); // check every file - foreach ($its as $fullFileName => $it) { + foreach ($its as $it) { // does it match the Filename pattern? $matches = array(); if (preg_match("/^class\.(.+)\.php$/i", $it->getFilename(), $matches)) { diff --git a/lib/classes/api/commands/class.Ftps.php b/lib/classes/api/commands/class.Ftps.php index f2909440..00e6057b 100644 --- a/lib/classes/api/commands/class.Ftps.php +++ b/lib/classes/api/commands/class.Ftps.php @@ -117,7 +117,7 @@ class Ftps extends ApiCommand implements ResourceEntity ), true, true); if ($ftpdomain_check && $ftpdomain_check['domain'] != $ftpdomain) { - standard_error('maindomainnonexist', $domain, true); + standard_error('maindomainnonexist', $ftpdomain, true); } $username = $ftpusername . "@" . $ftpdomain; } else { @@ -152,7 +152,6 @@ class Ftps extends ApiCommand implements ResourceEntity "shell" => $shell ); Database::pexecute($stmt, $params, true, true); - $ftp_userid = Database::lastInsertId(); $result_stmt = Database::prepare(" SELECT `bytes_in_used` FROM `" . TABLE_FTP_QUOTATALLIES . "` WHERE `name` = :name @@ -360,8 +359,7 @@ class Ftps extends ApiCommand implements ResourceEntity } else { $shell = "/bin/false"; } - - $params = array(); + // get needed customer info to reduce the ftp-user-counter by one if ($this->isAdmin()) { // get customer id @@ -461,7 +459,7 @@ class Ftps extends ApiCommand implements ResourceEntity $customerid = $this->getParam('customerid', true, 0); $loginname = $this->getParam('loginname', true, ''); - if (! empty($customer_id) || ! empty($loginname)) { + if (! empty($customerid) || ! empty($loginname)) { $json_result = Customers::getLocal($this->getUserData(), array( 'id' => $customerid, 'loginname' => $loginname diff --git a/lib/classes/api/commands/class.PhpSettings.php b/lib/classes/api/commands/class.PhpSettings.php index cc930714..e25a9d09 100644 --- a/lib/classes/api/commands/class.PhpSettings.php +++ b/lib/classes/api/commands/class.PhpSettings.php @@ -39,8 +39,6 @@ class PhpSettings extends ApiCommand implements ResourceEntity $phpconfigs = array(); while ($row = $result->fetch(PDO::FETCH_ASSOC)) { - - $domainresult = false; $query_params = array( 'id' => $row['id'] ); diff --git a/tests/Domains/DomainsTest.php b/tests/Domains/DomainsTest.php index c4ee05b8..d2b05651 100644 --- a/tests/Domains/DomainsTest.php +++ b/tests/Domains/DomainsTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\TestCase; */ class DomainsTest extends TestCase { + public function testAdminDomainsAdd() { global $admin_userdata; @@ -22,9 +23,9 @@ class DomainsTest extends TestCase ]; $json_result = Domains::getLocal($admin_userdata, $data)->add(); $result = json_decode($json_result, true)['data']; - $this->assertEquals($customer_userdata['documentroot'].'test.local/', $result['documentroot']); + $this->assertEquals($customer_userdata['documentroot'] . 'test.local/', $result['documentroot']); } - + /** * @depends testAdminDomainsAdd */ @@ -107,7 +108,7 @@ class DomainsTest extends TestCase $this->expectExceptionMessage('The server-hostname cannot be used as customer-domain.'); $json_result = Domains::getLocal($admin_userdata, $data)->add(); } - + public function testAdminDomainsAddNoPunycode() { global $admin_userdata; @@ -129,7 +130,7 @@ class DomainsTest extends TestCase $this->expectExceptionMessage("Wrong Input in Field 'Domain'"); Domains::getLocal($admin_userdata, $data)->add(); } - + /** * @depends testAdminDomainsAdd */ @@ -179,15 +180,15 @@ class DomainsTest extends TestCase ]; $json_result = Customers::getLocal($admin_userdata, $data)->add(); $customer_userdata = json_decode($json_result, true)['data']; - + $data = [ 'domainname' => 'test.local', 'customerid' => $customer_userdata['customerid'] ]; - $json_result =Domains::getLocal($admin_userdata, $data)->update(); + $json_result = Domains::getLocal($admin_userdata, $data)->update(); $result = json_decode($json_result, true)['data']; $this->assertEquals($customer_userdata['customerid'], $result['customerid']); - $this->assertEquals($customer_userdata['documentroot'].'test.local/', $result['documentroot']); + $this->assertEquals($customer_userdata['documentroot'] . 'test.local/', $result['documentroot']); } /** @@ -203,11 +204,10 @@ class DomainsTest extends TestCase $json_result = Domains::getLocal($admin_userdata, $data)->delete(); $result = json_decode($json_result, true)['data']; $this->assertEquals('test.local', $result['domain']); - + // remove customer again $json_result = Customers::getLocal($admin_userdata, array( 'loginname' => 'test3' ))->delete(); } - } From 7cc17b9ca591c36a3bc98197574eb6de3ad0ca56 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sat, 3 Mar 2018 21:29:16 +0100 Subject: [PATCH 111/746] fix short variables Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/abstract.ApiCommand.php | 6 +++--- lib/classes/api/commands/class.FpmDaemons.php | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index 34d62e3b..61243365 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -432,15 +432,15 @@ abstract class ApiCommand * @param string $table * @param string $keyfield * @param int $key - * @param string $op + * @param string $operator * @param string $resource * @param string $extra */ - protected static function updateResourceUsage($table = null, $keyfield = null, $key = null, $op = '+', $resource = null, $extra = null) + protected static function updateResourceUsage($table = null, $keyfield = null, $key = null, $operator = '+', $resource = null, $extra = null) { $stmt = Database::prepare(" UPDATE `" . $table . "` - SET `" . $resource . "` = `" . $resource . "` " . $op . " 1 " . $extra . " + SET `" . $resource . "` = `" . $resource . "` " . $operator . " 1 " . $extra . " WHERE `" . $keyfield . "` = :key "); Database::pexecute($stmt, array( diff --git a/lib/classes/api/commands/class.FpmDaemons.php b/lib/classes/api/commands/class.FpmDaemons.php index cd04990d..b42a0c99 100644 --- a/lib/classes/api/commands/class.FpmDaemons.php +++ b/lib/classes/api/commands/class.FpmDaemons.php @@ -114,7 +114,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity $config_dir = $this->getParam('config_dir'); // parameters - $pm = $this->getParam('pm', true, 'static'); + $pmanager = $this->getParam('pm', true, 'static'); $max_children = $this->getParam('max_children', true, 0); $start_servers = $this->getParam('start_servers', true, 0); $min_spare_servers = $this->getParam('min_spare_servers', true, 0); @@ -127,7 +127,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity $description = validate($description, 'description', '', '', array(), true); $reload_cmd = validate($reload_cmd, 'reload_cmd', '', '', array(), true); $config_dir = validate($config_dir, 'config_dir', '', '', array(), true); - if (! in_array($pm, array( + if (! in_array($pmanager, array( 'static', 'dynamic', 'ondemand' @@ -158,7 +158,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity 'desc' => $description, 'reload_cmd' => $reload_cmd, 'config_dir' => makeCorrectDir($config_dir), - 'pm' => $pm, + 'pm' => $pmanager, 'max_children' => $max_children, 'start_servers' => $start_servers, 'min_spare_servers' => $min_spare_servers, @@ -202,7 +202,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity $description = $this->getParam('description', true, $result['description']); $reload_cmd = $this->getParam('reload_cmd', true, $result['reload_cmd']); $config_dir = $this->getParam('config_dir', true, $result['config_dir']); - $pm = $this->getParam('pm', true, $result['pm']); + $pmanager = $this->getParam('pm', true, $result['pm']); $max_children = $this->getParam('max_children', true, $result['max_children']); $start_servers = $this->getParam('start_servers', true, $result['start_servers']); $min_spare_servers = $this->getParam('min_spare_servers', true, $result['min_spare_servers']); @@ -215,7 +215,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity $description = validate($description, 'description', '', '', array(), true); $reload_cmd = validate($reload_cmd, 'reload_cmd', '', '', array(), true); $config_dir = validate($config_dir, 'config_dir', '', '', array(), true); - if (! in_array($pm, array( + if (! in_array($pmanager, array( 'static', 'dynamic', 'ondemand' @@ -247,7 +247,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity 'desc' => $description, 'reload_cmd' => $reload_cmd, 'config_dir' => makeCorrectDir($config_dir), - 'pm' => $pm, + 'pm' => $pmanager, 'max_children' => $max_children, 'start_servers' => $start_servers, 'min_spare_servers' => $min_spare_servers, From bb3fddb08f7b9b3102a16372357ca09981ae02c5 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sat, 3 Mar 2018 21:39:51 +0100 Subject: [PATCH 112/746] optimize phpmd config Signed-off-by: Michael Kaufmann (d00p) --- phpmd.xml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/phpmd.xml b/phpmd.xml index 0f1aae49..a34f8f48 100644 --- a/phpmd.xml +++ b/phpmd.xml @@ -9,10 +9,15 @@ - + + + + + + @@ -21,4 +26,11 @@ + + 1 + + + + + From 702b52d13e00c6c705504695a731dedac7280ed6 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sat, 3 Mar 2018 21:42:34 +0100 Subject: [PATCH 113/746] forgot to save again Signed-off-by: Michael Kaufmann (d00p) --- phpmd.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpmd.xml b/phpmd.xml index a34f8f48..e9e42fd5 100644 --- a/phpmd.xml +++ b/phpmd.xml @@ -29,7 +29,7 @@ 1 - + From 91cecf8c1e739132d9ef060ae68ec6679daeeb18 Mon Sep 17 00:00:00 2001 From: Kai Neuwerth Date: Sun, 4 Mar 2018 11:43:50 +0100 Subject: [PATCH 114/746] Link domains to HTTPS in certificate list As an admin I want to check if the certificate of a website is working. Therefore I click on the domain in the SSL certificate list and have to prepend "https" to the URL later. In this case I'd link the domain to HTTPS initially. --- templates/Sparkle/ssl_certificates/certs_cert.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/Sparkle/ssl_certificates/certs_cert.tpl b/templates/Sparkle/ssl_certificates/certs_cert.tpl index 250a57f1..e383f54f 100644 --- a/templates/Sparkle/ssl_certificates/certs_cert.tpl +++ b/templates/Sparkle/ssl_certificates/certs_cert.tpl @@ -1,6 +1,6 @@ class="domain-expired"> - {$row['domain']} + {$row['domain']} {$adminCustomerLink} From b07d6ceeaa85cff538a56e1af1947ff46a746fe7 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sun, 4 Mar 2018 12:40:47 +0100 Subject: [PATCH 115/746] started UI api-key management Signed-off-by: Michael Kaufmann (d00p) --- admin_index.php | 3 + api_keys.php | 128 +++++++++++++++++++ customer_index.php | 3 + lib/classes/api/commands/class.Admins.php | 34 ++++- lib/classes/api/commands/class.Customers.php | 24 +++- lib/navigation/00.froxlor.main.php | 10 ++ lng/english.lng.php | 3 + lng/german.lng.php | 3 + templates/Sparkle/api_keys/keys_error.tpl | 3 + templates/Sparkle/api_keys/keys_key.tpl | 27 ++++ templates/Sparkle/api_keys/keys_list.tpl | 68 ++++++++++ templates/Sparkle/assets/css/main.css | 4 +- templates/Sparkle/header.tpl | 1 + 13 files changed, 299 insertions(+), 12 deletions(-) create mode 100644 api_keys.php create mode 100644 templates/Sparkle/api_keys/keys_error.tpl create mode 100644 templates/Sparkle/api_keys/keys_key.tpl create mode 100644 templates/Sparkle/api_keys/keys_list.tpl diff --git a/admin_index.php b/admin_index.php index 6efb31ff..8d7f0e48 100644 --- a/admin_index.php +++ b/admin_index.php @@ -393,6 +393,9 @@ if ($page == 'overview') { redirectTo($filename, array('s' => $s)); } } +elseif ($page == 'apikeys' && Settings::Get('api.enabled') == 1) { + require_once __DIR__ . '/api_keys.php'; +} elseif ($page == 'apihelp' && Settings::Get('api.enabled') == 1) { require_once __DIR__ . '/apihelp.php'; } diff --git a/api_keys.php b/api_keys.php new file mode 100644 index 00000000..d04891df --- /dev/null +++ b/api_keys.php @@ -0,0 +1,128 @@ + (2018-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package Panel + * @since 0.10.0 + * + */ + +// This file is being included in admin_index and customer_index +// and therefore does not need to require lib/init.php + +$log->logAction(USR_ACTION, LOG_NOTICE, "viewed api::api_keys"); + +// select all my (accessable) certificates +$keys_stmt_query = "SELECT ak.*, c.loginname, a.loginname as adminname + FROM `" . TABLE_API_KEYS . "` ak + LEFT JOIN `" . TABLE_PANEL_CUSTOMERS . "` c ON `c`.`customerid` = `ak`.`customerid` + LEFT JOIN `" . TABLE_PANEL_ADMINS . "` a ON `a`.`adminid` = `ak`.`adminid` + WHERE "; + +$qry_params = array(); +if (AREA == 'admin' && $userinfo['customers_see_all'] == '0') { + // admin with only customer-specific permissions + $keys_stmt_query .= "ak.adminid = :adminid "; + $qry_params['adminid'] = $userinfo['adminid']; + $fields = array( + 'a.loginname' => $lng['login']['username'] + ); +} elseif (AREA == 'customer') { + // customer-area + $keys_stmt_query .= "ak.customerid = :cid "; + $qry_params['cid'] = $userinfo['customerid']; + $fields = array( + 'c.loginname' => $lng['login']['username'] + ); +} else { + // admin who can see all customers / reseller / admins + $keys_stmt_query .= "1 "; + $fields = array( + 'a.loginname' => $lng['login']['username'] + ); +} + +$paging = new paging($userinfo, TABLE_API_KEYS, $fields); +$keys_stmt_query .= $paging->getSqlWhere(true) . " " . $paging->getSqlOrderBy() . " " . $paging->getSqlLimit(); + +$keys_stmt = Database::prepare($keys_stmt_query); +Database::pexecute($keys_stmt, $qry_params); +$all_keys = $keys_stmt->fetchAll(PDO::FETCH_ASSOC); +$apikeys = ""; + +if (count($all_keys) == 0) { + $count = 0; + $message = $lng['apikeys']['no_api_keys']; + $sortcode = ""; + $searchcode = ""; + $pagingcode = ""; + eval("\$apikeys.=\"" . getTemplate("api_keys/keys_error", true) . "\";"); +} else { + $count = count($all_keys); + $paging->setEntries($count); + $sortcode = $paging->getHtmlSortCode($lng); + $arrowcode = $paging->getHtmlArrowCode($filename . '?page=' . $page . '&s=' . $s); + $searchcode = $paging->getHtmlSearchCode($lng); + $pagingcode = $paging->getHtmlPagingCode($filename . '?page=' . $page . '&s=' . $s); + + foreach ($all_keys as $idx => $key) { + if ($paging->checkDisplay($idx)) { + + // my own key + $isMyKey = false; + if ($key['adminid'] == $userinfo['adminid'] && (AREA == 'admin' || (AREA == 'customer' && $key['customerid'] == $userinfo['customerid']))) { + // this is mine + $isMyKey = true; + } + + $adminCustomerLink = ""; + if (AREA == 'admin') { + if ($isMyKey) { + $adminCustomerLink = $key['adminname']; + } else { + $adminCustomerLink = ' (' . (empty($key['customerid']) ? $key['adminname'] : $key['loginname']) . ')'; + } + } else { + // customer do not need links + $adminCustomerLink = $key['loginname']; + } + + // escape stuff + $row = htmlentities_array($key); + + // check whether the api key is not valid anymore + $isValid = true; + if ($row['valid_until'] >= 0) { + if ($row['valid_until'] < time()) { + $isValid = false; + } + // format + $row['valid_until'] = date('d.m.Y H:i', $row['valid_until']); + } else { + $row['valid_until'] = "∞"; + } + eval("\$apikeys.=\"" . getTemplate("api_keys/keys_key", true) . "\";"); + } else { + continue; + } + } +} +eval("echo \"" . getTemplate("api_keys/keys_list", true) . "\";"); diff --git a/customer_index.php b/customer_index.php index 85d02158..f1ba35fb 100644 --- a/customer_index.php +++ b/customer_index.php @@ -314,6 +314,9 @@ if ($page == 'overview') { redirectTo($filename, array('s' => $s)); } } +elseif ($page == 'apikeys' && Settings::Get('api.enabled') == 1) { + require_once __DIR__ . '/api_keys.php'; +} elseif ($page == 'apihelp' && Settings::Get('api.enabled') == 1) { require_once __DIR__ . '/apihelp.php'; } diff --git a/lib/classes/api/commands/class.Admins.php b/lib/classes/api/commands/class.Admins.php index 5b61b50c..e100f8ee 100644 --- a/lib/classes/api/commands/class.Admins.php +++ b/lib/classes/api/commands/class.Admins.php @@ -595,27 +595,31 @@ class Admins extends ApiCommand implements ResourceEntity standard_error('youcantdeleteyourself', '', true); } + // delete admin $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_PANEL_ADMINS . "` WHERE `adminid` = :adminid "); Database::pexecute($del_stmt, array( 'adminid' => $id ), true, true); - + + // delete the traffic-usage $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_PANEL_TRAFFIC_ADMINS . "` WHERE `adminid` = :adminid "); Database::pexecute($del_stmt, array( 'adminid' => $id ), true, true); - + + // delete the diskspace usage $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_PANEL_DISKSPACE_ADMINS . "` WHERE `adminid` = :adminid "); Database::pexecute($del_stmt, array( 'adminid' => $id ), true, true); - + + // set admin-id of the old admin's customer to current admins $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `adminid` = :userid WHERE `adminid` = :adminid @@ -624,7 +628,8 @@ class Admins extends ApiCommand implements ResourceEntity 'userid' => $this->getUserDetail('adminid'), 'adminid' => $id ), true, true); - + + // set admin-id of the old admin's domains to current admins $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `adminid` = :userid WHERE `adminid` = :adminid @@ -633,7 +638,26 @@ class Admins extends ApiCommand implements ResourceEntity 'userid' => $this->getUserDetail('adminid'), 'adminid' => $id ), true, true); - + + // delete old admin's api keys if exists (no customer keys) + $upd_stmt = Database::prepare(" + DELETE FROM `" . TABLE_API_KEYS . "` WHERE + `adminid` = :userid AND `customerid` = '0' + "); + Database::pexecute($upd_stmt, array( + 'adminid' => $id + ), true, true); + + // set admin-id of the old admin's api-keys to current admins + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_API_KEYS . "` SET + `adminid` = :userid WHERE `adminid` = :adminid + "); + Database::pexecute($upd_stmt, array( + 'userid' => $this->getUserDetail('adminid'), + 'adminid' => $id + ), true, true); + $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] deleted admin '" . $result['loginname'] . "'"); updateCounters(); return $this->response(200, "successfull", $result); diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index cf26d8aa..ed1ff384 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -857,9 +857,9 @@ class Customers extends ApiCommand implements ResourceEntity // activate/deactivate customer services if ($deactivated != $result['deactivated']) { - $yesno = (($deactivated) ? 'N' : 'Y'); - $pop3 = (($deactivated) ? '0' : (int) $result['pop3']); - $imap = (($deactivated) ? '0' : (int) $result['imap']); + $yesno = ($deactivated ? 'N' : 'Y'); + $pop3 = ($deactivated ? '0' : (int) $result['pop3']); + $imap = ($deactivated ? '0' : (int) $result['imap']); $upd_stmt = Database::prepare(" UPDATE `" . TABLE_MAIL_USERS . "` SET `postfix`= :yesno, `pop3` = :pop3, `imap` = :imap WHERE `customerid` = :customerid @@ -923,8 +923,16 @@ class Customers extends ApiCommand implements ResourceEntity // At last flush the new privileges $dbm->getManager()->flushPrivileges(); Database::needRoot(false); - - $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] deactivated user '" . $result['loginname'] . "'"); + + // reactivate/deactivate api-keys + $valid_until = $deactivated ? 0 : - 1; + $stmt = Database::prepare("UPDATE `" . TABLE_API_KEYS . "` SET `valid_until` = :vu WHERE `customerid` = :id"); + Database::pexecute($stmt, array( + 'id' => $id, + 'vu' => $valid_until + ), true, true); + + $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] " . ($deactivated ? 'deactivated' : 'reactivated') . " user '" . $result['loginname'] . "'"); inserttask('1'); } @@ -1323,6 +1331,12 @@ class Customers extends ApiCommand implements ResourceEntity 'id' => $id ), true, true); + // remove api-keys + $stmt = Database::prepare("DELETE FROM `" . TABLE_API_KEYS . "` WHERE `customerid` = :id"); + Database::pexecute($stmt, array( + 'id' => $id + ), true, true); + // Delete all waiting "create user" -tasks for this user, #276 // Note: the WHERE selects part of a serialized array, but it should be safe this way $del_stmt = Database::prepare(" diff --git a/lib/navigation/00.froxlor.main.php b/lib/navigation/00.froxlor.main.php index 61c7fe6f..8775effd 100644 --- a/lib/navigation/00.froxlor.main.php +++ b/lib/navigation/00.froxlor.main.php @@ -38,6 +38,11 @@ return array( 'label' => $lng['menue']['main']['changetheme'], 'show_element' => (Settings::Get('panel.allow_theme_change_customer') == true) ), + array( + 'url' => 'customer_index.php?page=apikeys', + 'label' => $lng['menue']['main']['apikeys'], + 'show_element' => (Settings::Get('api.enabled') == true) + ), array( 'url' => 'customer_index.php?page=apihelp', 'label' => $lng['menue']['main']['apihelp'], @@ -184,6 +189,11 @@ return array( 'label' => $lng['menue']['main']['changetheme'], 'show_element' => (Settings::Get('panel.allow_theme_change_admin') == true) ), + array( + 'url' => 'admin_index.php?page=apikeys', + 'label' => $lng['menue']['main']['apikeys'], + 'show_element' => (Settings::Get('api.enabled') == true) + ), array( 'url' => 'admin_index.php?page=apihelp', 'label' => $lng['menue']['main']['apihelp'], diff --git a/lng/english.lng.php b/lng/english.lng.php index 264ba8c3..e0c4e59f 100644 --- a/lng/english.lng.php +++ b/lng/english.lng.php @@ -2123,3 +2123,6 @@ $lng['admin']['notryfiles']['description'] = 'Say yes here if you want to specif // added in froxlor 0.10.0 $lng['panel']['none_value'] = 'None'; $lng['menue']['main']['apihelp'] = 'API help'; +$lng['menue']['main']['apikeys'] = 'API keys'; +$lng['apikeys']['no_api_keys'] = 'No API keys found'; +$lng['apikeys']['key_add'] = 'Add new key'; diff --git a/lng/german.lng.php b/lng/german.lng.php index 06fac9f6..b6507e87 100644 --- a/lng/german.lng.php +++ b/lng/german.lng.php @@ -1773,3 +1773,6 @@ $lng['admin']['notryfiles']['description'] = 'Wähle "Ja", wenn eine eigene try_ // added in froxlor 0.10.0 $lng['panel']['none_value'] = 'Keine'; $lng['menue']['main']['apihelp'] = 'API Hilfe'; +$lng['menue']['main']['apikeys'] = 'API Keys'; +$lng['apikeys']['no_api_keys'] = 'Keine API Keys gefunden'; +$lng['apikeys']['key_add'] = 'API Key hinzufügen'; diff --git a/templates/Sparkle/api_keys/keys_error.tpl b/templates/Sparkle/api_keys/keys_error.tpl new file mode 100644 index 00000000..e573c664 --- /dev/null +++ b/templates/Sparkle/api_keys/keys_error.tpl @@ -0,0 +1,3 @@ + + {$message} + diff --git a/templates/Sparkle/api_keys/keys_key.tpl b/templates/Sparkle/api_keys/keys_key.tpl new file mode 100644 index 00000000..12eb43a2 --- /dev/null +++ b/templates/Sparkle/api_keys/keys_key.tpl @@ -0,0 +1,27 @@ +class="primary-entry"> + + {$adminCustomerLink} + + + {$row['apikey']} + + + {$row['secret']} + + + {$row['allowed_from']} + + + + {$row['valid_until']} + + + + + {$lng['panel']['edit']} +   + + {$lng['panel']['delete']} + + + diff --git a/templates/Sparkle/api_keys/keys_list.tpl b/templates/Sparkle/api_keys/keys_list.tpl new file mode 100644 index 00000000..11671ad7 --- /dev/null +++ b/templates/Sparkle/api_keys/keys_list.tpl @@ -0,0 +1,68 @@ + $header +
    +
    +

    +   + {$lng['menue']['main']['apikeys']} +

    +
    + + +
    +
    {$lng['success']['success']}
    +
    + $success_message +
    +
    +
    + +
    + +
    + + + +
    + {$searchcode} +
    + + + + + + + + + + + + + + + + + + + + + + + + + {$apikeys} + +
    UserAPI-keysSecretAllowed fromValid until{$lng['panel']['options']}
    {$pagingcode}
    +
    + + + + +
    +
    +$footer diff --git a/templates/Sparkle/assets/css/main.css b/templates/Sparkle/assets/css/main.css index 5e61b727..85e0dfb0 100644 --- a/templates/Sparkle/assets/css/main.css +++ b/templates/Sparkle/assets/css/main.css @@ -1682,13 +1682,13 @@ fieldset.file { background-color: rgb(242, 222, 222); } -.domain-hostname { +.primary-entry { background-color: rgb(53, 106, 160); color: #ddd; font-weight: bold; } -table.hl tbody tr.domain-hostname:hover { +table.hl tbody tr.primary-entry:hover { background-color: rgb(64, 150, 238); } diff --git a/templates/Sparkle/header.tpl b/templates/Sparkle/header.tpl index d858f52c..ecc5f168 100644 --- a/templates/Sparkle/header.tpl +++ b/templates/Sparkle/header.tpl @@ -68,6 +68,7 @@
  • {$lng['panel']['theme']}
  • +
  • {$lng['menue']['main']['apikeys']}
  • {$lng['menue']['main']['apihelp']}
  • From b664917147dc28bb11492ca01b9c18d8985f2ce0 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sun, 4 Mar 2018 12:44:31 +0100 Subject: [PATCH 116/746] fix sql variable in Admins.delete Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Admins.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/classes/api/commands/class.Admins.php b/lib/classes/api/commands/class.Admins.php index e100f8ee..38c2f449 100644 --- a/lib/classes/api/commands/class.Admins.php +++ b/lib/classes/api/commands/class.Admins.php @@ -642,7 +642,7 @@ class Admins extends ApiCommand implements ResourceEntity // delete old admin's api keys if exists (no customer keys) $upd_stmt = Database::prepare(" DELETE FROM `" . TABLE_API_KEYS . "` WHERE - `adminid` = :userid AND `customerid` = '0' + `adminid` = :adminid AND `customerid` = '0' "); Database::pexecute($upd_stmt, array( 'adminid' => $id From cfa07bab47caeae97c5d782f58e1e6f210c6c981 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sun, 4 Mar 2018 18:30:16 +0100 Subject: [PATCH 117/746] simplified and wrapped internal api calls Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/abstract.ApiCommand.php | 84 ++++++++++++------- lib/classes/api/commands/class.Admins.php | 29 +++---- lib/classes/api/commands/class.Customers.php | 68 +++++++-------- lib/classes/api/commands/class.Domains.php | 28 +++---- lib/classes/api/commands/class.FpmDaemons.php | 14 ++-- lib/classes/api/commands/class.Ftps.php | 55 ++++++------ .../api/commands/class.IpsAndPorts.php | 24 +++--- lib/classes/api/commands/class.Mysqls.php | 62 +++++++------- .../api/commands/class.PhpSettings.php | 26 +++--- lib/classes/api/commands/class.SubDomains.php | 40 ++++----- 10 files changed, 209 insertions(+), 221 deletions(-) diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index 61243365..8a09dd32 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -331,36 +331,6 @@ abstract class ApiCommand return $param_value; } - /** - * returns "module::function()" for better error-messages (missing parameter etc.) - * makes debugging a whole lot more comfortable - * - * @return string - */ - private function getModFunctionString() - { - $_class = get_called_class(); - - $level = 2; - if (version_compare(PHP_VERSION, "5.4.0", "<")) { - $trace = debug_backtrace(); - } else { - $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); - } - while (true) { - $class = $trace[$level]['class']; - $func = $trace[$level]['function']; - if ($class != $_class) { - $level ++; - if ($level > 5) { - break; - } - continue; - } - return $class . ':' . $func; - } - } - /** * update value of parameter * @@ -399,6 +369,24 @@ abstract class ApiCommand return $this->mail; } + /** + * call an api-command internally + * + * @param string $module + * @param string $function + * @param array|null $params + * + * @return array + */ + protected function apiCall($command = null, $params = null) + { + $_command = explode(".", $command); + $module = $_command[0]; + $function = $_command[1]; + $json_result = $module::getLocal($this->getUserData(), $params)->{$function}(); + return json_decode($json_result, true)['data']; + } + /** * return api-compatible response in JSON format and send corresponding http-header * @@ -448,6 +436,35 @@ abstract class ApiCommand ), true, true); } + /** + * returns "module::function()" for better error-messages (missing parameter etc.) + * makes debugging a whole lot more comfortable + * + * @return string + */ + private function getModFunctionString() + { + $_class = get_called_class(); + $level = 2; + if (version_compare(PHP_VERSION, "5.4.0", "<")) { + $trace = debug_backtrace(); + } else { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + } + while (true) { + $class = $trace[$level]['class']; + $func = $trace[$level]['function']; + if ($class != $_class) { + $level ++; + if ($level > 5) { + break; + } + continue; + } + return $class . ':' . $func; + } + } + /** * read user data from database by api-request-header fields * @@ -490,6 +507,13 @@ abstract class ApiCommand throw new Exception("Invalid API credentials", 400); } + /** + * run 'trim' function on an array recursively + * + * @param array $input + * + * @return array + */ private function trimArray($input) { if (! is_array($input)) { diff --git a/lib/classes/api/commands/class.Admins.php b/lib/classes/api/commands/class.Admins.php index 38c2f449..9a24c02e 100644 --- a/lib/classes/api/commands/class.Admins.php +++ b/lib/classes/api/commands/class.Admins.php @@ -285,10 +285,9 @@ class Admins extends ApiCommand implements ResourceEntity $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] added admin '" . $loginname . "'"); // get all admin-data for return-array - $json_result = Admins::getLocal($this->getUserData(), array( + $result = $this->apiCall('Admins.get', array( 'id' => $adminid - ))->get(); - $result = json_decode($json_result, true)['data']; + )); return $this->response(200, "successfull", $result); } } @@ -314,12 +313,11 @@ class Admins extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); $loginname = $this->getParam('loginname', $ln_optional, ''); - - $json_result = Admins::getLocal($this->getUserData(), array( + + $result = $this->apiCall('Admins.get', array( 'id' => $id, 'loginname' => $loginname - ))->get(); - $result = json_decode($json_result, true)['data']; + )); $id = $result['adminid']; if ($this->getUserDetail('change_serversettings') == 1 || $result['adminid'] == $this->getUserDetail('adminid')) { @@ -553,10 +551,9 @@ class Admins extends ApiCommand implements ResourceEntity $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] edited admin '" . $result['loginname'] . "'"); // get all admin-data for return-array - $json_result = Admins::getLocal($this->getUserData(), array( + $result = $this->apiCall('Admins.get', array( 'id' => $result['adminid'] - ))->get(); - $result = json_decode($json_result, true)['data']; + )); return $this->response(200, "successfull", $result); } } @@ -582,12 +579,11 @@ class Admins extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); $loginname = $this->getParam('loginname', $ln_optional, ''); - - $json_result = Admins::getLocal($this->getUserData(), array( + + $result = $this->apiCall('Admins.get', array( 'id' => $id, 'loginname' => $loginname - ))->get(); - $result = json_decode($json_result, true)['data']; + )); $id = $result['adminid']; // don't be stupid @@ -684,11 +680,10 @@ class Admins extends ApiCommand implements ResourceEntity $ln_optional = ($id <= 0 ? false : true); $loginname = $this->getParam('loginname', $ln_optional, ''); - $json_result = Admins::getLocal($this->getUserData(), array( + $result = $this->apiCall('Admins.get', array( 'id' => $id, 'loginname' => $loginname - ))->get(); - $result = json_decode($json_result, true)['data']; + )); $id = $result['adminid']; $result_stmt = Database::prepare(" diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index ed1ff384..fdbd42ce 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -536,8 +536,8 @@ class Customers extends ApiCommand implements ResourceEntity ); $domainid = - 1; try { - $std_domain = Domains::getLocal($this->getUserData(), $ins_data)->add(); - $domainid = json_decode($std_domain, true)['data']['id']; + $std_domain = $this->apiCall('Domains.add', $ins_data); + $domainid = $std_domain['id']; } catch (Exception $e) { $this->logger()->logAction(ADM_ACTION, LOG_ERR, "[API] Unable to add standard-subdomain: " . $e->getMessage()); } @@ -638,10 +638,9 @@ class Customers extends ApiCommand implements ResourceEntity } $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] added customer '" . $loginname . "'"); - $json_result = Customers::getLocal($this->getUserData(), array( + $result = $this->apiCall('Customers.get', array( 'loginname' => $loginname - ))->get(); - $result = json_decode($json_result, true)['data']; + )); return $this->response(200, "successfull", $result); } throw new Exception("No more resources available", 406); @@ -666,12 +665,11 @@ class Customers extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); $loginname = $this->getParam('loginname', $ln_optional, ''); - - $json_result = Customers::getLocal($this->getUserData(), array( + + $result = $this->apiCall('Customers.get', array( 'id' => $id, 'loginname' => $loginname - ))->get(); - $result = json_decode($json_result, true)['data']; + )); $id = $result['customerid']; if ($this->isAdmin()) { @@ -802,8 +800,8 @@ class Customers extends ApiCommand implements ResourceEntity ); $domainid = - 1; try { - $std_domain = Domains::getLocal($this->getUserData(), $ins_data)->add(); - $domainid = json_decode($std_domain, true)['data']['id']; + $std_domain = $this->apiCall('Domains.add', $ins_data); + $domainid = $std_domain['id']; } catch (Exception $e) { $this->logger()->logAction(ADM_ACTION, LOG_ERR, "[API] Unable to add standard-subdomain: " . $e->getMessage()); } @@ -823,10 +821,10 @@ class Customers extends ApiCommand implements ResourceEntity if ($createstdsubdomain == '0' && $result['standardsubdomain'] != '0') { try { - $std_domain = Domains::getLocal($this->getUserData(), array( + $std_domain = $this->apiCall('Domains.delete', array( 'id' => $result['standardsubdomain'], 'is_stdsubdomain' => 1 - ))->delete(); + )); } catch (Exception $e) { $this->logger()->logAction(ADM_ACTION, LOG_ERR, "[API] Unable to delete standard-subdomain: " . $e->getMessage()); } @@ -1157,21 +1155,19 @@ class Customers extends ApiCommand implements ResourceEntity */ if ($this->isAdmin()) { if ($move_to_admin > 0 && $move_to_admin != $result['adminid']) { - $json_result = Customers::getLocal($this->getUserData(), array( + $move_result = $this->apiCall('Customers.move', array( 'id' => $result['customerid'], 'adminid' => $move_to_admin - ))->move(); - $move_result = json_decode($json_result, true)['data']; + )); if ($move_result != true) { standard_error('moveofcustomerfailed', $move_result, true); } } } - - $json_result = Customers::getLocal($this->getUserData(), array( + + $result = $this->apiCall('Customers.get', array( 'id' => $result['customerid'] - ))->get(); - $result = json_decode($json_result, true)['data']; + )); return $this->response(200, "successfull", $result); } @@ -1196,12 +1192,11 @@ class Customers extends ApiCommand implements ResourceEntity $ln_optional = ($id <= 0 ? false : true); $loginname = $this->getParam('loginname', $ln_optional, ''); $delete_userfiles = $this->getParam('delete_userfiles', true, 0); - - $json_result = Customers::getLocal($this->getUserData(), array( + + $result = $this->apiCall('Customers.get', array( 'id' => $id, 'loginname' => $loginname - ))->get(); - $result = json_decode($json_result, true)['data']; + )); $id = $result['customerid']; // @fixme use Databases-ApiCommand later @@ -1443,12 +1438,11 @@ class Customers extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); $loginname = $this->getParam('loginname', $ln_optional, ''); - - $json_result = Customers::getLocal($this->getUserData(), array( + + $result = $this->apiCall('Customers.get', array( 'id' => $id, 'loginname' => $loginname - ))->get(); - $result = json_decode($json_result, true)['data']; + )); $id = $result['customerid']; $result_stmt = Database::prepare(" @@ -1488,12 +1482,11 @@ class Customers extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); $loginname = $this->getParam('loginname', $ln_optional, ''); - - $json_result = Customers::getLocal($this->getUserData(), array( + + $c_result = $this->apiCall('Customers.get', array( 'id' => $id, 'loginname' => $loginname - ))->get(); - $c_result = json_decode($json_result, true)['data']; + )); $id = $c_result['customerid']; // check if target-admin is the current admin @@ -1502,10 +1495,9 @@ class Customers extends ApiCommand implements ResourceEntity } // get target admin - $json_result = Admins::getLocal($this->getUserData(), array( + $a_result = $this->apiCall('Admins.get', array( 'id' => $adminid - ))->get(); - $a_result = json_decode($json_result, true)['data']; + )); // Update customer entry $updCustomer_stmt = Database::prepare(" @@ -1538,10 +1530,10 @@ class Customers extends ApiCommand implements ResourceEntity updateCounters(false); $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] moved user '" . $c_result['loginname'] . "' from admin/reseller '" . $c_result['adminname'] . " to admin/reseller '" . $a_result['loginname'] . "'"); - $json_result = Customers::getLocal($this->getUserData(), array( + + $result = $this->apiCall('Customers.get', array( 'id' => $c_result['customerid'] - ))->get(); - $result = json_decode($json_result, true)['data']; + )); return $this->response(200, "successfull", $result); } throw new Exception("Not allowed to execute given command.", 403); diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index fcd71ea6..99d29dec 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -178,10 +178,9 @@ class Domains extends ApiCommand implements ResourceEntity ), '', true); } - $json_result = Customers::getLocal($this->getUserData(), array( + $customer = $this->apiCall('Customers.get', array( 'id' => $customerid - ))->get(); - $customer = json_decode($json_result, true)['data']; + )); if ($this->getUserDetail('customers_see_all') == '1') { $admin_stmt = Database::prepare(" @@ -743,10 +742,9 @@ class Domains extends ApiCommand implements ResourceEntity $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] added domain '" . $domain . "'"); - $json_result = Domains::getLocal($this->getUserData(), array( + $result = $this->apiCall('Domains.get', array( 'domainname' => $domain - ))->get(); - $result = json_decode($json_result, true)['data']; + )); return $this->response(200, "successfull", $result); } } @@ -777,12 +775,11 @@ class Domains extends ApiCommand implements ResourceEntity $domainname = $this->getParam('domainname', $dn_optional, ''); // get requested domain - $json_result = Domains::getLocal($this->getUserData(), array( + $result = $this->apiCall('Domains.get', array( 'id' => $id, 'domainname' => $domainname, 'no_std_subdomain' => true - ))->get(); - $result = json_decode($json_result, true)['data']; + )); $id = $result['id']; // optional parameters @@ -877,8 +874,7 @@ class Domains extends ApiCommand implements ResourceEntity AND (`subdomains_used` + :subdomains <= `subdomains` OR `subdomains` = '-1' ) AND (`emails_used` + :emails <= `emails` OR `emails` = '-1' ) AND (`email_forwarders_used` + :forwarders <= `email_forwarders` OR `email_forwarders` = '-1' ) - AND (`email_accounts_used` + :accounts <= `email_accounts` OR `email_accounts` = '-1' ) " . ($this->getUserDetail('customers_see_all') ? '' : " AND `adminid` = :adminid") - ); + AND (`email_accounts_used` + :accounts <= `email_accounts` OR `email_accounts` = '-1' ) " . ($this->getUserDetail('customers_see_all') ? '' : " AND `adminid` = :adminid")); $params = array( 'customerid' => $customerid, 'subdomains' => $subdomains, @@ -897,10 +893,9 @@ class Domains extends ApiCommand implements ResourceEntity $customerid = $result['customerid']; // get customer - $json_result = Customers::getLocal($this->getUserData(), array( + $customer = $this->apiCall('Customers.get', array( 'id' => $customerid - ))->get(); - $customer = json_decode($json_result, true)['data']; + )); } // handle change of admin (move domain from admin to admin) @@ -1664,11 +1659,10 @@ class Domains extends ApiCommand implements ResourceEntity $is_stdsubdomain = $this->getParam('is_stdsubdomain', true, 0); $remove_subbutmain_domains = $this->getParam('delete_mainsubdomains', true, 0); - $json_result = Domains::getLocal($this->getUserData(), array( + $result = $this->apiCall('Domains.get', array( 'id' => $id, 'domainname' => $domainname - ))->get(); - $result = json_decode($json_result, true)['data']; + )); $id = $result['id']; // check for deletion of main-domains which are logically subdomains, #329 diff --git a/lib/classes/api/commands/class.FpmDaemons.php b/lib/classes/api/commands/class.FpmDaemons.php index b42a0c99..e740f4cf 100644 --- a/lib/classes/api/commands/class.FpmDaemons.php +++ b/lib/classes/api/commands/class.FpmDaemons.php @@ -192,11 +192,10 @@ class FpmDaemons extends ApiCommand implements ResourceEntity // required parameter $id = $this->getParam('id'); - - $json_result = PhpSettings::getLocal($this->getUserData(), array( + + $result = $this->apiCall('PhpSettings.get', array( 'id' => $id - ))->get(); - $result = json_decode($json_result, true)['data']; + )); // parameters $description = $this->getParam('description', true, $result['description']); @@ -283,11 +282,10 @@ class FpmDaemons extends ApiCommand implements ResourceEntity if ($id == 1) { standard_error('cannotdeletedefaultphpconfig', '', true); } - - $json_result = FpmDaemons::getLocal($this->getUserData(), array( + + $result = $this->apiCall('FpmDaemons.get', array( 'id' => $id - ))->get(); - $result = json_decode($json_result, true)['data']; + )); // set default fpm daemon config for all php-config that use this config that is to be deleted $upd_stmt = Database::prepare(" diff --git a/lib/classes/api/commands/class.Ftps.php b/lib/classes/api/commands/class.Ftps.php index 00e6057b..6411e3f2 100644 --- a/lib/classes/api/commands/class.Ftps.php +++ b/lib/classes/api/commands/class.Ftps.php @@ -84,10 +84,9 @@ class Ftps extends ApiCommand implements ResourceEntity if ($this->isAdmin()) { // get customer id $customer_id = $this->getParam('customer_id'); - $json_result = Customers::getLocal($this->getUserData(), array( + $customer = $this->apiCall('Customers.get', array( 'id' => $customer_id - ))->get(); - $customer = json_decode($json_result, true)['data']; + )); // check whether the customer has enough resources to get the ftp-user added if ($customer['ftps_used'] >= $customer['ftps'] && $customer['ftps'] != '-1') { throw new Exception("Customer has no more resources available", 406); @@ -254,11 +253,10 @@ class Ftps extends ApiCommand implements ResourceEntity $this->mailer()->ClearAddresses(); } $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_WARNING, "[API] added ftp-user '" . $username . "'"); - - $json_result = Ftps::getLocal($this->getUserData(), array( + + $result = $this->apiCall('Ftps.get', array( 'username' => $username - ))->get(); - $result = json_decode($json_result, true)['data']; + )); return $this->response(200, "successfull", $result); } } @@ -288,8 +286,8 @@ class Ftps extends ApiCommand implements ResourceEntity if ($this->getUserDetail('customers_see_all') == false) { // if it's a reseller or an admin who cannot see all customers, we need to check // whether the database belongs to one of his customers - $json_result = Customers::getLocal($this->getUserData())->listing(); - $custom_list_result = json_decode($json_result, true)['data']['list']; + $_custom_list_result = $this->apiCall('Customers.listing'); + $custom_list_result = $_custom_list_result['list']; $customer_ids = array(); foreach ($custom_list_result as $customer) { $customer_ids[] = $customer['customerid']; @@ -336,12 +334,11 @@ class Ftps extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $un_optional = ($id <= 0 ? false : true); $username = $this->getParam('username', $un_optional, ''); - - $json_result = Ftps::getLocal($this->getUserData(), array( + + $result = $this->apiCall('Ftps.get', array( 'id' => $id, 'username' => $username - ))->get(); - $result = json_decode($json_result, true)['data']; + )); $id = $result['id']; // parameters @@ -364,10 +361,9 @@ class Ftps extends ApiCommand implements ResourceEntity if ($this->isAdmin()) { // get customer id $customer_id = $this->getParam('customer_id'); - $json_result = Customers::getLocal($this->getUserData(), array( + $customer = $this->apiCall('Customers.get', array( 'id' => $customer_id - ))->get(); - $customer = json_decode($json_result, true)['data']; + )); } else { $customer_id = $this->getUserDetail('customerid'); $customer = $this->getUserData(); @@ -430,11 +426,10 @@ class Ftps extends ApiCommand implements ResourceEntity "customerid" => $customer['customerid'], "id" => $id ), true, true); - - $json_result = Ftps::getLocal($this->getUserData(), array( + + $result = $this->apiCall('Ftps.get', array( 'username' => $result['username'] - ))->get(); - $result = json_decode($json_result, true)['data']; + )); $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_NOTICE, "[API] updated ftp-user '" . $result['username'] . "'"); return $this->response(200, "successfull", $result); } @@ -460,16 +455,16 @@ class Ftps extends ApiCommand implements ResourceEntity $loginname = $this->getParam('loginname', true, ''); if (! empty($customerid) || ! empty($loginname)) { - $json_result = Customers::getLocal($this->getUserData(), array( + $_result = $this->apiCall('Customers.get', array( 'id' => $customerid, 'loginname' => $loginname - ))->get(); + )); $custom_list_result = array( - json_decode($json_result, true)['data'] + $_result ); } else { - $json_result = Customers::getLocal($this->getUserData())->listing(); - $custom_list_result = json_decode($json_result, true)['data']['list']; + $_custom_list_result = $this->apiCall('Customers.listing'); + $custom_list_result = $_custom_list_result['list']; } $customer_ids = array(); foreach ($custom_list_result as $customer) { @@ -526,19 +521,17 @@ class Ftps extends ApiCommand implements ResourceEntity } // get ftp-user - $json_result = Ftps::getLocal($this->getUserData(), array( + $result = $this->apiCall('Ftps.get', array( 'id' => $id, 'username' => $username - ))->get(); - $result = json_decode($json_result, true)['data']; + )); $id = $result['id']; if ($this->isAdmin()) { // get customer-data - $json_result = Customers::getLocal($this->getUserData(), array( + $customer_data = $this->apiCall('Customers.get', array( 'id' => $result['customerid'] - ))->get(); - $customer_data = json_decode($json_result, true)['data']; + )); } else { $customer_data = $this->getUserData(); } diff --git a/lib/classes/api/commands/class.IpsAndPorts.php b/lib/classes/api/commands/class.IpsAndPorts.php index f02ded1c..e6262575 100644 --- a/lib/classes/api/commands/class.IpsAndPorts.php +++ b/lib/classes/api/commands/class.IpsAndPorts.php @@ -215,10 +215,9 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity } $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] added IP/port '" . $ip . ":" . $port . "'"); // get ip for return-array - $json_result = IpsAndPorts::getLocal($this->getUserData(), array( + $result = $this->apiCall('IpsAndPorts.get', array( 'id' => $ins_data['id'] - ))->get(); - $result = json_decode($json_result, true)['data']; + )); return $this->response(200, "successfull", $result); } throw new Exception("Not allowed to execute given command.", 403); @@ -237,11 +236,10 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity { if ($this->isAdmin() && ($this->getUserDetail('change_serversettings') || ! empty($this->getUserDetail('ip')))) { $id = $this->getParam('id'); - - $json_result = IpsAndPorts::getLocal($this->getUserData(), array( + + $result = $this->apiCall('IpsAndPorts.get', array( 'id' => $id - ))->get(); - $result = json_decode($json_result, true)['data']; + )); $ip = validate_ip2($this->getParam('ip', true, $result['ip']), false, 'invalidip', false, false, false, true); $port = validate($this->getParam('port', true, $result['port']), 'port', '/^(([1-9])|([1-9][0-9])|([1-9][0-9][0-9])|([1-9][0-9][0-9][0-9])|([1-5][0-9][0-9][0-9][0-9])|(6[0-4][0-9][0-9][0-9])|(65[0-4][0-9][0-9])|(655[0-2][0-9])|(6553[0-5]))$/Di', array( @@ -373,10 +371,9 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] changed IP/port from '" . $result['ip'] . ":" . $result['port'] . "' to '" . $ip . ":" . $port . "'"); - $json_result = IpsAndPorts::getLocal($this->getUserData(), array( + $result = $this->apiCall('IpsAndPorts.get', array( 'id' => $result['id'] - ))->get(); - $result = json_decode($json_result, true)['data']; + )); return $this->response(200, "successfull", $result); } } @@ -397,11 +394,10 @@ class IpsAndPorts extends ApiCommand implements ResourceEntity { if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { $id = $this->getParam('id'); - - $json_result = IpsAndPorts::getLocal($this->getUserData(), array( + + $result = $this->apiCall('IpsAndPorts.get', array( 'id' => $id - ))->get(); - $result = json_decode($json_result, true)['data']; + )); $result_checkdomain_stmt = Database::prepare(" SELECT `id_domain` as `id` FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_ipandports` = :id diff --git a/lib/classes/api/commands/class.Mysqls.php b/lib/classes/api/commands/class.Mysqls.php index f783b34d..55929a80 100644 --- a/lib/classes/api/commands/class.Mysqls.php +++ b/lib/classes/api/commands/class.Mysqls.php @@ -62,7 +62,7 @@ class Mysqls extends ApiCommand implements ResourceEntity if (! isset($sql_root) || ! is_array($sql_root)) { throw new ErrorException("Database server with index #" . $dbserver . " is unknown", 404); } - + if ($sendinfomail != 1) { $sendinfomail = 0; } @@ -71,10 +71,9 @@ class Mysqls extends ApiCommand implements ResourceEntity if ($this->isAdmin()) { // get customer id $customer_id = $this->getParam('customer_id'); - $json_result = Customers::getLocal($this->getUserData(), array( + $customer = $this->apiCall('Customers.get', array( 'id' => $customer_id - ))->get(); - $customer = json_decode($json_result, true)['data']; + )); // check whether the customer has enough resources to get the database added if ($customer['mysqls_used'] >= $customer['mysqls'] && $customer['mysqls'] != '-1') { throw new Exception("Customer has no more resources available", 406); @@ -196,10 +195,9 @@ class Mysqls extends ApiCommand implements ResourceEntity } $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_WARNING, "[API] added mysql-database '" . $username . "'"); - $json_result = Mysqls::getLocal($this->getUserData(), array( + $result = $this->apiCall('Mysqls.get', array( 'dbname' => $username - ))->get(); - $result = json_decode($json_result, true)['data']; + )); return $this->response(200, "successfull", $result); } throw new Exception("No more resources available", 406); @@ -225,13 +223,13 @@ class Mysqls extends ApiCommand implements ResourceEntity $dn_optional = ($id <= 0 ? false : true); $dbname = $this->getParam('dbname', $dn_optional, ''); $dbserver = $this->getParam('mysql_server', true, - 1); - + if ($this->isAdmin()) { if ($this->getUserDetail('customers_see_all') != 1) { // if it's a reseller or an admin who cannot see all customers, we need to check // whether the database belongs to one of his customers - $json_result = Customers::getLocal($this->getUserData())->listing(); - $custom_list_result = json_decode($json_result, true)['data']['list']; + $_custom_list_result = $this->apiCall('Customers.listing'); + $custom_list_result = $_custom_list_result['list']; $customer_ids = array(); foreach ($custom_list_result as $customer) { $customer_ids[] = $customer['customerid']; @@ -311,7 +309,7 @@ class Mysqls extends ApiCommand implements ResourceEntity * optional, update password for the database * @param string $description * optional, description for database - * + * * @access admin, customer * @throws Exception * @return array @@ -322,17 +320,16 @@ class Mysqls extends ApiCommand implements ResourceEntity $dn_optional = ($id <= 0 ? false : true); $dbname = $this->getParam('dbname', $dn_optional, ''); $dbserver = $this->getParam('mysql_server', true, - 1); - + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'mysql')) { throw new Exception("You cannot access this resource", 405); } - $json_result = Mysqls::getLocal($this->getUserData(), array( + $result = $this->apiCall('Mysqls.get', array( 'id' => $id, 'dbname' => $dbname, 'mysql_server' => $dbserver - ))->get(); - $result = json_decode($json_result, true)['data']; + )); $id = $result['id']; // paramters @@ -352,15 +349,14 @@ class Mysqls extends ApiCommand implements ResourceEntity if (! isset($sql_root) || ! is_array($sql_root)) { throw new ErrorException("Database server with index #" . $dbserver . " is unknown", 404); } - + // get needed customer info to reduce the mysql-usage-counter by one if ($this->isAdmin()) { // get customer id $customer_id = $this->getParam('customer_id'); - $json_result = Customers::getLocal($this->getUserData(), array( - 'id' => $result['customerid'] - ))->get(); - $customer = json_decode($json_result, true)['data']; + $customer = $this->apiCall('Customers.get', array( + 'id' => $customer_id + )); // check whether the customer has enough resources to get the database added if ($customer['mysqls_used'] >= $customer['mysqls'] && $customer['mysqls'] != '-1') { throw new Exception("Customer has no more resources available", 406); @@ -406,7 +402,7 @@ class Mysqls extends ApiCommand implements ResourceEntity "id" => $id ); Database::pexecute($stmt, $params, true, true); - + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_WARNING, "[API] updated mysql-database '" . $result['databasename'] . "'"); return $this->response(200, "successfull", $params); } @@ -436,16 +432,16 @@ class Mysqls extends ApiCommand implements ResourceEntity $loginname = $this->getParam('loginname', true, ''); if (! empty($customer_id) || ! empty($loginname)) { - $json_result = Customers::getLocal($this->getUserData(), array( + $customer = $this->apiCall('Customers.get', array( 'id' => $customerid, 'loginname' => $loginname - ))->get(); + )); $custom_list_result = array( - json_decode($json_result, true)['data'] + $customer ); } else { - $json_result = Customers::getLocal($this->getUserData())->listing(); - $custom_list_result = json_decode($json_result, true)['data']['list']; + $_custom_list_result = $this->apiCall('Customers.listing'); + $custom_list_result = $_custom_list_result['list']; } $customer_ids = array(); foreach ($custom_list_result as $customer) { @@ -526,17 +522,16 @@ class Mysqls extends ApiCommand implements ResourceEntity $dn_optional = ($id <= 0 ? false : true); $dbname = $this->getParam('dbname', $dn_optional, ''); $dbserver = $this->getParam('mysql_server', true, - 1); - + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'mysql')) { throw new Exception("You cannot access this resource", 405); } - - $json_result = Mysqls::getLocal($this->getUserData(), array( + + $result = $this->apiCall('Mysqls.get', array( 'id' => $id, 'dbname' => $dbname, 'mysql_server' => $dbserver - ))->get(); - $result = json_decode($json_result, true)['data']; + )); $id = $result['id']; // Begin root-session @@ -554,10 +549,9 @@ class Mysqls extends ApiCommand implements ResourceEntity // get needed customer info to reduce the mysql-usage-counter by one if ($this->isAdmin()) { - $json_result = Customers::getLocal($this->getUserData(), array( + $customer = $this->apiCall('Customers.get', array( 'id' => $result['customerid'] - ))->get(); - $customer = json_decode($json_result, true)['data']; + )); $mysql_used = $customer['mysqls_used']; $customer_id = $customer['customer_id']; } else { diff --git a/lib/classes/api/commands/class.PhpSettings.php b/lib/classes/api/commands/class.PhpSettings.php index e25a9d09..72d3d2e9 100644 --- a/lib/classes/api/commands/class.PhpSettings.php +++ b/lib/classes/api/commands/class.PhpSettings.php @@ -230,7 +230,11 @@ class PhpSettings extends ApiCommand implements ResourceEntity inserttask('1'); $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] php setting with description '" . $description . "' has been created by '" . $this->getUserDetail('loginname') . "'"); - return $this->response(200, "successfull", $ins_data); + + $result = $this->apiCall('PhpSettings.get', array( + 'id' => $ins_data['id'] + )); + return $this->response(200, "successfull", $result); } throw new Exception("Not allowed to execute given command.", 403); } @@ -250,11 +254,10 @@ class PhpSettings extends ApiCommand implements ResourceEntity // required parameter $id = $this->getParam('id'); - - $json_result = PhpSettings::getLocal($this->getUserData(), array( + + $result = $this->apiCall('PhpSettings.get', array( 'id' => $id - ))->get(); - $result = json_decode($json_result, true)['data']; + )); // parameters $description = $this->getParam('description', true, $result['description']); @@ -341,7 +344,11 @@ class PhpSettings extends ApiCommand implements ResourceEntity inserttask('1'); $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] php setting with description '" . $description . "' has been updated by '" . $this->getUserDetail('loginname') . "'"); - return $this->response(200, "successfull", $upd_data); + + $result = $this->apiCall('PhpSettings.get', array( + 'id' => $id + )); + return $this->response(200, "successfull", $result); } throw new Exception("Not allowed to execute given command.", 403); } @@ -359,11 +366,10 @@ class PhpSettings extends ApiCommand implements ResourceEntity { if ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 1) { $id = $this->getParam('id'); - - $json_result = PhpSettings::getLocal($this->getUserData(), array( + + $result = $this->apiCall('PhpSettings.get', array( 'id' => $id - ))->get(); - $result = json_decode($json_result, true)['data']; + )); if ((Settings::Get('system.mod_fcgid') == '1' && Settings::Get('system.mod_fcgid_defaultini_ownvhost') == $id) || (Settings::Get('phpfpm.enabled') == '1' && Settings::Get('phpfpm.vhost_defaultini') == $id)) { standard_error('cannotdeletehostnamephpconfig', '', true); diff --git a/lib/classes/api/commands/class.SubDomains.php b/lib/classes/api/commands/class.SubDomains.php index eb1ea0d7..e8994ffa 100644 --- a/lib/classes/api/commands/class.SubDomains.php +++ b/lib/classes/api/commands/class.SubDomains.php @@ -80,10 +80,9 @@ class SubDomains extends ApiCommand implements ResourceEntity if ($this->isAdmin()) { // get customer id $customer_id = $this->getParam('customer_id'); - $json_result = Customers::getLocal($this->getUserData(), array( + $customer = $this->apiCall('Customers.get', array( 'id' => $customer_id - ))->get(); - $customer = json_decode($json_result, true)['data']; + )); // check whether the customer has enough resources to get the subdomain added if ($customer['subdomains_used'] >= $customer['subdomains'] && $customer['subdomains'] != '-1') { throw new Exception("Customer has no more resources available", 406); @@ -329,11 +328,10 @@ class SubDomains extends ApiCommand implements ResourceEntity Admins::increaseUsage(($this->isAdmin() ? $customer['adminid'] : $this->getUserDetail('adminid')), 'subdomains_used'); $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] added subdomain '" . $completedomain . "'"); - - $json_result = SubDomains::getLocal($this->getUserData(), array( + + $result = $this->apiCall('SubDomains.get', array( 'id' => $subdomain_id - ))->get(); - $result = json_decode($json_result, true)['data']; + )); return $this->response(200, "successfull", $result); } throw new Exception("No more resources available", 406); @@ -367,8 +365,8 @@ class SubDomains extends ApiCommand implements ResourceEntity if ($this->getUserDetail('customers_see_all') != 1) { // if it's a reseller or an admin who cannot see all customers, we need to check // whether the database belongs to one of his customers - $json_result = Customers::getLocal($this->getUserData())->listing(); - $custom_list_result = json_decode($json_result, true)['data']['list']; + $_custom_list_result = $this->apiCall('Customers.listing'); + $custom_list_result = $_custom_list_result['list']; $customer_ids = array(); foreach ($custom_list_result as $customer) { $customer_ids[] = $customer['customerid']; @@ -428,16 +426,16 @@ class SubDomains extends ApiCommand implements ResourceEntity $loginname = $this->getParam('loginname', true, ''); if (! empty($customerid) || ! empty($loginname)) { - $json_result = Customers::getLocal($this->getUserData(), array( - 'id' => $customerid, + $result = $this->apiCall('Customers.get', array( + 'id' => $id, 'loginname' => $loginname - ))->get(); + )); $custom_list_result = array( - json_decode($json_result, true)['data'] + $result ); } else { - $json_result = Customers::getLocal($this->getUserData())->listing(); - $custom_list_result = json_decode($json_result, true)['data']['list']; + $_custom_list_result = $this->apiCall('Customers.listing'); + $custom_list_result = $_custom_list_result['list']; } $customer_ids = array(); $customer_stdsubs = array(); @@ -505,20 +503,18 @@ class SubDomains extends ApiCommand implements ResourceEntity if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'domains')) { throw new Exception("You cannot access this resource", 405); } - - $json_result = SubDomains::getLocal($this->getUserData(), array( + + $result = $this->apiCall('SubDomains.get', array( 'id' => $id, 'domainname' => $domainname - ))->get(); - $result = json_decode($json_result, true)['data']; + )); $id = $result['id']; // get needed customer info to reduce the subdomain-usage-counter by one if ($this->isAdmin()) { - $json_result = Customers::getLocal($this->getUserData(), array( + $customer = $this->apiCall('Customers.get', array( 'id' => $result['customerid'] - ))->get(); - $customer = json_decode($json_result, true)['data']; + )); $subdomains_used = $customer['subdomains_used']; $customer_id = $customer['customer_id']; } else { From a869bc58cd9d27ef4dd5ad6d2bb0b8c4160eba22 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sun, 4 Mar 2018 19:47:48 +0100 Subject: [PATCH 118/746] fix wrong variable-name in SubDomains.listing Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.SubDomains.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/classes/api/commands/class.SubDomains.php b/lib/classes/api/commands/class.SubDomains.php index e8994ffa..d61c75bd 100644 --- a/lib/classes/api/commands/class.SubDomains.php +++ b/lib/classes/api/commands/class.SubDomains.php @@ -427,7 +427,7 @@ class SubDomains extends ApiCommand implements ResourceEntity if (! empty($customerid) || ! empty($loginname)) { $result = $this->apiCall('Customers.get', array( - 'id' => $id, + 'id' => $customerid, 'loginname' => $loginname )); $custom_list_result = array( From ae4a7ff9434c2537cc590be6582deb7d129ea684 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 5 Mar 2018 08:12:08 +0100 Subject: [PATCH 119/746] wrap ip-validating in Domains-ApiCommand to reduce duplicate code Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/abstract.ApiCommand.php | 2 +- lib/classes/api/commands/class.Domains.php | 314 ++++++++------------- 2 files changed, 116 insertions(+), 200 deletions(-) diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index 8a09dd32..156f84a5 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -156,7 +156,7 @@ abstract class ApiCommand // ensure that we can display messages $language = Settings::Get('panel.standardlanguage'); - if (isset($this->user_data['language']) && isset($languages[$this->user_data['language']])) { + if (isset($this->user_data['language']) && isset($langs[$this->user_data['language']])) { // default: use language from session, #277 $language = $this->user_data['language']; } elseif (isset($this->user_data['def_language'])) { diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index 99d29dec..47a9c209 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -63,7 +63,7 @@ class Domains extends ApiCommand implements ResourceEntity * optional, the domainname * @param bool $no_std_subdomain * optional, default false - * + * * @access admin * @throws Exception * @return array @@ -75,7 +75,7 @@ class Domains extends ApiCommand implements ResourceEntity $dn_optional = ($id <= 0 ? false : true); $domainname = $this->getParam('domainname', $dn_optional, ''); $no_std_subdomain = $this->getParam('no_std_subdomain', true, false); - + // convert possible idn domain to punycode if (substr($domainname, 0, 4) != 'xn--') { $idna_convert = new idna_convert_wrapper(); @@ -155,7 +155,6 @@ class Domains extends ApiCommand implements ResourceEntity $ocsp_stapling = $this->getParam('ocsp_stapling', true, 0); // validation - if ($p_domain == Settings::Get('system.hostname')) { standard_error('admin_domain_emailsystemhostname', '', true); } @@ -177,11 +176,11 @@ class Domains extends ApiCommand implements ResourceEntity 'mydomain' ), '', true); } - + $customer = $this->apiCall('Customers.get', array( 'id' => $customerid )); - + if ($this->getUserDetail('customers_see_all') == '1') { $admin_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_ADMINS . "` @@ -304,104 +303,14 @@ class Domains extends ApiCommand implements ResourceEntity $mod_fcgid_maxrequests = '-1'; } - if ($this->getUserDetail('ip') != "-1") { - $admin_ip_stmt = Database::prepare(" - SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` - WHERE `id` = :id ORDER BY `ip`, `port` ASC"); - $admin_ip = Database::pexecute_first($admin_ip_stmt, array( - 'id' => $this->getUserDetail('ip') - ), true, true); - $additional_ip_condition = " AND `ip` = :adminip "; - $aip_param = array( - 'adminip' => $admin_ip['ip'] - ); - } else { - $additional_ip_condition = ''; - $aip_param = array(); - } - - if (empty($p_ipandports)) { - throw new Exception("No IPs given, unable to add domain (no default IPs set?)", 406); - } - - $ipandports = array(); - if (! empty($p_ipandports) && is_numeric($p_ipandports)) { - $p_ipandports = array($p_ipandports); - } - if (! empty($p_ipandports) && ! is_array($p_ipandports)) { - $p_ipandports = unserialize($p_ipandports); - } - - if (! empty($p_ipandports) && is_array($p_ipandports)) { - foreach ($p_ipandports as $ipandport) { - $ipandport = intval($ipandport); - $ipandport_check_stmt = Database::prepare(" - SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` - WHERE `id` = :id " . $additional_ip_condition); - $ip_params = null; - $ip_params = array_merge(array( - 'id' => $ipandport - ), $aip_param); - $ipandport_check = Database::pexecute_first($ipandport_check_stmt, $ip_params, true, true); - - if (! isset($ipandport_check['id']) || $ipandport_check['id'] == '0' || $ipandport_check['id'] != $ipandport) { - standard_error('ipportdoesntexist', '', true); - } else { - $ipandports[] = $ipandport; - } - } - } - + // check non-ssl IP + $ipandports = $this->validateIpAddresses($p_ipandports); + // check ssl IP + $ssl_ipandports = array(); if (Settings::Get('system.use_ssl') == "1" && ! empty($p_ssl_ipandports)) { - - $ssl_ipandports = array(); - if (! empty($p_ssl_ipandports) && ! is_array($p_ssl_ipandports)) { - $p_ssl_ipandports = unserialize($p_ssl_ipandports); - } - - // Verify SSL-Ports - if (! empty($p_ssl_ipandports) && is_array($p_ssl_ipandports)) { - foreach ($p_ssl_ipandports as $ssl_ipandport) { - if (trim($ssl_ipandport) == "") { - continue; - } - // fix if no ssl-ip/port is checked - if (trim($ssl_ipandport) < 1) { - continue; - } - $ssl_ipandport = intval($ssl_ipandport); - $ssl_ipandport_check_stmt = Database::prepare(" - SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` - WHERE `id` = :id " . $additional_ip_condition); - $ip_params = null; - $ip_params = array_merge(array( - 'id' => $ssl_ipandport - ), $aip_param); - $ssl_ipandport_check = Database::pexecute_first($ssl_ipandport_check_stmt, $ip_params, true, true); - - if (! isset($ssl_ipandport_check['id']) || $ssl_ipandport_check['id'] == '0' || $ssl_ipandport_check['id'] != $ssl_ipandport) { - standard_error('ipportdoesntexist', '', true); - } else { - $ssl_ipandports[] = $ssl_ipandport; - } - } - } else { - $ssl_redirect = 0; - $letsencrypt = 0; - $http2 = 0; - // we need this for the serialize - // if ssl is disabled or no ssl-ip/port exists - $ssl_ipandports[] = - 1; - - // HSTS - $hsts_maxage = 0; - $hsts_sub = 0; - $hsts_preload = 0; - - // OCSP stapling - $ocsp_stapling = 0; - } - } else { + $ssl_ipandports = $this->validateIpAddresses($p_ssl_ipandports, true); + } + if (Settings::Get('system.use_ssl') == "0" || empty($ssl_ipandports)) { $ssl_redirect = 0; $letsencrypt = 0; $http2 = 0; @@ -741,7 +650,7 @@ class Domains extends ApiCommand implements ResourceEntity inserttask('4'); $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] added domain '" . $domain . "'"); - + $result = $this->apiCall('Domains.get', array( 'domainname' => $domain )); @@ -760,7 +669,7 @@ class Domains extends ApiCommand implements ResourceEntity * optional, the domain-id * @param string $domainname * optional, the domainname - * + * * @access admin * @throws Exception * @return array @@ -773,7 +682,7 @@ class Domains extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $dn_optional = ($id <= 0 ? false : true); $domainname = $this->getParam('domainname', $dn_optional, ''); - + // get requested domain $result = $this->apiCall('Domains.get', array( 'id' => $id, @@ -819,7 +728,7 @@ class Domains extends ApiCommand implements ResourceEntity $hsts_sub = $this->getParam('hsts_sub', true, $result['hsts_sub']); $hsts_preload = $this->getParam('hsts_preload', true, $result['hsts_preload']); $ocsp_stapling = $this->getParam('ocsp_stapling', true, $result['ocsp_stapling']); - + // count subdomain usage of source-domain $subdomains_stmt = Database::prepare(" SELECT COUNT(`id`) AS count FROM `" . TABLE_PANEL_DOMAINS . "` WHERE @@ -829,7 +738,7 @@ class Domains extends ApiCommand implements ResourceEntity 'resultid' => $result['id'] ), true, true); $subdomains = $subdomains['count']; - + // count where this domain is alias domain $alias_check_stmt = Database::prepare(" SELECT COUNT(`id`) AS count FROM `" . TABLE_PANEL_DOMAINS . "` WHERE @@ -849,11 +758,11 @@ class Domains extends ApiCommand implements ResourceEntity 'customerid' => $result['customerid'], 'id' => $result['id'] ), true, true); - + $emails = Database::num_rows(); $email_forwarders = 0; $email_accounts = 0; - + while ($domain_emails_row = $domain_emails_result_stmt->fetch(PDO::FETCH_ASSOC)) { if ($domain_emails_row['destination'] != '') { $domain_emails_row['destination'] = explode(' ', makeCorrectDestination($domain_emails_row['destination'])); @@ -864,7 +773,7 @@ class Domains extends ApiCommand implements ResourceEntity } } } - + // handle change of customer (move domain from customer to customer) if ($customerid > 0 && $customerid != $result['customerid'] && Settings::Get('panel.allow_domain_change_customer') == '1') { // check whether target customer has enough resources @@ -891,7 +800,7 @@ class Domains extends ApiCommand implements ResourceEntity } } else { $customerid = $result['customerid']; - + // get customer $customer = $this->apiCall('Customers.get', array( 'id' => $customerid @@ -963,10 +872,10 @@ class Domains extends ApiCommand implements ResourceEntity $specialsettings = validate(str_replace("\r\n", "\n", $specialsettings), 'specialsettings', '/^[^\0]*$/', '', array(), true); $documentroot = validate($documentroot, 'documentroot', '', '', array(), true); - + // when moving customer and no path is specified, update would normally reuse the current document-root // which would point to the wrong customer, therefore we will re-create that directory - if (!empty($documentroot) && $customerid > 0 && $customerid != $result['customerid'] && Settings::Get('panel.allow_domain_change_customer') == '1') { + if (! empty($documentroot) && $customerid > 0 && $customerid != $result['customerid'] && Settings::Get('panel.allow_domain_change_customer') == '1') { if (Settings::Get('system.documentroot_use_default_value') == 1) { $_documentroot = makeCorrectDir($customer['documentroot'] . '/' . $result['domain']); } else { @@ -975,7 +884,7 @@ class Domains extends ApiCommand implements ResourceEntity // set the customers default docroot $documentroot = $_documentroot; } - + if ($documentroot == '') { // If path is empty and 'Use domain name as default value for DocumentRoot path' is enabled in settings, // set default path to subdomain or domain name @@ -1041,89 +950,14 @@ class Domains extends ApiCommand implements ResourceEntity $mod_fcgid_maxrequests = $result['mod_fcgid_maxrequests']; } - $ipandports = array(); - if (! empty($p_ipandports) && is_numeric($p_ipandports)) { - $p_ipandports = array($p_ipandports); + // check non-ssl IP + $ipandports = $this->validateIpAddresses($p_ipandports, false, $result['id']); + // check ssl IP + $ssl_ipandports = array(); + if (Settings::Get('system.use_ssl') == "1" && ! empty($p_ssl_ipandports)) { + $ssl_ipandports = $this->validateIpAddresses($p_ssl_ipandports, true, $result['id']); } - if (! empty($p_ipandports) && ! is_array($p_ipandports)) { - $p_ipandports = unserialize($p_ipandports); - } - - if (! empty($p_ipandports) && is_array($p_ipandports)) { - $ipandport_check_stmt = Database::prepare(" - SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :ipandport - "); - foreach ($p_ipandports as $ipandport) { - if (trim($ipandport) == "") { - continue; - } - $ipandport = intval($ipandport); - $ipandport_check = Database::pexecute_first($ipandport_check_stmt, array( - 'ipandport' => $ipandport - ), true, true); - if (! isset($ipandport_check['id']) || $ipandport_check['id'] == '0' || $ipandport_check['id'] != $ipandport) { - standard_error('ipportdoesntexist', '', true); - } else { - $ipandports[] = $ipandport; - } - } - } else { - // set currently used ip's - $ipsresult_stmt = Database::prepare(" - SELECT `id_ipandports` FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :id - "); - Database::pexecute($ipsresult_stmt, array( - 'id' => $result['id'] - )); - while ($ipsresultrow = $ipsresult_stmt->fetch(PDO::FETCH_ASSOC)) { - $ipandports[] = $ipsresultrow['id_ipandports']; - } - } - - if (Settings::Get('system.use_ssl') == '1' && ! empty($p_ssl_ipandports)) { - $ssl_ipandports = array(); - if (! empty($p_ssl_ipandports) && ! is_array($p_ssl_ipandports)) { - $p_ssl_ipandports = unserialize($p_ssl_ipandports); - } - if (! empty($p_ssl_ipandports) && is_array($p_ssl_ipandports)) { - $ssl_ipandport_check_stmt = Database::prepare(" - SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :ipandport - "); - foreach ($p_ssl_ipandports as $ssl_ipandport) { - if (trim($ssl_ipandport) == "") { - continue; - } - // fix if ip/port got de-checked and it was the last one - if (trim($ssl_ipandport) < 1) { - continue; - } - $ssl_ipandport = intval($ssl_ipandport); - $ssl_ipandport_check = Database::pexecute_first($ssl_ipandport_check_stmt, array( - 'ipandport' => $ssl_ipandport - ), true, true); - if (! isset($ssl_ipandport_check['id']) || $ssl_ipandport_check['id'] == '0' || $ssl_ipandport_check['id'] != $ssl_ipandport) { - standard_error('ipportdoesntexist', '', true); - } else { - $ssl_ipandports[] = $ssl_ipandport; - } - } - } else { - $ssl_redirect = 0; - $letsencrypt = 0; - $http2 = 0; - // we need this for the serialize - // if ssl is disabled or no ssl-ip/port exists - $ssl_ipandports[] = - 1; - - // HSTS - $hsts_maxage = 0; - $hsts_sub = 0; - $hsts_preload = 0; - - // OCSP stapling - $ocsp_stapling = 0; - } - } else { + if (Settings::Get('system.use_ssl') == "0" || empty($ssl_ipandports)) { $ssl_redirect = 0; $letsencrypt = 0; $http2 = 0; @@ -1139,7 +973,7 @@ class Domains extends ApiCommand implements ResourceEntity // OCSP stapling $ocsp_stapling = 0; } - + // We can't enable let's encrypt for wildcard domains when using acme-v1 if ($serveraliasoption == '0' && $letsencrypt == '1' && Settings::Get('system.leapiversion') == '1') { standard_error('nowildcardwithletsencrypt', '', true); @@ -1645,7 +1479,7 @@ class Domains extends ApiCommand implements ResourceEntity * optional, remove also domains that are subdomains of this domain but added as main domains; default false * @param bool $is_stdsubdomain * optional, default false, specify whether it's a std-subdomain you are deleting as it does not count as subdomain-resource - * + * * @access admin * @throws Exception * @return array @@ -1658,7 +1492,7 @@ class Domains extends ApiCommand implements ResourceEntity $domainname = $this->getParam('domainname', $dn_optional, ''); $is_stdsubdomain = $this->getParam('is_stdsubdomain', true, 0); $remove_subbutmain_domains = $this->getParam('delete_mainsubdomains', true, 0); - + $result = $this->apiCall('Domains.get', array( 'id' => $id, 'domainname' => $domainname @@ -1788,4 +1622,86 @@ class Domains extends ApiCommand implements ResourceEntity } throw new Exception("Not allowed to execute given command.", 403); } + + /** + * validate given ips + * + * @param int|string|array $p_ipsandports + * @param boolean $edit + * default false + * @param boolean $ssl + * default false + * + * @throws Exception + * @return array + */ + private function validateIpAddresses($p_ipandports = null, $ssl = false, $edit_id = 0) + { + // when adding a new domain and no ip is given, we try to use the + // system-default, check here if there is none + // this is not required for ssl-enabled ip's + if ($edit_id <= 0 && ! $ssl && empty($p_ipandports)) { + throw new Exception("No IPs given, unable to add domain (no default IPs set?)", 406); + } + + // convert given value(s) correctly + $ipandports = array(); + if (! empty($p_ipandports) && is_numeric($p_ipandports)) { + $p_ipandports = array( + $p_ipandports + ); + } + if (! empty($p_ipandports) && ! is_array($p_ipandports)) { + $p_ipandports = unserialize($p_ipandports); + } + + // check whether there are ip usage restrictions + $additional_ip_condition = ''; + $aip_param = array(); + if ($this->getUserDetail('ip') != "-1") { + // handle multiple-ip-array + $additional_ip_condition = " AND `ip` IN (:adminips) "; + $aip_param = array( + 'adminips' => implode(",", json_decode($this->getUserDetail('ip'), true)) + ); + } + + if (! empty($p_ipandports) && is_array($p_ipandports)) { + $ipandport_check_stmt = Database::prepare(" + SELECT `id`, `ip`, `port` + FROM `" . TABLE_PANEL_IPSANDPORTS . "` + WHERE `id` = :ipandport " . ($ssl ? " AND `ssl` = '1'" : "") . $additional_ip_condition); + foreach ($p_ipandports as $ipandport) { + if (trim($ipandport) == "") { + continue; + } + // fix if no ip/port is checked + if (trim($ipandport) < 1) { + continue; + } + $ipandport = intval($ipandport); + $ip_params = array_merge(array( + 'ipandport' => $ipandport + ), $aip_param); + $ipandport_check = Database::pexecute_first($ipandport_check_stmt, $ip_params, true, true); + if (! isset($ipandport_check['id']) || $ipandport_check['id'] == '0' || $ipandport_check['id'] != $ipandport) { + standard_error('ipportdoesntexist', '', true); + } else { + $ipandports[] = $ipandport; + } + } + } elseif ($edit_id > 0) { + // set currently used ip's + $ipsresult_stmt = Database::prepare(" + SELECT `id_ipandports` FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :id + "); + Database::pexecute($ipsresult_stmt, array( + 'id' => $edit_id + ), true, true); + while ($ipsresultrow = $ipsresult_stmt->fetch(PDO::FETCH_ASSOC)) { + $ipandports[] = $ipsresultrow['id_ipandports']; + } + } + return $ipandports; + } } From b2e2590324d0d6582ed0e0d4cbc92b835e41ef6d Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 5 Mar 2018 12:13:01 +0100 Subject: [PATCH 120/746] implemented SubDomains.update; minor fixes and enhancements in Domains-Command and validateUrl-function Signed-off-by: Michael Kaufmann (d00p) --- customer_domains.php | 234 +----------- lib/classes/api/commands/class.Domains.php | 121 +------ lib/classes/api/commands/class.SubDomains.php | 334 +++++++++++++++--- .../validate/function.validateUrl.php | 32 +- tests/Domains/DomainsTest.php | 5 +- tests/SubDomains/SubDomainsTest.php | 84 ++++- 6 files changed, 402 insertions(+), 408 deletions(-) diff --git a/customer_domains.php b/customer_domains.php index e48c48f1..8cc3bf50 100644 --- a/customer_domains.php +++ b/customer_domains.php @@ -299,231 +299,23 @@ if ($page == 'overview') { } } elseif ($action == 'edit' && $id != 0) { - $stmt = Database::prepare("SELECT `d`.*, `pd`.`subcanemaildomain` - FROM `" . TABLE_PANEL_DOMAINS . "` `d`, `" . TABLE_PANEL_DOMAINS . "` `pd` - WHERE `d`.`customerid` = :customerid - AND `d`.`id` = :id - AND ((`d`.`parentdomainid`!='0' - AND `pd`.`id` = `d`.`parentdomainid`) - OR (`d`.`parentdomainid`='0' - AND `pd`.`id` = `d`.`id`)) - AND `d`.`caneditdomain`='1'"); - $result = Database::pexecute_first($stmt, array("customerid" => $userinfo['customerid'], "id" => $id)); - - $alias_stmt = Database::prepare("SELECT COUNT(`id`) AS count FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `aliasdomain`= :aliasdomain"); - $alias_check = Database::pexecute_first($alias_stmt, array("aliasdomain" => $result['id'])); - $alias_check = $alias_check['count']; - $_doredirect = false; + try { + $json_result = SubDomains::getLocal($userinfo, array( + 'id' => $id + ))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; if (isset($result['customerid']) && $result['customerid'] == $userinfo['customerid']) { if (isset($_POST['send']) && $_POST['send'] == 'send') { - if (isset($_POST['url']) && $_POST['url'] != '' && validateUrl($_POST['url'])) { - $path = $_POST['url']; - $_doredirect = true; - } else { - $path = validate($_POST['path'], 'path'); - } - - if (!preg_match('/^https?\:\/\//', $path) || !validateUrl($path)) { - if (strstr($path, ":") !== FALSE) { - standard_error('pathmaynotcontaincolon'); - } - // If path is empty or '/' and 'Use domain name as default value for DocumentRoot path' is enabled in settings, - // set default path to subdomain or domain name - if ((($path == '') || ($path == '/')) && Settings::Get('system.documentroot_use_default_value') == 1) { - $path = makeCorrectDir($userinfo['documentroot'] . '/' . $result['domain']); - } else { - $path = makeCorrectDir($userinfo['documentroot'] . '/' . $path); - } - } else { - $_doredirect = true; - } - - $aliasdomain = isset($_POST['alias']) ? intval($_POST['alias']) : 0; - - if (isset($_POST['selectserveralias'])) { - $iswildcarddomain = ($_POST['selectserveralias'] == '0') ? '1' : '0'; - $wwwserveralias = ($_POST['selectserveralias'] == '1') ? '1' : '0'; - } else { - $iswildcarddomain = $result['iswildcarddomain']; - $wwwserveralias = $result['wwwserveralias']; - } - - if ($result['parentdomainid'] != '0' && ($result['subcanemaildomain'] == '1' || $result['subcanemaildomain'] == '2') && isset($_POST['isemaildomain'])) { - $isemaildomain = intval($_POST['isemaildomain']); - } else { - $isemaildomain = $result['isemaildomain']; - } - - $aliasdomain_check = array('id' => 0); - - if ($aliasdomain != 0) { - $aliasdomain_stmt = Database::prepare("SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` `d`,`" . TABLE_PANEL_CUSTOMERS . "` `c` - WHERE `d`.`customerid`= :customerid - AND `d`.`aliasdomain` IS NULL - AND `d`.`id`<>`c`.`standardsubdomain` - AND `c`.`customerid`= :customerid - AND `d`.`id`= :id" - ); - $aliasdomain_check = Database::pexecute_first($aliasdomain_stmt, array("customerid" => $result['customerid'], "id" => $aliasdomain)); - } - - if ($aliasdomain_check['id'] != $aliasdomain) { - standard_error('domainisaliasorothercustomer'); - } - - if (isset($_POST['openbasedir_path']) && $_POST['openbasedir_path'] == '1') { - $openbasedir_path = '1'; - } else { - $openbasedir_path = '0'; - } - - // check whether the customer has chosen its own php-config - if (isset($_POST['phpsettingid'])) { - $phpsettingid = intval($_POST['phpsettingid']); - } else { - $phpsettingid = $result['phpsettingid']; - } - - if (isset($_POST['ssl_redirect']) && $_POST['ssl_redirect'] == '1') { - // a ssl-redirect only works if there actually is a - // ssl ip/port assigned to the domain - if (domainHasSslIpPort($id) == true) { - $ssl_redirect = '1'; - $_doredirect = true; - } else { - standard_error('sslredirectonlypossiblewithsslipport'); - } - } else { - $ssl_redirect = '0'; - } - - if (isset($_POST['letsencrypt']) && $_POST['letsencrypt'] == '1') { - // let's encrypt only works if there actually is a - // ssl ip/port assigned to the domain - if (domainHasSslIpPort($id) == true) { - $letsencrypt = '1'; - } else { - standard_error('letsencryptonlypossiblewithsslipport'); - } - } else { - $letsencrypt = '0'; - } - - // We can't enable let's encrypt for wildcard - domains when using acme-v1 - if ($iswildcarddomain == '1' && $letsencrypt == '1' && Settings::Get('system.leapiversion') == '1') { - standard_error('nowildcardwithletsencrypt'); - } - // if using acme-v2 we cannot issue wildcard-certificates - // because they currently only support the dns-01 challenge - if ($iswildcarddomain == '0' && $letsencrypt == '1' && Settings::Get('system.leapiversion') == '2') { - standard_error('nowildcardwithletsencryptv2'); - } - - // Temporarily deactivate ssl_redirect until Let's Encrypt certificate was generated - if ($ssl_redirect > 0 && $letsencrypt == 1 && $result['letsencrypt'] != $letsencrypt) { - $ssl_redirect = 2; - } - - // HSTS - $hsts_maxage = isset($_POST['hsts_maxage']) ? (int)$_POST['hsts_maxage'] : 0; - $hsts_sub = isset($_POST['hsts_sub']) && (int)$_POST['hsts_sub'] == 1 ? 1 : 0; - $hsts_preload = isset($_POST['hsts_preload']) && (int)$_POST['hsts_preload'] == 1 ? 1 : 0; - - if ($path == '') { - standard_error('patherror'); - } else { - if (($result['isemaildomain'] == '1') && ($isemaildomain == '0')) { - $params = array("customerid" => $userinfo['customerid'], "domainid" => $id); - $stmt = Database::prepare("DELETE FROM `" . TABLE_MAIL_USERS . "` WHERE `customerid`= :customerid AND `domainid`= :domainid"); - Database::pexecute($stmt, $params); - $stmt = Database::prepare("DELETE FROM `" . TABLE_MAIL_VIRTUAL . "` WHERE `customerid`= :customerid AND `domainid`= :domainid"); - Database::pexecute($stmt, $params); - $log->logAction(USR_ACTION, LOG_NOTICE, "automatically deleted mail-table entries for '" . $idna_convert->decode($result['domain']) . "'"); - } - - if ($_doredirect) { - $redirect = isset($_POST['redirectcode']) ? (int)$_POST['redirectcode'] : false; - updateRedirectOfDomain($id, $redirect); - } - - if ($path != $result['documentroot'] - || $isemaildomain != $result['isemaildomain'] - || $wwwserveralias != $result['wwwserveralias'] - || $iswildcarddomain != $result['iswildcarddomain'] - || $aliasdomain != $result['aliasdomain'] - || $openbasedir_path != $result['openbasedir_path'] - || $ssl_redirect != $result['ssl_redirect'] - || $letsencrypt != $result['letsencrypt'] - || $hsts_maxage != $result['hsts'] - || $hsts_sub != $result['hsts_sub'] - || $hsts_preload != $result['hsts_preload'] - || $phpsettingid != $result['phpsettingid'] - ) { - $log->logAction(USR_ACTION, LOG_INFO, "edited domain '" . $idna_convert->decode($result['domain']) . "'"); - - $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_DOMAINS . "` SET - `documentroot`= :documentroot, - `isemaildomain`= :isemaildomain, - `wwwserveralias`= :wwwserveralias, - `iswildcarddomain`= :iswildcarddomain, - `aliasdomain`= :aliasdomain, - `openbasedir_path`= :openbasedir_path, - `ssl_redirect`= :ssl_redirect, - `letsencrypt`= :letsencrypt, - `hsts` = :hsts, - `hsts_sub` = :hsts_sub, - `hsts_preload` = :hsts_preload, - `phpsettingid` = :phpsettingid - WHERE `customerid`= :customerid - AND `id`= :id" - ); - $params = array( - "documentroot" => $path, - "isemaildomain" => $isemaildomain, - "wwwserveralias" => $wwwserveralias, - "iswildcarddomain" => $iswildcarddomain, - "aliasdomain" => ($aliasdomain != 0 && $alias_check == 0) ? $aliasdomain : null, - "openbasedir_path" => $openbasedir_path, - "ssl_redirect" => $ssl_redirect, - "letsencrypt" => $letsencrypt, - "hsts" => $hsts_maxage, - "hsts_sub" => $hsts_sub, - "hsts_preload" => $hsts_preload, - "phpsettingid" => $phpsettingid, - "customerid" => $userinfo['customerid'], - "id" => $id - ); - Database::pexecute($stmt, $params); - - if ($result['aliasdomain'] != $aliasdomain) { - // trigger when domain id for alias destination has changed: both for old and new destination - triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $log); - triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $log); - } elseif ($result['wwwserveralias'] != $wwwserveralias || $result['letsencrypt'] != $letsencrypt) { - // or when wwwserveralias or letsencrypt was changed - triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $log); - } - - // check whether LE has been disabled, so we remove the certificate - if ($letsencrypt == '0' && $result['letsencrypt'] == '1') { - $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` WHERE `domainid` = :id - "); - Database::pexecute($del_stmt, array( - 'id' => $id - )); - } - - inserttask('1'); - - // Using nameserver, insert a task which rebuilds the server config - inserttask('4'); - - } - - redirectTo($filename, array('page' => $page, 's' => $s)); + try { + SubDomains::getLocal($userinfo, $_POST)->update(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } + redirectTo($filename, array('page' => $page, 's' => $s)); } else { $result['domain'] = $idna_convert->decode($result['domain']); diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index 47a9c209..364198a7 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -469,66 +469,6 @@ class Domains extends ApiCommand implements ResourceEntity } elseif ($aliasdomain_check['id'] != $aliasdomain) { standard_error('domainisaliasorothercustomer', '', true); } else { - - /** - * - * @todo how to handle security questions now? - * - * $params = array( - * 'page' => $page, - * 'action' => $action, - * 'domain' => $domain, - * 'customerid' => $customerid, - * 'adminid' => $adminid, - * 'documentroot' => $documentroot, - * 'alias' => $aliasdomain, - * 'isbinddomain' => $isbinddomain, - * 'isemaildomain' => $isemaildomain, - * 'email_only' => $email_only, - * 'subcanemaildomain' => $subcanemaildomain, - * 'caneditdomain' => $caneditdomain, - * 'zonefile' => $zonefile, - * 'dkim' => $dkim, - * 'speciallogfile' => $speciallogfile, - * 'selectserveralias' => $serveraliasoption, - * 'ipandport' => serialize($ipandports), - * 'ssl_redirect' => $ssl_redirect, - * 'ssl_ipandport' => serialize($ssl_ipandports), - * 'phpenabled' => $phpenabled, - * 'openbasedir' => $openbasedir, - * 'phpsettingid' => $phpsettingid, - * 'mod_fcgid_starter' => $mod_fcgid_starter, - * 'mod_fcgid_maxrequests' => $mod_fcgid_maxrequests, - * 'specialsettings' => $specialsettings, - * 'notryfiles' => $notryfiles, - * 'registration_date' => $registration_date, - * 'termination_date' => $termination_date, - * 'issubof' => $issubof, - * 'letsencrypt' => $letsencrypt, - * 'http2' => $http2, - * 'hsts_maxage' => $hsts_maxage, - * 'hsts_sub' => $hsts_sub, - * 'hsts_preload' => $hsts_preload, - * 'ocsp_stapling' => $ocsp_stapling - * ); - * - * $security_questions = array( - * 'reallydisablesecuritysetting' => ($openbasedir == '0' && $userinfo['change_serversettings'] == '1'), - * 'reallydocrootoutofcustomerroot' => (substr($documentroot, 0, strlen($customer['documentroot'])) != $customer['documentroot'] && ! preg_match('/^https?\:\/\//', $documentroot)) - * ); - * $question_nr = 1; - * foreach ($security_questions as $question_name => $question_launch) { - * if ($question_launch !== false) { - * $params[$question_name] = $question_name; - * - * if (! isset($_POST[$question_name]) || $_POST[$question_name] != $question_name) { - * ask_yesno('admin_domain_' . $question_name, $filename, $params, $question_nr); - * } - * } - * $question_nr ++; - * } - */ - $wwwserveralias = ($serveraliasoption == '1') ? '1' : '0'; $iswildcarddomain = ($serveraliasoption == '0') ? '1' : '0'; @@ -1087,66 +1027,7 @@ class Domains extends ApiCommand implements ResourceEntity if ($serveraliasoption != '1' && $serveraliasoption != '2') { $serveraliasoption = '0'; } - - /** - * - * @todo how to handle security questions now? - * - * $params = array( - * 'id' => $id, - * 'page' => $page, - * 'action' => $action, - * 'customerid' => $customerid, - * 'adminid' => $adminid, - * 'documentroot' => $documentroot, - * 'alias' => $aliasdomain, - * 'isbinddomain' => $isbinddomain, - * 'isemaildomain' => $isemaildomain, - * 'email_only' => $email_only, - * 'subcanemaildomain' => $subcanemaildomain, - * 'caneditdomain' => $caneditdomain, - * 'zonefile' => $zonefile, - * 'dkim' => $dkim, - * 'selectserveralias' => $serveraliasoption, - * 'ssl_redirect' => $ssl_redirect, - * 'phpenabled' => $phpenabled, - * 'openbasedir' => $openbasedir, - * 'phpsettingid' => $phpsettingid, - * 'phpsettingsforsubdomains' => $phpfs, - * 'mod_fcgid_starter' => $mod_fcgid_starter, - * 'mod_fcgid_maxrequests' => $mod_fcgid_maxrequests, - * 'specialsettings' => $specialsettings, - * 'specialsettingsforsubdomains' => $ssfs, - * 'notryfiles' => $notryfiles, - * 'registration_date' => $registration_date, - * 'termination_date' => $termination_date, - * 'issubof' => $issubof, - * 'speciallogfile' => $speciallogfile, - * 'speciallogverified' => $speciallogverified, - * 'ipandport' => serialize($ipandports), - * 'ssl_ipandport' => serialize($ssl_ipandports), - * 'letsencrypt' => $letsencrypt, - * 'http2' => $http2, - * 'hsts_maxage' => $hsts_maxage, - * 'hsts_sub' => $hsts_sub, - * 'hsts_preload' => $hsts_preload, - * 'ocsp_stapling' => $ocsp_stapling - * ); - * - * $security_questions = array( - * 'reallydisablesecuritysetting' => ($openbasedir == '0' && $userinfo['change_serversettings'] == '1'), - * 'reallydocrootoutofcustomerroot' => (substr($documentroot, 0, strlen($customer['documentroot'])) != $customer['documentroot'] && ! preg_match('/^https?\:\/\//', $documentroot)) - * ); - * foreach ($security_questions as $question_name => $question_launch) { - * if ($question_launch !== false) { - * $params[$question_name] = $question_name; - * if (! isset($_POST[$question_name]) || $_POST[$question_name] != $question_name) { - * ask_yesno('admin_domain_' . $question_name, $filename, $params); - * } - * } - * } - */ - + $wwwserveralias = ($serveraliasoption == '1') ? '1' : '0'; $iswildcarddomain = ($serveraliasoption == '0') ? '1' : '0'; diff --git a/lib/classes/api/commands/class.SubDomains.php b/lib/classes/api/commands/class.SubDomains.php index d61c75bd..6ef6592a 100644 --- a/lib/classes/api/commands/class.SubDomains.php +++ b/lib/classes/api/commands/class.SubDomains.php @@ -62,6 +62,7 @@ class SubDomains extends ApiCommand implements ResourceEntity $openbasedir_path = $this->getParam('openbasedir_path', true, 1); $phpsettingid = $this->getParam('phpsettingid', true, 0); $redirectcode = $this->getParam('redirectcode', true, Settings::Get('customredirect.default')); + $isemaildomain = $this->getParam('isemaildomain', true, 0); if (Settings::Get('system.use_ssl')) { $ssl_redirect = $this->getParam('ssl_redirect', true, 0); $letsencrypt = $this->getParam('letsencrypt', true, 0); @@ -76,7 +77,7 @@ class SubDomains extends ApiCommand implements ResourceEntity $hsts_preload = 0; } - // get needed customer info to reduce the mysql-usage-counter by one + // get needed customer info to reduce the subdomain-usage-counter by one if ($this->isAdmin()) { // get customer id $customer_id = $this->getParam('customer_id'); @@ -115,7 +116,7 @@ class SubDomains extends ApiCommand implements ResourceEntity if ($completedomain == Settings::Get('system.hostname')) { standard_error('admin_domain_emailsystemhostname', '', true); } - + // check whether the domain already exists $completedomain_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_DOMAINS . "` @@ -128,12 +129,12 @@ class SubDomains extends ApiCommand implements ResourceEntity "domain" => $completedomain, "customerid" => $customer_id ), true, true); - + if ($completedomain_check) { // no exception so far - domain exists standard_error('domainexistalready', $completedomain, true); } - + // alias domain checked? if ($aliasdomain != 0) { // also check ip/port combination to be the same, #176 @@ -160,36 +161,13 @@ class SubDomains extends ApiCommand implements ResourceEntity triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $this->logger()); } - // check whether an URL was specified - $_doredirect = false; - if (! empty($url) && validateUrl($url)) { - $path = $url; - $_doredirect = true; - } else { - $path = validate($path, 'path', '', '', array(), true); - } - - // check whether path is a real path - if (! preg_match('/^https?\:\/\//', $path) || ! validateUrl($path)) { - if (strstr($path, ":") !== false) { - standard_error('pathmaynotcontaincolon', '', true); - } - // If path is empty or '/' and 'Use domain name as default value for DocumentRoot path' is enabled in settings, - // set default path to subdomain or domain name - if ((($path == '') || ($path == '/')) && Settings::Get('system.documentroot_use_default_value') == 1) { - $path = makeCorrectDir($customer['documentroot'] . '/' . $completedomain); - } else { - $path = makeCorrectDir($customer['documentroot'] . '/' . $path); - } - } else { - // no it's not, create a redirect - $_doredirect = true; - } + // validate / correct path/url of domain + $path = $this->validateDomainDocumentRoot($path, $url, $customer, $completedomain, $_doredirect); if ($openbasedir_path != 1) { $openbasedir_path = 0; } - + // get main domain for various checks $domain_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_DOMAINS . "` @@ -214,7 +192,14 @@ class SubDomains extends ApiCommand implements ResourceEntity // the domain does already exist as main-domain standard_error('domainexistalready', $completedomain, true); } - + + // if allowed, check for 'is email domain'-flag + if ($domain_check['subcanemaildomain'] == '1' || $domain_check['subcanemaildomain'] == '2') { + $isemaildomain = intval($isemaildomain); + } else { + $isemaildomain = $domain_check['subcanemaildomain'] == '3' ? 1 : 0; + } + if ($ssl_redirect != 0) { // a ssl-redirect only works if there actually is a // ssl ip/port assigned to the domain @@ -289,7 +274,7 @@ class SubDomains extends ApiCommand implements ResourceEntity "parentdomainid" => $domain_check['id'], "wwwserveralias" => $domain_check['wwwserveralias'] == '1' ? '1' : '0', "iswildcarddomain" => $domain_check['iswildcarddomain'] == '1' ? '1' : '0', - "isemaildomain" => $domain_check['subcanemaildomain'] == '3' ? '1' : '0', + "isemaildomain" => $isemaildomain, "openbasedir" => $domain_check['openbasedir'], "openbasedir_path" => $openbasedir_path, "phpenabled" => $domain_check['phpenabled'], @@ -328,7 +313,7 @@ class SubDomains extends ApiCommand implements ResourceEntity Admins::increaseUsage(($this->isAdmin() ? $customer['adminid'] : $this->getUserDetail('adminid')), 'subdomains_used'); $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] added subdomain '" . $completedomain . "'"); - + $result = $this->apiCall('SubDomains.get', array( 'id' => $subdomain_id )); @@ -373,8 +358,10 @@ class SubDomains extends ApiCommand implements ResourceEntity } if (count($customer_ids) > 0) { $result_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_DOMAINS . "` - WHERE " . ($id > 0 ? "`id` = :iddn" : "`domain` = :iddn") . " AND `customerid` IN (:customerids) + SELECT d.*, pd.`subcanemaildomain` + FROM `" . TABLE_PANEL_DOMAINS . "` d, `" . TABLE_PANEL_DOMAINS . "` pd + WHERE " . ($id > 0 ? "d.`id` = :iddn" : "d.`domain` = :iddn") . " AND d.`customerid` IN (:customerids) + AND ((d.`parentdomainid`!='0' AND pd.`id` = d.`parentdomainid`) OR (d.`parentdomainid`='0' AND pd.`id` = d.`id`)) "); $params = array( 'iddn' => ($id <= 0 ? $domainname : $id), @@ -385,8 +372,11 @@ class SubDomains extends ApiCommand implements ResourceEntity } } else { $result_stmt = Database::prepare(" - SELECT * FROM `" . TABLE_PANEL_DOMAINS . "` - WHERE " . ($id > 0 ? "`id` = :iddn" : "`domain` = :iddn")); + SELECT d.*, pd.`subcanemaildomain` + FROM `" . TABLE_PANEL_DOMAINS . "` d, `" . TABLE_PANEL_DOMAINS . "` pd + WHERE " . ($id > 0 ? "d.`id` = :iddn" : "d.`domain` = :iddn") . " + AND ((d.`parentdomainid`!='0' AND pd.`id` = d.`parentdomainid`) OR (d.`parentdomainid`='0' AND pd.`id` = d.`id`)) + "); $params = array( 'iddn' => ($id <= 0 ? $domainname : $id) ); @@ -396,8 +386,11 @@ class SubDomains extends ApiCommand implements ResourceEntity throw new Exception("You cannot access this resource", 405); } $result_stmt = Database::prepare(" - SELECT `id`, `customerid`, `domain`, `documentroot`, `isemaildomain`, `parentdomainid`, `aliasdomain`, `caneditdomain` FROM `" . TABLE_PANEL_DOMAINS . "` - WHERE `customerid`= :customerid AND " . ($id > 0 ? "`id` = :iddn" : "`domain` = :iddn")); + SELECT d.*, pd.`subcanemaildomain` + FROM `" . TABLE_PANEL_DOMAINS . "` d, `" . TABLE_PANEL_DOMAINS . "` pd + WHERE d.`customerid`= :customerid AND " . ($id > 0 ? "d.`id` = :iddn" : "d.`domain` = :iddn") . " + AND ((d.`parentdomainid`!='0' AND pd.`id` = d.`parentdomainid`) OR (d.`parentdomainid`='0' AND pd.`id` = d.`id`)) + "); $params = array( 'customerid' => $this->getUserDetail('customerid'), 'iddn' => ($id <= 0 ? $domainname : $id) @@ -414,7 +407,225 @@ class SubDomains extends ApiCommand implements ResourceEntity public function update() { - throw new Exception("Not available yet.", 501); + $id = $this->getParam('id', true, 0); + $dn_optional = ($id <= 0 ? false : true); + $domainname = $this->getParam('domainname', $dn_optional, ''); + + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'domains')) { + throw new Exception("You cannot access this resource", 405); + } + + $result = $this->apiCall('SubDomains.get', array( + 'id' => $id, + 'domainname' => $domainname + )); + $id = $result['id']; + + // parameters + $aliasdomain = $this->getParam('alias', true, 0); + $path = $this->getParam('path', true, $result['documentroot']); + $url = $this->getParam('url', true, ''); + // default: 0 = wildcard, 1 = www-alias, 2 = none + $_serveraliasdefault = $result['iswildcarddomain'] == '1' ? 0 : ($result['wwwserveralias'] == '1' ? 1 : 2); + $selectserveralias = $this->getParam('selectserveralias', true, $_serveraliasdefault); + $isemaildomain = $this->getParam('isemaildomain', true, $result['isemaildomain']); + $openbasedir_path = $this->getParam('openbasedir_path', true, $result['openbasedir_path']); + $phpsettingid = $this->getParam('phpsettingid', true, $result['phpsettingid']); + $redirectcode = $this->getParam('redirectcode', true, getDomainRedirectId($id)); + if (Settings::Get('system.use_ssl')) { + $ssl_redirect = $this->getParam('ssl_redirect', true, $result['ssl_redirect']); + $letsencrypt = $this->getParam('letsencrypt', true, $result['letsencrypt']); + $hsts_maxage = $this->getParam('hsts_maxage', true, $result['hsts']); + $hsts_sub = $this->getParam('hsts_sub', true, $result['hsts_sub']); + $hsts_preload = $this->getParam('hsts_preload', true, $result['hsts_preload']); + } else { + $ssl_redirect = 0; + $letsencrypt = 0; + $hsts_maxage = 0; + $hsts_sub = 0; + $hsts_preload = 0; + } + + // get needed customer info to reduce the subdomain-usage-counter by one + if ($this->isAdmin()) { + // get customer id + $customer_id = $this->getParam('customer_id'); + $customer = $this->apiCall('Customers.get', array( + 'id' => $customer_id + )); + // check whether the customer has enough resources to get the subdomain added + if ($customer['subdomains_used'] >= $customer['subdomains'] && $customer['subdomains'] != '-1') { + throw new Exception("Customer has no more resources available", 406); + } + } else { + $customer_id = $this->getUserDetail('customerid'); + $customer = $this->getUserData(); + } + + $alias_stmt = Database::prepare("SELECT COUNT(`id`) AS count FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `aliasdomain`= :aliasdomain"); + $alias_check = Database::pexecute_first($alias_stmt, array( + "aliasdomain" => $result['id'] + )); + $alias_check = $alias_check['count']; + + // alias domain checked? + if ($aliasdomain != 0) { + $aliasdomain_stmt = Database::prepare(" + SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` `d`,`" . TABLE_PANEL_CUSTOMERS . "` `c` + WHERE `d`.`customerid`= :customerid + AND `d`.`aliasdomain` IS NULL + AND `d`.`id`<>`c`.`standardsubdomain` + AND `c`.`customerid`= :customerid + AND `d`.`id`= :id + "); + $aliasdomain_check = Database::pexecute_first($aliasdomain_stmt, array( + "id" => $aliasdomain, + "customerid" => $customer_id + ), true, true); + if ($aliasdomain_check['id'] != $aliasdomain) { + standard_error('domainisaliasorothercustomer', '', true); + } + triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $this->logger()); + } + + // validate / correct path/url of domain + $path = $this->validateDomainDocumentRoot($path, $url, $customer, $result['domain'], $_doredirect); + + // set alias-fields according to selected alias mode + $iswildcarddomain = ($selectserveralias == '0') ? '1' : '0'; + $wwwserveralias = ($selectserveralias == '1') ? '1' : '0'; + + // if allowed, check for 'is email domain'-flag + if ($result['parentdomainid'] != '0' && ($result['subcanemaildomain'] == '1' || $result['subcanemaildomain'] == '2') && $isemaildomain != $result['isemaildomain']) { + $isemaildomain = intval($isemaildomain); + } else { + $isemaildomain = $result['subcanemaildomain'] == '3' ? 1 : 0; + } + + // check changes of openbasedir-path variable + if ($openbasedir_path != 1) { + $openbasedir_path = 0; + } + + if ($ssl_redirect != 0) { + // a ssl-redirect only works if there actually is a + // ssl ip/port assigned to the domain + if (domainHasSslIpPort($result['id']) == true) { + $ssl_redirect = '1'; + $_doredirect = true; + } else { + standard_error('sslredirectonlypossiblewithsslipport', '', true); + } + } + + if ($letsencrypt != 0) { + // let's encrypt only works if there actually is a + // ssl ip/port assigned to the domain + if (domainHasSslIpPort($result['id']) == true) { + $letsencrypt = '1'; + } else { + standard_error('letsencryptonlypossiblewithsslipport', '', true); + } + } + + // We can't enable let's encrypt for wildcard - domains when using acme-v1 + if ($iswildcarddomain == '1' && $letsencrypt == '1' && Settings::Get('system.leapiversion') == '1') { + standard_error('nowildcardwithletsencrypt'); + } + // if using acme-v2 we cannot issue wildcard-certificates + // because they currently only support the dns-01 challenge + if ($iswildcarddomain == '0' && $letsencrypt == '1' && Settings::Get('system.leapiversion') == '2') { + standard_error('nowildcardwithletsencryptv2'); + } + + // Temporarily deactivate ssl_redirect until Let's Encrypt certificate was generated + if ($ssl_redirect > 0 && $letsencrypt == 1 && $result['letsencrypt'] != $letsencrypt) { + $ssl_redirect = 2; + } + + // is-email-domain flag changed - remove mail accounts and mail-addresses + if (($result['isemaildomain'] == '1') && $isemaildomain == '0') { + $params = array( + "customerid" => $customer['customerid'], + "domainid" => $id + ); + $stmt = Database::prepare("DELETE FROM `" . TABLE_MAIL_USERS . "` WHERE `customerid`= :customerid AND `domainid`= :domainid"); + Database::pexecute($stmt, $params, true, true); + $stmt = Database::prepare("DELETE FROM `" . TABLE_MAIL_VIRTUAL . "` WHERE `customerid`= :customerid AND `domainid`= :domainid"); + Database::pexecute($stmt, $params, true, true); + $idna_convert = new idna_convert_wrapper(); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_NOTICE, "[API] automatically deleted mail-table entries for '" . $idna_convert->decode($result['domain']) . "'"); + } + + // handle redirect + if ($_doredirect) { + updateRedirectOfDomain($id, $redirectcode); + } + + if ($path != $result['documentroot'] || $isemaildomain != $result['isemaildomain'] || $wwwserveralias != $result['wwwserveralias'] || $iswildcarddomain != $result['iswildcarddomain'] || $aliasdomain != $result['aliasdomain'] || $openbasedir_path != $result['openbasedir_path'] || $ssl_redirect != $result['ssl_redirect'] || $letsencrypt != $result['letsencrypt'] || $hsts_maxage != $result['hsts'] || $hsts_sub != $result['hsts_sub'] || $hsts_preload != $result['hsts_preload'] || $phpsettingid != $result['phpsettingid']) { + $stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_DOMAINS . "` SET + `documentroot`= :documentroot, + `isemaildomain`= :isemaildomain, + `wwwserveralias`= :wwwserveralias, + `iswildcarddomain`= :iswildcarddomain, + `aliasdomain`= :aliasdomain, + `openbasedir_path`= :openbasedir_path, + `ssl_redirect`= :ssl_redirect, + `letsencrypt`= :letsencrypt, + `hsts` = :hsts, + `hsts_sub` = :hsts_sub, + `hsts_preload` = :hsts_preload, + `phpsettingid` = :phpsettingid + WHERE `customerid`= :customerid AND `id`= :id + "); + $params = array( + "documentroot" => $path, + "isemaildomain" => $isemaildomain, + "wwwserveralias" => $wwwserveralias, + "iswildcarddomain" => $iswildcarddomain, + "aliasdomain" => ($aliasdomain != 0 && $alias_check == 0) ? $aliasdomain : null, + "openbasedir_path" => $openbasedir_path, + "ssl_redirect" => $ssl_redirect, + "letsencrypt" => $letsencrypt, + "hsts" => $hsts_maxage, + "hsts_sub" => $hsts_sub, + "hsts_preload" => $hsts_preload, + "phpsettingid" => $phpsettingid, + "customerid" => $customer['customerid'], + "id" => $id + ); + Database::pexecute($stmt, $params, true, true); + + if ($result['aliasdomain'] != $aliasdomain) { + // trigger when domain id for alias destination has changed: both for old and new destination + triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $this->logger()); + triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $this->logger()); + } elseif ($result['wwwserveralias'] != $wwwserveralias || $result['letsencrypt'] != $letsencrypt) { + // or when wwwserveralias or letsencrypt was changed + triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $this->logger()); + } + + // check whether LE has been disabled, so we remove the certificate + if ($letsencrypt == '0' && $result['letsencrypt'] == '1') { + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` WHERE `domainid` = :id + "); + Database::pexecute($del_stmt, array( + 'id' => $id + ), true, true); + } + + inserttask('1'); + inserttask('4'); + + $idna_convert = new idna_convert_wrapper(); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] edited domain '" . $idna_convert->decode($result['domain']) . "'"); + } + $result = $this->apiCall('SubDomains.get', array( + 'id' => $id + )); + return $this->response(200, "successfull", $result); } public function listing() @@ -503,7 +714,7 @@ class SubDomains extends ApiCommand implements ResourceEntity if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'domains')) { throw new Exception("You cannot access this resource", 405); } - + $result = $this->apiCall('SubDomains.get', array( 'id' => $id, 'domainname' => $domainname @@ -600,4 +811,45 @@ class SubDomains extends ApiCommand implements ResourceEntity $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_WARNING, "[API] deleted subdomain '" . $result['domain'] . "'"); return $this->response(200, "successfull", $result); } + + /** + * validate given path and replace with url if given and valid + * + * @param string $path + * @param string $url + * @param array $customer + * @param string $completedomain + * @param boolean $_doredirect + * + * @return string validated path + */ + private function validateDomainDocumentRoot($path = null, $url = null, $customer = null, $completedomain = null, &$_doredirect = false) + { + // check whether an URL was specified + $_doredirect = false; + if (! empty($url) && validateUrl($url)) { + $path = $url; + $_doredirect = true; + } else { + $path = validate($path, 'path', '', '', array(), true); + } + + // check whether path is a real path + if (! preg_match('/^https?\:\/\//', $path) || ! validateUrl($path)) { + if (strstr($path, ":") !== false) { + standard_error('pathmaynotcontaincolon', '', true); + } + // If path is empty or '/' and 'Use domain name as default value for DocumentRoot path' is enabled in settings, + // set default path to subdomain or domain name + if ((($path == '') || ($path == '/')) && Settings::Get('system.documentroot_use_default_value') == 1) { + $path = makeCorrectDir($customer['documentroot'] . '/' . $completedomain); + } else { + $path = makeCorrectDir($customer['documentroot'] . '/' . $path); + } + } else { + // no it's not, create a redirect + $_doredirect = true; + } + return $path; + } } diff --git a/lib/functions/validate/function.validateUrl.php b/lib/functions/validate/function.validateUrl.php index 0515e48d..680a7a4d 100644 --- a/lib/functions/validate/function.validateUrl.php +++ b/lib/functions/validate/function.validateUrl.php @@ -20,53 +20,49 @@ /** * Returns whether a URL is in a correct format or not * - * @param string URL to be tested + * @param + * string URL to be tested * @return bool * @author Christian Hoffmann * @author Froxlor team (2010-) - * + * */ -function validateUrl($url) { - - global $idna_convert, $theme; - - if (strtolower(substr($url, 0, 7)) != "http://" - && strtolower(substr($url, 0, 8)) != "https://" - ) { +function validateUrl($url) +{ + if (strtolower(substr($url, 0, 7)) != "http://" && strtolower(substr($url, 0, 8)) != "https://") { $url = 'http://' . $url; } - + // needs converting try { + $idna_convert = new idna_convert_wrapper(); $url = $idna_convert->encode($url); } catch (Exception $e) { return false; } - + $pattern = "/^https?:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,4}(\:[0-9]+)?\/?(.+)?$/i"; if (preg_match($pattern, $url)) { return true; } - + // not an fqdn - if (strtolower(substr($url, 0, 7)) == "http://" - || strtolower(substr($url, 0, 8)) == "https://" - ) { + if (strtolower(substr($url, 0, 7)) == "http://" || strtolower(substr($url, 0, 8)) == "https://") { if (strtolower(substr($url, 0, 7)) == "http://") { $ip = strtolower(substr($url, 7)); } - + if (strtolower(substr($url, 0, 8)) == "https://") { $ip = strtolower(substr($url, 8)); } - + $ip = substr($ip, 0, strpos($ip, '/')); // possible : in IP (when a port is given), #1173 // but only if there actually IS ONE if (strpos($ip, ':') !== false) { $ip = substr($ip, 0, strpos($ip, ':')); } - + if (validate_ip($ip, true) !== false) { return true; } else { diff --git a/tests/Domains/DomainsTest.php b/tests/Domains/DomainsTest.php index d2b05651..ed13d5ff 100644 --- a/tests/Domains/DomainsTest.php +++ b/tests/Domains/DomainsTest.php @@ -91,11 +91,14 @@ class DomainsTest extends TestCase $data = [ 'domain' => 'test2.local', 'customerid' => 1, - 'ipandport' => 3 + 'ipandport' => 3, + 'isemaildomain' => 1, + 'subcanemaildomain' => 2 ]; $json_result = Domains::getLocal($reseller_userdata, $data)->add(); $result = json_decode($json_result, true)['data']; $this->assertEquals('test2.local', $result['domain']); + $this->assertEquals(2, $result['subcanemaildomain']); } public function testAdminDomainsAddSysHostname() diff --git a/tests/SubDomains/SubDomainsTest.php b/tests/SubDomains/SubDomainsTest.php index 1e77e43a..f930ca5e 100644 --- a/tests/SubDomains/SubDomainsTest.php +++ b/tests/SubDomains/SubDomainsTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\TestCase; */ class SubDomainsTest extends TestCase { + public function testCustomerSubDomainsAdd() { global $admin_userdata; @@ -46,7 +47,7 @@ class SubDomainsTest extends TestCase $result = json_decode($json_result, true)['data']; $this->assertEquals('mysub2.test2.local', $result['domain']); } - + public function testCustomerSubDomainsAddNoPunycode() { global $admin_userdata; @@ -100,7 +101,7 @@ class SubDomainsTest extends TestCase $this->expectExceptionMessage("Wrong Input in Field 'Domain'"); SubDomains::getLocal($customer_userdata, $data)->add(); } - + /** * @depends testCustomerSubDomainsAdd */ @@ -122,7 +123,72 @@ class SubDomainsTest extends TestCase $this->assertEquals('mysub.test2.local', $result['domain']); $this->assertEquals(1, $result['customerid']); } - + + /** + * @depends testCustomerSubDomainsAdd + */ + public function testAdminSubDomainsGetMainDomain() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'domainname' => 'test2.local' + ]; + $json_result = SubDomains::getLocal($admin_userdata, $data)->get(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('test2.local', $result['domain']); + $this->assertEquals(1, $result['customerid']); + } + + /** + * @depends testCustomerSubDomainsAdd + */ + public function testAdminSubDomainsUpdate() + { + global $admin_userdata; + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $data = [ + 'domainname' => 'mysub.test2.local', + 'path' => 'mysub.test2.local', + 'isemaildomain' => 1, + 'customer_id' => $customer_userdata['customerid'] + ]; + $json_result = SubDomains::getLocal($admin_userdata, $data)->update(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals($customer_userdata['documentroot'] . 'mysub.test2.local/', $result['documentroot']); + } + + /** + * @depends testAdminSubDomainsUpdate + */ + public function testCustomerSubDomainsUpdate() + { + global $admin_userdata; + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $data = [ + 'domainname' => 'mysub.test2.local', + 'url' => 'https://www.froxlor.org/', + 'isemaildomain' => 0, + ]; + $json_result = SubDomains::getLocal($customer_userdata, $data)->update(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('https://www.froxlor.org/', $result['documentroot']); + } + public function testCustomerSubDomainsList() { global $admin_userdata; @@ -136,7 +202,7 @@ class SubDomainsTest extends TestCase $result = json_decode($json_result, true)['data']; $this->assertEquals(3, $result['count']); } - + public function testResellerSubDomainsList() { global $admin_userdata; @@ -154,11 +220,13 @@ class SubDomainsTest extends TestCase public function testAdminSubDomainsListWithCustomer() { global $admin_userdata; - $json_result = SubDomains::getLocal($admin_userdata, ['loginname' => 'test1'])->listing(); + $json_result = SubDomains::getLocal($admin_userdata, [ + 'loginname' => 'test1' + ])->listing(); $result = json_decode($json_result, true)['data']; $this->assertEquals(3, $result['count']); } - + /** * @depends testCustomerSubDomainsList */ @@ -170,7 +238,9 @@ class SubDomainsTest extends TestCase 'loginname' => 'test1' ))->get(); $customer_userdata = json_decode($json_result, true)['data']; - $json_result = SubDomains::getLocal($customer_userdata, ['domainname' => 'mysub.test2.local'])->delete(); + $json_result = SubDomains::getLocal($customer_userdata, [ + 'domainname' => 'mysub.test2.local' + ])->delete(); $result = json_decode($json_result, true)['data']; $this->assertEquals('mysub.test2.local', $result['domain']); $this->assertEquals($customer_userdata['customerid'], $result['customerid']); From f5ec759d998fba8f65a06dc4c31c5a5b08c58197 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 5 Mar 2018 14:21:36 +0100 Subject: [PATCH 121/746] added Certificates-ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- customer_domains.php | 86 +---- .../api/commands/class.Certificates.php | 325 ++++++++++++++++++ lib/classes/api/commands/class.SubDomains.php | 21 +- ssl_certificates.php | 31 +- 4 files changed, 357 insertions(+), 106 deletions(-) create mode 100644 lib/classes/api/commands/class.Certificates.php diff --git a/customer_domains.php b/customer_domains.php index 8cc3bf50..c97f6b07 100644 --- a/customer_domains.php +++ b/customer_domains.php @@ -447,87 +447,16 @@ if ($page == 'overview') { if ($action == '' || $action == 'view') { if (isset($_POST['send']) && $_POST['send'] == 'send') { - - $ssl_cert_file = isset($_POST['ssl_cert_file']) ? $_POST['ssl_cert_file'] : ''; - $ssl_key_file = isset($_POST['ssl_key_file']) ? $_POST['ssl_key_file'] : ''; - $ssl_ca_file = isset($_POST['ssl_ca_file']) ? $_POST['ssl_ca_file'] : ''; - $ssl_cert_chainfile = isset($_POST['ssl_cert_chainfile']) ? $_POST['ssl_cert_chainfile'] : ''; $do_insert = isset($_POST['do_insert']) ? (($_POST['do_insert'] == 1) ? true : false) : false; - - if ($ssl_cert_file != '' && $ssl_key_file == '') { - standard_error('sslcertificateismissingprivatekey'); - } - - $do_verify = true; - - // no cert-file given -> forget everything - if ($ssl_cert_file == '') { - $ssl_key_file = ''; - $ssl_ca_file = ''; - $ssl_cert_chainfile = ''; - $do_verify = false; - } - - // verify certificate content - if ($do_verify) { - // array openssl_x509_parse ( mixed $x509cert [, bool $shortnames = true ] ) - // openssl_x509_parse() returns information about the supplied x509cert, including fields such as - // subject name, issuer name, purposes, valid from and valid to dates etc. - $cert_content = openssl_x509_parse($ssl_cert_file); - - if (is_array($cert_content) && isset($cert_content['subject']) && isset($cert_content['subject']['CN'])) { - // bool openssl_x509_check_private_key ( mixed $cert , mixed $key ) - // Checks whether the given key is the private key that corresponds to cert. - if (openssl_x509_check_private_key($ssl_cert_file, $ssl_key_file) === false) { - standard_error('sslcertificateinvalidcertkeypair'); - } - - // check optional stuff - if ($ssl_ca_file != '') { - $ca_content = openssl_x509_parse($ssl_ca_file); - if (!is_array($ca_content)) { - // invalid - standard_error('sslcertificateinvalidca'); - } - } - if ($ssl_cert_chainfile != '') { - $chain_content = openssl_x509_parse($ssl_cert_chainfile); - if (!is_array($chain_content)) { - // invalid - standard_error('sslcertificateinvalidchain'); - } - } + try { + if ($do_insert) { + Certificates::getLocal($userinfo, $_POST)->add(); } else { - standard_error('sslcertificateinvalidcert'); + Certificates::getLocal($userinfo, $_POST)->update(); } + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - - // Add/Update database entry - $qrystart = "UPDATE "; - $qrywhere = "WHERE "; - if ($do_insert) { - $qrystart = "INSERT INTO "; - $qrywhere = ", "; - } - $stmt = Database::prepare($qrystart." `".TABLE_PANEL_DOMAIN_SSL_SETTINGS."` SET - `ssl_cert_file` = :ssl_cert_file, - `ssl_key_file` = :ssl_key_file, - `ssl_ca_file` = :ssl_ca_file, - `ssl_cert_chainfile` = :ssl_cert_chainfile - ".$qrywhere." `domainid`= :domainid" - ); - $params = array( - "ssl_cert_file" => $ssl_cert_file, - "ssl_key_file" => $ssl_key_file, - "ssl_ca_file" => $ssl_ca_file, - "ssl_cert_chainfile" => $ssl_cert_chainfile, - "domainid" => $id - ); - Database::pexecute($stmt, $params); - - // insert task to re-generate webserver-configs (#1260) - inserttask('1'); - // back to domain overview redirectTo($filename, array('page' => 'domains', 's' => $s)); } @@ -535,8 +464,7 @@ if ($page == 'overview') { $stmt = Database::prepare("SELECT * FROM `".TABLE_PANEL_DOMAIN_SSL_SETTINGS."` WHERE `domainid`= :domainid" ); - Database::pexecute($stmt, array("domainid" => $id)); - $result = $stmt->fetch(PDO::FETCH_ASSOC); + $result = Database::pexecute_first($stmt, array("domainid" => $id)); $do_insert = false; // if no entry can be found, behave like we have empty values diff --git a/lib/classes/api/commands/class.Certificates.php b/lib/classes/api/commands/class.Certificates.php new file mode 100644 index 00000000..fb17a709 --- /dev/null +++ b/lib/classes/api/commands/class.Certificates.php @@ -0,0 +1,325 @@ + (2010-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package API + * @since 0.10.0 + * + */ +class Certificates extends ApiCommand implements ResourceEntity +{ + + /** + * add new ssl-certificate entry for given domain by either id or domainname + * + * @param int $id + * optional, the domain-id + * @param string $domainname + * optional, the domainname + * @param string $ssl_cert_file + * @param string $ssl_key_file + * @param string $ssl_ca_file + * optional + * @param string $ssl_cert_chainfile + * optional + * + * @access admin, customer + * @throws Exception + * @return array + */ + public function add() + { + $domainid = $this->getParam('domainid', true, 0); + $dn_optional = ($domainid <= 0 ? false : true); + $domainname = $this->getParam('domainname', $dn_optional, ''); + + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'domains')) { + throw new Exception("You cannot access this resource", 405); + } + + $domain = $this->apiCall('SubDomains.get', array( + 'id' => $domainid, + 'domainname' => $domainname + )); + // parameters + $ssl_cert_file = $this->getParam('ssl_cert_file'); + $ssl_key_file = $this->getParam('ssl_key_file'); + $ssl_ca_file = $this->getParam('ssl_ca_file', true, ''); + $ssl_cert_chainfile = $this->getParam('ssl_cert_chainfile', true, ''); + $this->addOrUpdateCertificate($domain['id'], $ssl_cert_file, $ssl_key_file, $ssl_ca_file, $ssl_cert_chainfile, true); + $idna_convert = new idna_convert_wrapper(); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] added ssl-certificate for '" . $domain['domain'] . "'"); + $result = $this->apiCall('Certificates.get', array( + 'id' => $domain['id'] + )); + return $this->response(200, "successfull", $result); + } + + /** + * return ssl-certificate entry for given domain by either id or domainname + * + * @param int $id + * optional, the domain-id + * @param string $domainname + * optional, the domainname + * + * @access admin, customer + * @throws Exception + * @return array + */ + public function get() + { + $id = $this->getParam('id', true, 0); + $dn_optional = ($id <= 0 ? false : true); + $domainname = $this->getParam('domainname', $dn_optional, ''); + + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'domains')) { + throw new Exception("You cannot access this resource", 405); + } + + $domain = $this->apiCall('SubDomains.get', array( + 'id' => $id, + 'domainname' => $domainname + )); + $domainid = $domain['id']; + + $stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` WHERE `domainid`= :domainid"); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] get ssl-certificate for '" . $domain['domain'] . "'"); + $result = Database::pexecute_first($stmt, array( + "domainid" => $domainid + )); + return $this->response(200, "successfull", $result); + } + + /** + * update ssl-certificate entry for given domain by either id or domainname + * + * @param int $id + * optional, the domain-id + * @param string $domainname + * optional, the domainname + * + * @access admin, customer + * @throws Exception + * @return array + */ + public function update() + { + $id = $this->getParam('id', true, 0); + $dn_optional = ($id <= 0 ? false : true); + $domainname = $this->getParam('domainname', $dn_optional, ''); + + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'domains')) { + throw new Exception("You cannot access this resource", 405); + } + + $domain = $this->apiCall('SubDomains.get', array( + 'id' => $id, + 'domainname' => $domainname + )); + + // parameters + $ssl_cert_file = $this->getParam('ssl_cert_file'); + $ssl_key_file = $this->getParam('ssl_key_file'); + $ssl_ca_file = $this->getParam('ssl_ca_file', true, ''); + $ssl_cert_chainfile = $this->getParam('ssl_cert_chainfile', true, ''); + $this->addOrUpdateCertificate($domain['id'], $ssl_cert_file, $ssl_key_file, $ssl_ca_file, $ssl_cert_chainfile, false); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] updated ssl-certificate for '" . $domain['domain'] . "'"); + $result = $this->apiCall('Certificates.get', array( + 'id' => $domain['id'] + )); + return $this->response(200, "successfull", $result); + } + + /** + * lists all certificate entries + * + * @access admin, customer + * @throws Exception + * @return array count|list + */ + public function listing() + { + // select all my (accessable) certificates + $certs_stmt_query = "SELECT s.*, d.domain, d.letsencrypt, c.customerid, c.loginname + FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` s + LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` d ON `d`.`id` = `s`.`domainid` + LEFT JOIN `" . TABLE_PANEL_CUSTOMERS . "` c ON `c`.`customerid` = `d`.`customerid` + WHERE "; + + $qry_params = array(); + + if ($this->isAdmin() && $this->getUserDetail('customers_see_all') == '0') { + // admin with only customer-specific permissions + $certs_stmt_query .= "d.adminid = :adminid "; + $qry_params['adminid'] = $this->getUserDetail('adminid'); + } elseif ($this->isAdmin() == false) { + // customer-area + $certs_stmt_query .= "d.customerid = :cid "; + $qry_params['cid'] = $this->getUserDetail('customerid'); + } else { + $certs_stmt_query .= "1 "; + } + $certs_stmt = Database::prepare($certs_stmt_query); + Database::pexecute($certs_stmt, $qry_params, true, true); + $result = array(); + while ($cert = $certs_stmt->fetch(PDO::FETCH_ASSOC)) { + // respect froxlor-hostname + if ($cert['domainid'] == 0) { + $cert['domain'] = Settings::Get('system.hostname'); + $cert['letsencrypt'] = Settings::Get('system.le_froxlor_enabled'); + $cert['loginname'] = 'froxlor.panel'; + } + $result[] = $cert; + } + return $this->response(200, "successfull", array( + 'count' => count($result), + 'list' => $result + )); + } + + /** + * delete certificates entry by id + * + * @param int $id + * + * @return array + */ + public function delete() + { + $id = $this->getParam('id'); + + $chk = ($this->isAdmin() && $this->getUserDetail('customers_see_all') == '1') ? true : false; + if ($this->isAdmin() == false) { + $chk_stmt = Database::prepare(" + SELECT d.domain FROM `" . TABLE_PANEL_DOMAINS . "` d + LEFT JOIN `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` s ON s.domainid = d.id + WHERE s.`id` = :id AND d.`customerid` = :cid + "); + $chk = Database::pexecute_first($chk_stmt, array( + 'id' => $id, + 'cid' => $this->getUserDetail('customerid') + )); + } elseif ($this->isAdmin() && $this->getUserDetail('customers_see_all') == '0') { + $chk_stmt = Database::prepare(" + SELECT d.domain FROM `" . TABLE_PANEL_DOMAINS . "` d + LEFT JOIN `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` s ON s.domainid = d.id + WHERE s.`id` = :id AND d.`adminid` = :aid + "); + $chk = Database::pexecute_first($chk_stmt, array( + 'id' => $id, + 'aid' => $this->getUserDetail('adminid') + )); + } + if ($chk !== false) { + // additional access check by trying to get the certificate + $result = $this->apiCall('Certificates.get', array( + 'domainname' => $chk['domain'] + )); + $del_stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` WHERE id = :id"); + Database::pexecute($del_stmt, array( + 'id' => $id + )); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] removed ssl-certificate for '" . $chk['domain'] . "'"); + return $this->response(200, "successfull", $result); + } + } + + /** + * insert or update certificates entry + * + * @param int $domainid + * @param string $ssl_cert_file + * @param string $ssl_key_file + * @param string $ssl_ca_file + * @param string $ssl_cert_chainfile + * @param boolean $do_insert + * optional default false + * + * @return boolean + */ + private function addOrUpdateCertificate($domainid = 0, $ssl_cert_file = '', $ssl_key_file = '', $ssl_ca_file = '', $ssl_cert_chainfile = '', $do_insert = false) + { + if ($ssl_cert_file != '' && $ssl_key_file == '') { + standard_error('sslcertificateismissingprivatekey', '', true); + } + + $do_verify = true; + // no cert-file given -> forget everything + if ($ssl_cert_file == '') { + $ssl_key_file = ''; + $ssl_ca_file = ''; + $ssl_cert_chainfile = ''; + $do_verify = false; + } + + // verify certificate content + if ($do_verify) { + // array openssl_x509_parse ( mixed $x509cert [, bool $shortnames = true ] ) + // openssl_x509_parse() returns information about the supplied x509cert, including fields such as + // subject name, issuer name, purposes, valid from and valid to dates etc. + $cert_content = openssl_x509_parse($ssl_cert_file); + + if (is_array($cert_content) && isset($cert_content['subject']) && isset($cert_content['subject']['CN'])) { + // bool openssl_x509_check_private_key ( mixed $cert , mixed $key ) + // Checks whether the given key is the private key that corresponds to cert. + if (openssl_x509_check_private_key($ssl_cert_file, $ssl_key_file) === false) { + standard_error('sslcertificateinvalidcertkeypair', '', true); + } + + // check optional stuff + if ($ssl_ca_file != '') { + $ca_content = openssl_x509_parse($ssl_ca_file); + if (! is_array($ca_content)) { + // invalid + standard_error('sslcertificateinvalidca', '', true); + } + } + if ($ssl_cert_chainfile != '') { + $chain_content = openssl_x509_parse($ssl_cert_chainfile); + if (! is_array($chain_content)) { + // invalid + standard_error('sslcertificateinvalidchain', '', true); + } + } + } else { + standard_error('sslcertificateinvalidcert', '', true); + } + } + + // Add/Update database entry + $qrystart = "UPDATE "; + $qrywhere = "WHERE "; + if ($do_insert) { + $qrystart = "INSERT INTO "; + $qrywhere = ", "; + } + $stmt = Database::prepare($qrystart . " `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` SET + `ssl_cert_file` = :ssl_cert_file, + `ssl_key_file` = :ssl_key_file, + `ssl_ca_file` = :ssl_ca_file, + `ssl_cert_chainfile` = :ssl_cert_chainfile + " . $qrywhere . " `domainid`= :domainid + "); + $params = array( + "ssl_cert_file" => $ssl_cert_file, + "ssl_key_file" => $ssl_key_file, + "ssl_ca_file" => $ssl_ca_file, + "ssl_cert_chainfile" => $ssl_cert_chainfile, + "domainid" => $domainid + ); + Database::pexecute($stmt, $params, true, true); + // insert task to re-generate webserver-configs (#1260) + inserttask('1'); + return true; + } +} diff --git a/lib/classes/api/commands/class.SubDomains.php b/lib/classes/api/commands/class.SubDomains.php index 6ef6592a..1e2e1cbf 100644 --- a/lib/classes/api/commands/class.SubDomains.php +++ b/lib/classes/api/commands/class.SubDomains.php @@ -405,6 +405,18 @@ class SubDomains extends ApiCommand implements ResourceEntity throw new Exception("Subdomain with " . $key . " could not be found", 404); } + /** + * update subdomain entry by either id or domainname + * + * @param int $id + * optional, the domain-id + * @param string $domainname + * optional, the domainname + * + * @access admin, customer + * @throws Exception + * @return array + */ public function update() { $id = $this->getParam('id', true, 0); @@ -414,7 +426,7 @@ class SubDomains extends ApiCommand implements ResourceEntity if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'domains')) { throw new Exception("You cannot access this resource", 405); } - + $result = $this->apiCall('SubDomains.get', array( 'id' => $id, 'domainname' => $domainname @@ -628,6 +640,13 @@ class SubDomains extends ApiCommand implements ResourceEntity return $this->response(200, "successfull", $result); } + /** + * lists all subdomain entries + * + * @access admin, customer + * @throws Exception + * @return array count|list + */ public function listing() { if ($this->isAdmin()) { diff --git a/ssl_certificates.php b/ssl_certificates.php index dd0bba10..eb08f534 100644 --- a/ssl_certificates.php +++ b/ssl_certificates.php @@ -22,40 +22,19 @@ if (! defined('AREA')) { // This file is being included in admin_domains and customer_domains // and therefore does not need to require lib/init.php -$del_stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` WHERE id = :id"); $success_message = ""; // do the delete and then just show a success-message and the certificates list again if ($action == 'delete') { $id = isset($_GET['id']) ? (int) $_GET['id'] : 0; if ($id > 0) { - $chk = (AREA == 'admin' && $userinfo['customers_see_all'] == '1') ? true : false; - if (AREA == 'customer') { - $chk_stmt = Database::prepare(" - SELECT d.domain FROM `" . TABLE_PANEL_DOMAINS . "` d - LEFT JOIN `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` s ON s.domainid = d.id - WHERE s.`id` = :id AND d.`customerid` = :cid - "); - $chk = Database::pexecute_first($chk_stmt, array( - 'id' => $id, - 'cid' => $userinfo['customerid'] - )); - } elseif (AREA == 'admin' && $userinfo['customers_see_all'] == '0') { - $chk_stmt = Database::prepare(" - SELECT d.domain FROM `" . TABLE_PANEL_DOMAINS . "` d - LEFT JOIN `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` s ON s.domainid = d.id - WHERE s.`id` = :id AND d.`adminid` = :aid - "); - $chk = Database::pexecute_first($chk_stmt, array( - 'id' => $id, - 'aid' => $userinfo['adminid'] - )); - } - if ($chk !== false) { - Database::pexecute($del_stmt, array( + try { + $json_result = Certificates::getLocal($userinfo, array( 'id' => $id - )); + ))->delete(); $success_message = sprintf($lng['domains']['ssl_certificate_removed'], $id); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } } } From 864331d3713c3ee5fccf0a529b87b80cd29e72cb Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 5 Mar 2018 23:35:00 +0100 Subject: [PATCH 122/746] code reduction; added unit-tests for Certificates-Command; minor fixes here and there Signed-off-by: Michael Kaufmann (d00p) --- install/lib/class.FroxlorInstall.php | 2 +- lib/classes/api/abstract.ApiCommand.php | 43 ++++ .../api/commands/class.Certificates.php | 19 +- lib/classes/api/commands/class.Ftps.php | 33 +-- lib/classes/api/commands/class.Mysqls.php | 31 +-- lib/classes/api/commands/class.SubDomains.php | 2 + phpunit.xml | 1 + tests/Certificates/CertificatesTest.php | 194 ++++++++++++++++++ tests/Domains/DomainsTest.php | 6 +- tests/IpsAndPorts/IpsAndPortsTest.php | 20 +- tests/bootstrap.php | 14 ++ 11 files changed, 285 insertions(+), 80 deletions(-) create mode 100644 tests/Certificates/CertificatesTest.php diff --git a/install/lib/class.FroxlorInstall.php b/install/lib/class.FroxlorInstall.php index 4cc136a5..86e50f78 100644 --- a/install/lib/class.FroxlorInstall.php +++ b/install/lib/class.FroxlorInstall.php @@ -995,7 +995,7 @@ class FroxlorInstall $content .= $this->_status_message('green', $this->_lng['requirements']['installed']); } - // check for bstring-extension + // check for mbstring-extension $content .= $this->_status_message('begin', $this->_lng['requirements']['phpmbstring']); if (! extension_loaded('mbstring')) { diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index 156f84a5..e3677f9a 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -414,6 +414,49 @@ abstract class ApiCommand return $json_response; } + /** + * returns an array of customers the current user can access + * + * @param string $customer_hide_option optional, when called as customer, some options might be hidden due to the panel.customer_hide_options ettings + * + * @throws Exception + * @return array + */ + protected function getAllowedCustomerIds($customer_hide_option = '') + { + $customer_ids = array(); + if ($this->isAdmin()) { + // if we're an admin, list all ftp-users of all the admins customers + // or optionally for one specific customer identified by id or loginname + $customerid = $this->getParam('customerid', true, 0); + $loginname = $this->getParam('loginname', true, ''); + + if (! empty($customerid) || ! empty($loginname)) { + $_result = $this->apiCall('Customers.get', array( + 'id' => $customerid, + 'loginname' => $loginname + )); + $custom_list_result = array( + $_result + ); + } else { + $_custom_list_result = $this->apiCall('Customers.listing'); + $custom_list_result = $_custom_list_result['list']; + } + foreach ($custom_list_result as $customer) { + $customer_ids[] = $customer['customerid']; + } + } else { + if (!empty($customer_hide_option) && Settings::IsInList('panel.customer_hide_options', $customer_hide_option)) { + throw new Exception("You cannot access this resource", 405); + } + $customer_ids = array( + $this->getUserDetail('customerid') + ); + } + return $customer_ids; + } + /** * increase/decrease a resource field for customers/admins * diff --git a/lib/classes/api/commands/class.Certificates.php b/lib/classes/api/commands/class.Certificates.php index fb17a709..c49869ec 100644 --- a/lib/classes/api/commands/class.Certificates.php +++ b/lib/classes/api/commands/class.Certificates.php @@ -50,18 +50,27 @@ class Certificates extends ApiCommand implements ResourceEntity 'id' => $domainid, 'domainname' => $domainname )); + $domainid = $domain['id']; + // parameters $ssl_cert_file = $this->getParam('ssl_cert_file'); $ssl_key_file = $this->getParam('ssl_key_file'); $ssl_ca_file = $this->getParam('ssl_ca_file', true, ''); $ssl_cert_chainfile = $this->getParam('ssl_cert_chainfile', true, ''); - $this->addOrUpdateCertificate($domain['id'], $ssl_cert_file, $ssl_key_file, $ssl_ca_file, $ssl_cert_chainfile, true); - $idna_convert = new idna_convert_wrapper(); - $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] added ssl-certificate for '" . $domain['domain'] . "'"); + + // validate whether the domain does not already have an entry $result = $this->apiCall('Certificates.get', array( - 'id' => $domain['id'] + 'id' => $domainid )); - return $this->response(200, "successfull", $result); + if (empty($result)) { + $this->addOrUpdateCertificate($domain['id'], $ssl_cert_file, $ssl_key_file, $ssl_ca_file, $ssl_cert_chainfile, true); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] added ssl-certificate for '" . $domain['domain'] . "'"); + $result = $this->apiCall('Certificates.get', array( + 'id' => $domain['id'] + )); + return $this->response(200, "successfull", $result); + } + throw new Exception("Domain '" . $domain['domain'] . "' already has a certificate. Did you mean to call update?", 406); } /** diff --git a/lib/classes/api/commands/class.Ftps.php b/lib/classes/api/commands/class.Ftps.php index 6411e3f2..6f3223be 100644 --- a/lib/classes/api/commands/class.Ftps.php +++ b/lib/classes/api/commands/class.Ftps.php @@ -448,43 +448,14 @@ class Ftps extends ApiCommand implements ResourceEntity */ public function listing() { - if ($this->isAdmin()) { - // if we're an admin, list all ftp-users of all the admins customers - // or optionally for one specific customer identified by id or loginname - $customerid = $this->getParam('customerid', true, 0); - $loginname = $this->getParam('loginname', true, ''); - - if (! empty($customerid) || ! empty($loginname)) { - $_result = $this->apiCall('Customers.get', array( - 'id' => $customerid, - 'loginname' => $loginname - )); - $custom_list_result = array( - $_result - ); - } else { - $_custom_list_result = $this->apiCall('Customers.listing'); - $custom_list_result = $_custom_list_result['list']; - } - $customer_ids = array(); - foreach ($custom_list_result as $customer) { - $customer_ids[] = $customer['customerid']; - } - } else { - if (Settings::IsInList('panel.customer_hide_options', 'ftp')) { - throw new Exception("You cannot access this resource", 405); - } - $customer_ids = array( - $this->getUserDetail('customerid') - ); - } + $customer_ids = $this->getAllowedCustomerIds('ftp'); $result = array(); $params['customerid'] = implode(", ", $customer_ids); $result_stmt = Database::prepare(" SELECT * FROM `" . TABLE_FTP_USERS . "` WHERE `customerid` IN (:customerid) "); - Database::pexecute($result_stmt, $params); + Database::pexecute($result_stmt, $params, true, true); while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { $result[] = $row; } diff --git a/lib/classes/api/commands/class.Mysqls.php b/lib/classes/api/commands/class.Mysqls.php index 55929a80..dfb1cd55 100644 --- a/lib/classes/api/commands/class.Mysqls.php +++ b/lib/classes/api/commands/class.Mysqls.php @@ -425,36 +425,7 @@ class Mysqls extends ApiCommand implements ResourceEntity { $result = array(); $dbserver = $this->getParam('mysql_server', true, - 1); - if ($this->isAdmin()) { - // if we're an admin, list all databases of all the admins customers - // or optionally for one specific customer identified by id or loginname - $customerid = $this->getParam('customerid', true, 0); - $loginname = $this->getParam('loginname', true, ''); - - if (! empty($customer_id) || ! empty($loginname)) { - $customer = $this->apiCall('Customers.get', array( - 'id' => $customerid, - 'loginname' => $loginname - )); - $custom_list_result = array( - $customer - ); - } else { - $_custom_list_result = $this->apiCall('Customers.listing'); - $custom_list_result = $_custom_list_result['list']; - } - $customer_ids = array(); - foreach ($custom_list_result as $customer) { - $customer_ids[] = $customer['customerid']; - } - } else { - if (Settings::IsInList('panel.customer_hide_options', 'mysql')) { - throw new Exception("You cannot access this resource", 405); - } - $customer_ids = array( - $this->getUserDetail('customerid') - ); - } + $customer_ids = $this->getAllowedCustomerIds('mysql'); $result_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_DATABASES . "` WHERE `customerid`= :customerid AND `dbserver` = :dbserver diff --git a/lib/classes/api/commands/class.SubDomains.php b/lib/classes/api/commands/class.SubDomains.php index 1e2e1cbf..ba257882 100644 --- a/lib/classes/api/commands/class.SubDomains.php +++ b/lib/classes/api/commands/class.SubDomains.php @@ -247,6 +247,7 @@ class SubDomains extends ApiCommand implements ResourceEntity $stmt = Database::prepare(" INSERT INTO `" . TABLE_PANEL_DOMAINS . "` SET `customerid` = :customerid, + `adminid` = :adminid, `domain` = :domain, `documentroot` = :documentroot, `aliasdomain` = :aliasdomain, @@ -268,6 +269,7 @@ class SubDomains extends ApiCommand implements ResourceEntity "); $params = array( "customerid" => $customer['customerid'], + "adminid" => $customer['adminid'], "domain" => $completedomain, "documentroot" => $path, "aliasdomain" => $aliasdomain != 0 ? $aliasdomain : null, diff --git a/phpunit.xml b/phpunit.xml index 391c3a47..efedaf51 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -14,6 +14,7 @@ tests/IpsAndPorts tests/Domains tests/SubDomains + tests/Certificates tests/Ftps diff --git a/tests/Certificates/CertificatesTest.php b/tests/Certificates/CertificatesTest.php new file mode 100644 index 00000000..85a307a4 --- /dev/null +++ b/tests/Certificates/CertificatesTest.php @@ -0,0 +1,194 @@ +generateKey(); + $json_result = Certificates::getLocal($admin_userdata, array( + 'domainname' => 'test2.local', + 'ssl_cert_file' => $certdata['cert'], + 'ssl_key_file' => $certdata['key'] + ))->add(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(3, $result['domainid']); + } + + public function testResellerCertificatesAddAgain() + { + global $admin_userdata; + // get reseller + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller' + ))->get(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + + $certdata = $this->generateKey(); + $this->expectExceptionCode(406); + $this->expectExceptionMessage("Domain 'test2.local' already has a certificate. Did you mean to call update?"); + $json_result = Certificates::getLocal($reseller_userdata, array( + 'domainname' => 'test2.local', + 'ssl_cert_file' => $certdata['cert'], + 'ssl_key_file' => $certdata['key'] + ))->add(); + } + + public function testCustomerCertificatesAdd() + { + global $admin_userdata; + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $certdata = $this->generateKey(); + $json_result = Certificates::getLocal($customer_userdata, array( + 'domainname' => 'mysub2.test2.local', + 'ssl_cert_file' => $certdata['cert'], + 'ssl_key_file' => $certdata['key'] + ))->add(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(5, $result['domainid']); + } + + public function testAdminCertificatesList() + { + global $admin_userdata; + + $json_result = Certificates::getLocal($admin_userdata)->listing(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(2, $result['count']); + } + + public function testResellerCertificatesList() + { + global $admin_userdata; + // get reseller + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller' + ))->get(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + + $json_result = Certificates::getLocal($reseller_userdata)->listing(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(2, $result['count']); + } + + public function testCustomerCertificatesList() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $json_result = Certificates::getLocal($customer_userdata)->listing(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(2, $result['count']); + } + + public function testAdminCertificatesUpdate() + { + global $admin_userdata; + + $certdata = $this->generateKey(); + $json_result = Certificates::getLocal($admin_userdata, array( + 'domainname' => 'test2.local', + 'ssl_cert_file' => $certdata['cert'], + 'ssl_key_file' => $certdata['key'] + ))->update(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(3, $result['domainid']); + $this->assertEquals(str_replace("\n", "", $certdata['cert']), str_replace("\n", "", $result['ssl_cert_file'])); + } + + public function testCustomerCertificatesUpdate() + { + global $admin_userdata; + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $certdata = $this->generateKey(); + $json_result = Certificates::getLocal($customer_userdata, array( + 'domainname' => 'mysub2.test2.local', + 'ssl_cert_file' => $certdata['cert'], + 'ssl_key_file' => $certdata['key'] + ))->update(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(5, $result['domainid']); + $this->assertEquals(str_replace("\n", "", $certdata['cert']), str_replace("\n", "", $result['ssl_cert_file'])); + } + + /** + * @depends testAdminCertificatesUpdate + */ + public function testCustomerCertificatesDelete() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $json_result = Certificates::getLocal($customer_userdata, array( + 'id' => 1 + ))->delete(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(3, $result['domainid']); + } + + private function generateKey() + { + $dn = array( + "countryName" => "DE", + "stateOrProvinceName" => "Hessen", + "localityName" => "Frankfurt", + "organizationName" => "Froxlor", + "organizationalUnitName" => "Testing", + "commonName" => "test2.local", + "emailAddress" => "team@froxlor.org" + ); + + // generate key pair + $privkey = openssl_pkey_new(array( + "private_key_bits" => 2048, + "private_key_type" => OPENSSL_KEYTYPE_RSA + )); + + // generate csr + $csr = openssl_csr_new($dn, $privkey, array( + 'digest_alg' => 'sha256' + )); + + // generate self-signed certificate + $sscert = openssl_csr_sign($csr, null, $privkey, 365, array( + 'digest_alg' => 'sha256' + )); + + // export + openssl_csr_export($csr, $csrout); + openssl_x509_export($sscert, $certout); + openssl_pkey_export($privkey, $pkeyout, null); + + return array( + 'cert' => $certout, + 'key' => $pkeyout + ); + } +} diff --git a/tests/Domains/DomainsTest.php b/tests/Domains/DomainsTest.php index ed13d5ff..964b29e4 100644 --- a/tests/Domains/DomainsTest.php +++ b/tests/Domains/DomainsTest.php @@ -77,10 +77,10 @@ class DomainsTest extends TestCase public function testResellerDomainsAddWithCanEditPhpSettingsAllowedIp() { global $admin_userdata; - // first, allow reseller access to ip #3 + // first, allow reseller access to ip #4 Admins::getLocal($admin_userdata, array( 'loginname' => 'reseller', - 'ipaddress' => 3 + 'ipaddress' => 4 ))->update(); // get reseller $json_result = Admins::getLocal($admin_userdata, array( @@ -91,7 +91,7 @@ class DomainsTest extends TestCase $data = [ 'domain' => 'test2.local', 'customerid' => 1, - 'ipandport' => 3, + 'ipandport' => 4, 'isemaildomain' => 1, 'subcanemaildomain' => 2 ]; diff --git a/tests/IpsAndPorts/IpsAndPortsTest.php b/tests/IpsAndPorts/IpsAndPortsTest.php index 9b3ec579..94d32f27 100644 --- a/tests/IpsAndPorts/IpsAndPortsTest.php +++ b/tests/IpsAndPorts/IpsAndPortsTest.php @@ -13,7 +13,7 @@ class IpsAndPortsTest extends TestCase global $admin_userdata; $json_result = IpsAndPorts::getLocal($admin_userdata)->listing(); $result = json_decode($json_result, true)['data']; - $this->assertEquals(1, $result['count']); + $this->assertEquals(2, $result['count']); $this->assertEquals('82.149.225.46', $result['list'][0]['ip']); } @@ -40,7 +40,7 @@ class IpsAndPortsTest extends TestCase ]; $json_result = IpsAndPorts::getLocal($admin_userdata, $data)->add(); $result = json_decode($json_result, true)['data']; - $this->assertEquals(2, $result['id']); + $this->assertEquals(3, $result['id']); $this->assertEquals(80, $result['port']); } @@ -66,7 +66,7 @@ class IpsAndPortsTest extends TestCase ]; $json_result = IpsAndPorts::getLocal($admin_userdata, $data)->add(); $result = json_decode($json_result, true)['data']; - $this->assertEquals(3, $result['id']); + $this->assertEquals(4, $result['id']); $this->assertEquals('/var/www/html/', $result['docroot']); } @@ -84,10 +84,10 @@ class IpsAndPortsTest extends TestCase public function testResellerIpsAndPortsList() { global $admin_userdata; - // update reseller to allow ip access to ip id #2 + // update reseller to allow ip access to ip id #3 $json_result = Admins::getLocal($admin_userdata, array( 'loginname' => 'reseller', - 'ipaddress' => array(2) + 'ipaddress' => array(3) ))->update(); $reseller_userdata = json_decode($json_result, true)['data']; $reseller_userdata['adminsession'] = 1; @@ -109,7 +109,7 @@ class IpsAndPortsTest extends TestCase ))->get(); $reseller_userdata = json_decode($json_result, true)['data']; $reseller_userdata['adminsession'] = 1; - $json_result = IpsAndPorts::getLocal($reseller_userdata, array('id' => 2))->get(); + $json_result = IpsAndPorts::getLocal($reseller_userdata, array('id' => 3))->get(); $result = json_decode($json_result, true)['data']; $this->assertEquals('82.149.225.47', $result['ip']); } @@ -120,7 +120,7 @@ class IpsAndPortsTest extends TestCase public function testResellerIpsAndPortsGetRestrictedNotOwned() { global $admin_userdata; - // update reseller to allow ip access to ip id #2 + // get reseller $json_result = Admins::getLocal($admin_userdata, array( 'loginname' => 'reseller' ))->get(); @@ -134,7 +134,7 @@ class IpsAndPortsTest extends TestCase public function testResellerIpsAndPortsAdd() { global $admin_userdata; - // update reseller to allow ip access to ip id #2 + // get reseller $json_result = Admins::getLocal($admin_userdata, array( 'loginname' => 'reseller' ))->get(); @@ -230,7 +230,7 @@ class IpsAndPortsTest extends TestCase $reseller_userdata = json_decode($json_result, true)['data']; $reseller_userdata['adminsession'] = 1; $data = [ - 'id' => 2, + 'id' => 3, 'ip' => '82.149.225.46' ]; $this->expectExceptionMessage("This IP/Port combination already exists."); @@ -251,7 +251,7 @@ class IpsAndPortsTest extends TestCase { global $admin_userdata; $data = [ - 'id' => 2 + 'id' => 3 ]; $json_result = IpsAndPorts::getLocal($admin_userdata, $data)->delete(); $result = json_decode($json_result, true)['data']; diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 31001195..fcf14947 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -103,6 +103,20 @@ Database::query("INSERT INTO `" . TABLE_PANEL_IPSANDPORTS . "` SET $defaultip = Database::lastInsertId(); Settings::Set('system.defaultip', $defaultip, true); +// add ssl ip (system default) +Database::query("INSERT INTO `" . TABLE_PANEL_IPSANDPORTS . "` SET + `ip` = '82.149.225.56', + `port` = '443', + `listen_statement` = '0', + `namevirtualhost_statement` = '0', + `vhostcontainer` = '1', + `vhostcontainer_servername_statement` = '1', + `specialsettings` = '', + `ssl` = '1' +"); +$defaultip = Database::lastInsertId(); +Settings::Set('system.defaultsslip', $defaultip, true); + // get userdata of admin 'admin' $sel_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_ADMINS . "` WHERE `adminid` = '1'"); $admin_userdata = Database::pexecute_first($sel_stmt); From 0c43c5d2b5fa77c51e533813dd0862968e6504d3 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 6 Mar 2018 10:52:57 +0100 Subject: [PATCH 123/746] backport config-services --import-settings parameter from 0.10.0 Signed-off-by: Michael Kaufmann (d00p) --- install/scripts/config-services.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/install/scripts/config-services.php b/install/scripts/config-services.php index 69d1b654..c0c84aec 100755 --- a/install/scripts/config-services.php +++ b/install/scripts/config-services.php @@ -43,6 +43,7 @@ class ConfigServicesCmd extends CmdLineHandler public static $params = array( 'create', 'apply', + 'import-settings', 'daemon', 'list-daemons', 'froxlor-dir', @@ -68,6 +69,9 @@ class ConfigServicesCmd extends CmdLineHandler self::println("--daemon\t\tWhen running --apply you can specify a daemon. This will be the only service that gets configured"); self::println("\t\t\tExample: --apply=/path/to/my-config.json --daemon=apache24"); self::println(""); + self::println("--import-settings\tImport settings from another froxlor installation. This should be done prior to running --apply or alternatively in the same command together."); + self::println("\t\t\tExample: --import-settings=/path/to/Froxlor_settings-[version]-[dbversion]-[date].json"); + self::println(""); self::println("--froxlor-dir\t\tpath to froxlor installation"); self::println("\t\t\tExample: --froxlor-dir=/var/www/froxlor/"); self::println(""); @@ -115,10 +119,15 @@ class Action require FROXLOR_INSTALL_DIR . '/lib/tables.inc.php'; require FROXLOR_INSTALL_DIR . '/lib/functions.php'; require FROXLOR_INSTALL_DIR . '/lib/classes/settings/class.Settings.php'; + require FROXLOR_INSTALL_DIR . '/lib/classes/settings/class.SImExporter.php'; require FROXLOR_INSTALL_DIR . '/lib/classes/config/class.ConfigParser.php'; require FROXLOR_INSTALL_DIR . '/lib/classes/config/class.ConfigService.php'; require FROXLOR_INSTALL_DIR . '/lib/classes/config/class.ConfigDaemon.php'; + if (array_key_exists("import-settings", $this->_args)) { + $this->_importSettings(); + } + if (array_key_exists("create", $this->_args)) { $this->_createConfig(); } elseif (array_key_exists("apply", $this->_args)) { @@ -128,6 +137,20 @@ class Action } } + private function _importSettings() + { + if (! is_file($this->_args["import-settings"])) { + throw new Exception("Given settings file is not a file"); + } elseif (! file_exists($this->_args["import-settings"])) { + throw new Exception("Given settings file cannot be found ('" . $this->_args["import-settings"] . "')"); + } elseif (! is_readable($this->_args["import-settings"])) { + throw new Exception("Given settings file cannot be read ('" . $this->_args["import-settings"] . "')"); + } + $imp_content = file_get_contents($this->_args["import-settings"]); + SImExporter::import($imp_content); + CmdLineHandler::printsucc("Successfully imported settings from '" . $this->_args["import-settings"] . "'"); + } + private function _createConfig() { $_daemons_config = array( From a621fd3b09a5d5703d7594761df18c573dac8a78 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 6 Mar 2018 11:05:26 +0100 Subject: [PATCH 124/746] run cronjob at the end of config-services script (when using --apply), thx v3ng Signed-off-by: Michael Kaufmann (d00p) --- install/scripts/config-services.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/install/scripts/config-services.php b/install/scripts/config-services.php index c0c84aec..3b5d1821 100755 --- a/install/scripts/config-services.php +++ b/install/scripts/config-services.php @@ -357,6 +357,9 @@ class Action } } } + // run cronjob at the end to ensure configs are all up to date + exec('php ' . FROXLOR_INSTALL_DIR . '/scripts/froxlor_master_cronjob.php --force'); + // and done CmdLineHandler::printsucc("All services have been configured"); } else { CmdLineHandler::printerr("Unable to decode given JSON file"); From 893fd0774c2eac148a3adf709103c2f0e8506957 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 6 Mar 2018 12:26:41 +0100 Subject: [PATCH 125/746] implement deleting of api-keys via webinterface Signed-off-by: Michael Kaufmann (d00p) --- api_keys.php | 38 ++++++++++++++++++++++++++++++++++++++ lng/english.lng.php | 1 + lng/german.lng.php | 1 + 3 files changed, 40 insertions(+) diff --git a/api_keys.php b/api_keys.php index d04891df..c1d75215 100644 --- a/api_keys.php +++ b/api_keys.php @@ -23,6 +23,44 @@ if (! defined('AREA')) { // This file is being included in admin_index and customer_index // and therefore does not need to require lib/init.php +$del_stmt = Database::prepare("DELETE FROM `" . TABLE_API_KEYS . "` WHERE id = :id"); +$success_message = ""; + +// do the delete and then just show a success-message and the certificates list again +if ($action == 'delete') { + $id = isset($_GET['id']) ? (int) $_GET['id'] : 0; + if ($id > 0) { + $chk = (AREA == 'admin' && $userinfo['customers_see_all'] == '1') ? true : false; + if (AREA == 'customer') { + $chk_stmt = Database::prepare(" + SELECT c.customerid FROM `" . TABLE_PANEL_CUSTOMERS . "` c + LEFT JOIN `" . TABLE_API_KEYS . "` ak ON ak.customerid = c.customerid + WHERE ak.`id` = :id AND c.`customerid` = :cid + "); + $chk = Database::pexecute_first($chk_stmt, array( + 'id' => $id, + 'cid' => $userinfo['customerid'] + )); + } elseif (AREA == 'admin' && $userinfo['customers_see_all'] == '0') { + $chk_stmt = Database::prepare(" + SELECT a.adminid FROM `" . TABLE_PANEL_ADMINS . "` a + LEFT JOIN `" . TABLE_API_KEYS . "` ak ON ak.adminid = a.adminid + WHERE ak.`id` = :id AND a.`adminid` = :aid + "); + $chk = Database::pexecute_first($chk_stmt, array( + 'id' => $id, + 'aid' => $userinfo['adminid'] + )); + } + if ($chk !== false) { + Database::pexecute($del_stmt, array( + 'id' => $id + )); + $success_message = sprintf($lng['apikeys']['apikey_removed'], $id); + } + } +} + $log->logAction(USR_ACTION, LOG_NOTICE, "viewed api::api_keys"); // select all my (accessable) certificates diff --git a/lng/english.lng.php b/lng/english.lng.php index e0c4e59f..507799e7 100644 --- a/lng/english.lng.php +++ b/lng/english.lng.php @@ -2126,3 +2126,4 @@ $lng['menue']['main']['apihelp'] = 'API help'; $lng['menue']['main']['apikeys'] = 'API keys'; $lng['apikeys']['no_api_keys'] = 'No API keys found'; $lng['apikeys']['key_add'] = 'Add new key'; +$lng['apikeys']['apikey_removed'] = 'The api key with the id #%s has been removed successfully'; diff --git a/lng/german.lng.php b/lng/german.lng.php index b6507e87..07b641cb 100644 --- a/lng/german.lng.php +++ b/lng/german.lng.php @@ -1776,3 +1776,4 @@ $lng['menue']['main']['apihelp'] = 'API Hilfe'; $lng['menue']['main']['apikeys'] = 'API Keys'; $lng['apikeys']['no_api_keys'] = 'Keine API Keys gefunden'; $lng['apikeys']['key_add'] = 'API Key hinzufügen'; +$lng['apikeys']['apikey_removed'] = 'Der API Key mit der ID #%s wurde erfolgreich gelöscht.'; From a83031504f674a925ba82f545f96c6003ca253c2 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 6 Mar 2018 12:43:24 +0100 Subject: [PATCH 126/746] implement generating of api-key for customer Signed-off-by: Michael Kaufmann (d00p) --- api_keys.php | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/api_keys.php b/api_keys.php index c1d75215..c60480b1 100644 --- a/api_keys.php +++ b/api_keys.php @@ -25,10 +25,10 @@ if (! defined('AREA')) { $del_stmt = Database::prepare("DELETE FROM `" . TABLE_API_KEYS . "` WHERE id = :id"); $success_message = ""; +$id = isset($_GET['id']) ? (int) $_GET['id'] : 0; // do the delete and then just show a success-message and the certificates list again if ($action == 'delete') { - $id = isset($_GET['id']) ? (int) $_GET['id'] : 0; if ($id > 0) { $chk = (AREA == 'admin' && $userinfo['customers_see_all'] == '1') ? true : false; if (AREA == 'customer') { @@ -59,6 +59,26 @@ if ($action == 'delete') { $success_message = sprintf($lng['apikeys']['apikey_removed'], $id); } } +} elseif ($action == 'add') { + $ins_stmt = Database::prepare(" + INSERT INTO `" . TABLE_API_KEYS . "` SET + `apikey` = :key, `secret` = :secret, `adminid` = :aid, `customerid` = :cid, `valid_until` = '-1', `allowed_from` = '' + "); + // customer generates for himself, admins will see a customer-select-box + if (AREA == 'customer') { + $key = hash('sha256', openssl_random_pseudo_bytes(64 * 64)); + $secret = hash('sha512', openssl_random_pseudo_bytes(64 * 64 * 4)); + Database::pexecute($ins_stmt, array( + 'key' => $key, + 'secret' => $secret, + 'aid' => $userinfo['adminid'], + 'cid' => $userinfo['customerid'] + )); + redirectTo($filename, array( + 'page' => $page, + 's' => $s + )); + } } $log->logAction(USR_ACTION, LOG_NOTICE, "viewed api::api_keys"); @@ -146,6 +166,10 @@ if (count($all_keys) == 0) { // escape stuff $row = htmlentities_array($key); + // shorten keys + $row['apikey'] = substr($row['apikey'], 0, 20) . '...'; + $row['secret'] = substr($row['secret'], 0, 20) . '...'; + // check whether the api key is not valid anymore $isValid = true; if ($row['valid_until'] >= 0) { From 231159a6c697169bd7740a7d364cd1eb105cfbd0 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 6 Mar 2018 14:02:27 +0100 Subject: [PATCH 127/746] added first methods of Emails ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- customer_email.php | 75 +------ lib/classes/api/commands/class.Emails.php | 230 ++++++++++++++++++++++ lib/classes/api/commands/class.Ftps.php | 6 +- phpunit.xml | 1 + tests/Emails/EmailsTest.php | 44 +++++ 5 files changed, 285 insertions(+), 71 deletions(-) create mode 100644 lib/classes/api/commands/class.Emails.php create mode 100644 tests/Emails/EmailsTest.php diff --git a/customer_email.php b/customer_email.php index 02517fc1..efed543a 100644 --- a/customer_email.php +++ b/customer_email.php @@ -205,76 +205,13 @@ if ($page == 'overview') { } elseif ($action == 'add') { if ($userinfo['emails_used'] < $userinfo['emails'] || $userinfo['emails'] == '-1') { if (isset($_POST['send']) && $_POST['send'] == 'send') { - $email_part = $_POST['email_part']; - // domain does not need idna encoding as the value of the select-box is already Punycode - $domain = validate($_POST['domain'], 'domain'); - $stmt = Database::prepare("SELECT `id`, `domain`, `customerid` FROM `" . TABLE_PANEL_DOMAINS . "` - WHERE `domain`= :domain - AND `customerid`= :customerid - AND `isemaildomain`='1' " - ); - $domain_check = Database::pexecute_first($stmt, array("domain" => $domain, "customerid" => $userinfo['customerid'])); - - if (isset($_POST['iscatchall']) && $_POST['iscatchall'] == '1') { - $iscatchall = '1'; - $email = '@' . $domain; - } else { - $iscatchall = '0'; - $email = $email_part . '@' . $domain; - } - - $email_full = $email_part . '@' . $domain; - - if (!validateEmail($email_full)) { - standard_error('emailiswrong', $email_full); - } - - $stmt = Database::prepare("SELECT `id`, `email`, `email_full`, `iscatchall`, `destination`, `customerid` FROM `" . TABLE_MAIL_VIRTUAL . "` - WHERE (`email` = :email - OR `email_full` = :emailfull ) - AND `customerid`= :cid" - ); - $params = array( - "email" => $email, - "emailfull" => $email_full, - "cid" => $userinfo['customerid'] - ); - $email_check = Database::pexecute_first($stmt, $params); - - if ($email == '' || $email_full == '' || $email_part == '') { - standard_error(array('stringisempty', 'emailadd')); - } elseif ($domain == '') { - standard_error('domaincantbeempty'); - } elseif ($domain_check['domain'] != $domain) { - standard_error('maindomainnonexist', $domain); - } elseif (strtolower($email_check['email_full']) == strtolower($email_full)) { - standard_error('emailexistalready', $email_full); - } elseif ($email_check['email'] == $email) { - standard_error('youhavealreadyacatchallforthisdomain'); - } else { - $stmt = Database::prepare("INSERT INTO `" . TABLE_MAIL_VIRTUAL . "` - (`customerid`, `email`, `email_full`, `iscatchall`, `domainid`) - VALUES (:cid, :email, :email_full, :iscatchall, :domainid)" - ); - $params = array( - "cid" => $userinfo['customerid'], - "email" => $email, - "email_full" => $email_full, - "iscatchall" => $iscatchall, - "domainid" => $domain_check['id'] - ); - Database::pexecute($stmt, $params); - - $address_id = Database::lastInsertId(); - $stmt = Database::prepare("UPDATE " . TABLE_PANEL_CUSTOMERS . " - SET `emails_used` = `emails_used` + 1 - WHERE `customerid`= :cid" - ); - Database::pexecute($stmt, array("cid" => $userinfo['customerid'])); - - $log->logAction(USR_ACTION, LOG_INFO, "added email address '" . $email_full . "'"); - redirectTo($filename, array('page' => $page, 'action' => 'edit', 'id' => $address_id, 's' => $s)); + try { + $json_result = Emails::getLocal($userinfo, $_POST)->add(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } + $result = json_decode($json_result, true)['data']; + redirectTo($filename, array('page' => $page, 'action' => 'edit', 'id' => $result['id'], 's' => $s)); } else { $result_stmt = Database::prepare("SELECT `id`, `domain`, `customerid` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `customerid`= :cid diff --git a/lib/classes/api/commands/class.Emails.php b/lib/classes/api/commands/class.Emails.php new file mode 100644 index 00000000..ce45ab72 --- /dev/null +++ b/lib/classes/api/commands/class.Emails.php @@ -0,0 +1,230 @@ + (2010-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package API + * @since 0.10.0 + * + */ +class Emails extends ApiCommand implements ResourceEntity +{ + + /** + * add a new email address + * + * @access admin, customer + * @throws Exception + * @return array + */ + public function add() + { + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'email')) { + throw new Exception("You cannot access this resource", 405); + } + + if ($this->getUserDetail('emails_used') < $this->getUserDetail('emails') || $this->getUserDetail('emails') == '-1') { + + // required parameters + $email_part = $this->getParam('email_part'); + $domain = $this->getParam('domain'); + + // parameters + $iscatchall = $this->getParam('iscatchall', true, 0); + + // validation + if (substr($domain, 0, 4) != 'xn--') { + $idna_convert = new idna_convert_wrapper(); + $domain = $idna_convert->encode(validate($domain, 'domain', '', '', array(), true)); + } + + // check domain and whether it's an email-enabled domain + $domain_check = $this->apiCall('SubDomains.get', array( + 'domainname' => $domain + )); + if ($domain_check['isemaildomain'] == 0) { + standard_error('maindomainnonexist', $domain, true); + } + + // check for catchall-flag + if ($iscatchall) { + $iscatchall = '1'; + $email = '@' . $domain; + } else { + $iscatchall = '0'; + $email = $email_part . '@' . $domain; + } + + // full email value + $email_full = $email_part . '@' . $domain; + + // validate it + if (!validateEmail($email_full)) { + standard_error('emailiswrong', $email_full, true); + } + + // get needed customer info to reduce the email-address-counter by one + if ($this->isAdmin()) { + // get customer id + $customer_id = $this->getParam('customer_id'); + $customer = $this->apiCall('Customers.get', array( + 'id' => $customer_id + )); + // check whether the customer has enough resources to get the ftp-user added + if ($customer['emails_used'] >= $customer['emails'] && $customer['emails'] != '-1') { + throw new Exception("Customer has no more resources available", 406); + } + } else { + $customer_id = $this->getUserDetail('customerid'); + $customer = $this->getUserData(); + } + + // duplicate check + $stmt = Database::prepare(" + SELECT `id`, `email`, `email_full`, `iscatchall`, `destination`, `customerid` FROM `" . TABLE_MAIL_VIRTUAL . "` + WHERE (`email` = :email OR `email_full` = :emailfull ) + AND `customerid`= :cid + "); + $params = array( + "email" => $email, + "emailfull" => $email_full, + "cid" => $customer['customerid'] + ); + $email_check = Database::pexecute_first($stmt, $params, true, true); + + if (strtolower($email_check['email_full']) == strtolower($email_full)) { + standard_error('emailexistalready', $email_full, true); + } elseif ($email_check['email'] == $email) { + standard_error('youhavealreadyacatchallforthisdomain', '', true); + } + + $stmt = Database::prepare(" + INSERT INTO `" . TABLE_MAIL_VIRTUAL . "` SET + `customerid` = :cid, + `email` = :email, + `email_full` = :email_full, + `iscatchall` = :iscatchall, + `domainid` = :domainid + "); + $params = array( + "cid" => $customer['customerid'], + "email" => $email, + "email_full" => $email_full, + "iscatchall" => $iscatchall, + "domainid" => $domain_check['id'] + ); + Database::pexecute($stmt, $params, true, true); + $address_id = Database::lastInsertId(); + + // update customer usage + Customers::increaseUsage($customer_id, 'emails_used'); + + // update admin usage + Admins::increaseUsage($customer['adminid'], 'emails_used'); + + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] added email address '" . $email_full . "'"); + + $result = $this->apiCall('Emails.get', array( + 'emailaddr' => $email_full + )); + return $this->response(200, "successfull", $result); + } + throw new Exception("No more resources available", 406); + } + + /** + * return a email-address entry by either id or email-address + * + * @param int $id + * optional, the customer-id + * @param string $emailaddr + * optional, the email-address + * + * @access admin, customer + * @throws Exception + * @return array + */ + public function get() + { + $id = $this->getParam('id', true, 0); + $ea_optional = ($id <= 0 ? false : true); + $emailaddr = $this->getParam('emailaddr', $ea_optional, ''); + + $params = array(); + $customer_ids = $this->getAllowedCustomerIds('email'); + $params['customerid'] = implode(", ", $customer_ids); + $params['idea'] = ($id <= 0 ? $emailaddr : $id); + + $result_stmt = Database::prepare("SELECT v.`id`, v.`email`, v.`email_full`, v.`iscatchall`, v.`destination`, v.`customerid`, v.`popaccountid`, u.`quota` + FROM `" . TABLE_MAIL_VIRTUAL . "` v + LEFT JOIN `" . TABLE_MAIL_USERS . "` u ON v.`popaccountid` = u.`id` + WHERE v.`customerid` IN (:customerid) + AND (v.`id`= :idea OR (v.`email` = :idea OR v.`email_full` = :idea)) + "); + $result = Database::pexecute_first($result_stmt, $params, true, true); + if ($result) { + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_NOTICE, "[API] get email address '" . $result['email_full'] . "'"); + return $this->response(200, "successfull", $result); + } + $key = ($id > 0 ? "id #" . $id : "emailaddr '" . $emailaddr . "'"); + throw new Exception("Email address with " . $key . " could not be found", 404); + } + + public function update() + { + } + + /** + * list all email addresses, if called from an admin, list all email addresses of all customers you are allowed to view, or specify id or loginname for one specific customer + * + * @param int $customerid + * optional, admin-only, select ftp-users of a specific customer by id + * @param string $loginname + * optional, admin-only, select ftp-users of a specific customer by loginname + * + * @access admin, customer + * @throws Exception + * @return array count|list + */ + public function listing() + { + $customer_ids = $this->getAllowedCustomerIds('email'); + $result = array(); + $params['customerid'] = implode(", ", $customer_ids); + $result_stmt = Database::prepare(" + SELECT m.`id`, m.`domainid`, m.`email`, m.`email_full`, m.`iscatchall`, u.`quota`, m.`destination`, m.`popaccountid`, d.`domain`, u.`mboxsize` + FROM `" . TABLE_MAIL_VIRTUAL . "` m + LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` d ON (m.`domainid` = d.`id`) + LEFT JOIN `" . TABLE_MAIL_USERS . "` u ON (m.`popaccountid` = u.`id`) + WHERE m.`customerid` IN (:customerid) + "); + Database::pexecute($result_stmt, $params, true, true); + while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { + $result[] = $row; + } + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_NOTICE, "[API] list email-addresses"); + return $this->response(200, "successfull", array( + 'count' => count($result), + 'list' => $result + )); + } + + /** + * delete an email address by either id or username + * + * @access admin, customer + * @throws Exception + * @return array + */ + public function delete() + { + } +} diff --git a/lib/classes/api/commands/class.Ftps.php b/lib/classes/api/commands/class.Ftps.php index 6f3223be..56ef099b 100644 --- a/lib/classes/api/commands/class.Ftps.php +++ b/lib/classes/api/commands/class.Ftps.php @@ -75,8 +75,10 @@ class Ftps extends ApiCommand implements ResourceEntity if (Settings::Get('customer.ftpatdomain') == '1') { $ftpusername = validate(trim($ftpusername), 'username', '/^[a-zA-Z0-9][a-zA-Z0-9\-_]+\$?$/', '', array(), true); - $idna_convert = new idna_convert_wrapper(); - $ftpdomain = $idna_convert->encode(validate($ftpdomain, 'domain', '', '', array(), true)); + if (substr($ftpdomain, 0, 4) != 'xn--') { + $idna_convert = new idna_convert_wrapper(); + $ftpdomain = $idna_convert->encode(validate($ftpdomain, 'domain', '', '', array(), true)); + } } $params = array(); diff --git a/phpunit.xml b/phpunit.xml index efedaf51..f1754d3b 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -16,6 +16,7 @@ tests/SubDomains tests/Certificates tests/Ftps + tests/Emails diff --git a/tests/Emails/EmailsTest.php b/tests/Emails/EmailsTest.php new file mode 100644 index 00000000..5a07bc1e --- /dev/null +++ b/tests/Emails/EmailsTest.php @@ -0,0 +1,44 @@ + 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'email_part' => 'info', + 'domain' => 'test2.local' + ]; + $json_result = Emails::getLocal($customer_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals("info@test2.local", $result['email_full']); + $this->assertEquals(0, $result['iscatchall']); + } + + public function testAdminEmailsAdd() + { + global $admin_userdata; + $data = [ + 'email_part' => 'catchall', + 'domain' => 'test2.local', + 'iscatchall' => 1, + 'customer_id' => 1 + ]; + $json_result = Emails::getLocal($admin_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals("catchall@test2.local", $result['email_full']); + $this->assertEquals(1, $result['iscatchall']); + } +} From cd5cef51e848d122fe5bb90eda208e81d42c02a5 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 6 Mar 2018 21:07:08 +0100 Subject: [PATCH 128/746] allow config and settings json file for config-services.php to be downloaded from a remote url Signed-off-by: Michael Kaufmann (d00p) --- install/scripts/config-services.php | 42 ++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/install/scripts/config-services.php b/install/scripts/config-services.php index 3b5d1821..80119178 100755 --- a/install/scripts/config-services.php +++ b/install/scripts/config-services.php @@ -61,7 +61,7 @@ class ConfigServicesCmd extends CmdLineHandler self::println("--create\t\tlets you create a services list configuration for the 'apply' command"); self::println(""); self::println("--apply\t\t\tconfigure your services by given configuration file. To create one run the --create command"); - self::println("\t\t\tExample: --apply=/path/to/my-config.json"); + self::println("\t\t\tExample: --apply=/path/to/my-config.json or --apply=http://domain.tld/my-config.json"); self::println(""); self::println("--list-daemons\t\tOutput the services that are going to be configured using a given config file. No services will be configured."); self::println("\t\t\tExample: --apply=/path/to/my-config.json --list-daemons"); @@ -70,7 +70,7 @@ class ConfigServicesCmd extends CmdLineHandler self::println("\t\t\tExample: --apply=/path/to/my-config.json --daemon=apache24"); self::println(""); self::println("--import-settings\tImport settings from another froxlor installation. This should be done prior to running --apply or alternatively in the same command together."); - self::println("\t\t\tExample: --import-settings=/path/to/Froxlor_settings-[version]-[dbversion]-[date].json"); + self::println("\t\t\tExample: --import-settings=/path/to/Froxlor_settings-[version]-[dbversion]-[date].json or --import-settings=http://domain.tld/Froxlor_settings-[version]-[dbversion]-[date].json"); self::println(""); self::println("--froxlor-dir\t\tpath to froxlor installation"); self::println("\t\t\tExample: --froxlor-dir=/var/www/froxlor/"); @@ -127,7 +127,7 @@ class Action if (array_key_exists("import-settings", $this->_args)) { $this->_importSettings(); } - + if (array_key_exists("create", $this->_args)) { $this->_createConfig(); } elseif (array_key_exists("apply", $this->_args)) { @@ -139,6 +139,15 @@ class Action private function _importSettings() { + if (strtoupper(substr($this->_args["import-settings"], 0, 4)) == 'HTTP') { + echo "Settings file seems to be an URL, trying to download" . PHP_EOL; + $target = "/tmp/froxlor-import-settings-" . time() . ".json"; + if (@file_exists($target)) { + @unlink($target); + } + $this->downloadFile($this->_args["import-settings"], $target); + $this->_args["import-settings"] = $target; + } if (! is_file($this->_args["import-settings"])) { throw new Exception("Given settings file is not a file"); } elseif (! file_exists($this->_args["import-settings"])) { @@ -260,6 +269,15 @@ class Action private function _applyConfig() { + if (strtoupper(substr($this->_args["apply"], 0, 4)) == 'HTTP') { + echo "Config file seems to be an URL, trying to download" . PHP_EOL; + $target = "/tmp/froxlor-config-" . time() . ".json"; + if (@file_exists($target)) { + @unlink($target); + } + $this->downloadFile($this->_args["apply"], $target); + $this->_args["apply"] = $target; + } if (! is_file($this->_args["apply"])) { throw new Exception("Given config file is not a file"); } elseif (! file_exists($this->_args["apply"])) { @@ -443,6 +461,24 @@ class Action } } } + + private function downloadFile($src, $dest) + { + set_time_limit(0); + // This is the file where we save the information + $fp = fopen($dest, 'w+'); + // Here is the file we are downloading, replace spaces with %20 + $ch = curl_init(str_replace(" ", "%20", $src)); + curl_setopt($ch, CURLOPT_TIMEOUT, 50); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + // write curl response to file + curl_setopt($ch, CURLOPT_FILE, $fp); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + // get curl response + curl_exec($ch); + curl_close($ch); + fclose($fp); + } } // give control to command line handler From 164650adc3fee251fcd4201bb3ef2e7111a87c45 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 8 Mar 2018 11:49:28 +0100 Subject: [PATCH 129/746] added Emails.update() and Emails.delete() commands Signed-off-by: Michael Kaufmann (d00p) --- customer_email.php | 153 ++++--------- lib/classes/api/abstract.ApiCommand.php | 15 +- lib/classes/api/commands/class.Customers.php | 8 +- lib/classes/api/commands/class.Emails.php | 215 +++++++++++++++++-- lib/classes/settings/class.Settings.php | 9 + 5 files changed, 253 insertions(+), 147 deletions(-) diff --git a/customer_email.php b/customer_email.php index efed543a..870f243c 100644 --- a/customer_email.php +++ b/customer_email.php @@ -133,65 +133,24 @@ if ($page == 'overview') { eval("echo \"" . getTemplate("email/emails") . "\";"); } elseif ($action == 'delete' && $id != 0) { - $stmt = Database::prepare("SELECT `id`, `email`, `email_full`, `iscatchall`, `destination`, `customerid`, `popaccountid` FROM `" . TABLE_MAIL_VIRTUAL . "` - WHERE `customerid`= :customerid - AND `id`= :id" - ); - $result = Database::pexecute_first($stmt, array("customerid" => $userinfo['customerid'], "id" => $id)); + try { + $json_result = Emails::getLocal($userinfo, array( + 'id' => $id + ))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; if (isset($result['email']) && $result['email'] != '') { if (isset($_POST['send']) && $_POST['send'] == 'send') { - $update_users_query_addon = ''; - - if ($result['destination'] != '') { - $result['destination'] = explode(' ', $result['destination']); - $number_forwarders = count($result['destination']); - - if ($result['popaccountid'] != 0) { - // Free the Quota used by the email account - if (Settings::Get('system.mail_quota_enabled') == 1) { - $stmt = Database::prepare("SELECT `quota` FROM `" . TABLE_MAIL_USERS . "` - WHERE `customerid`= :customerid - AND `id`= :id" - ); - $res_quota = Database::pexecute_first($stmt, array("customerid" => $userinfo['customerid'], "id" => $result['popaccountid'])); - $update_users_query_addon.= " , `email_quota_used` = `email_quota_used` - " . (int)$res_quota['quota'] . " "; - } - - $stmt = Database::prepare("DELETE FROM `" . TABLE_MAIL_USERS . "` - WHERE `customerid`= :customerid - AND `id`= :id" - ); - Database::pexecute($stmt, array("customerid" => $userinfo['customerid'], "id" => $result['popaccountid'])); - $update_users_query_addon .= " , `email_accounts_used` = `email_accounts_used` - 1 "; - $number_forwarders-= 1; - $log->logAction(USR_ACTION, LOG_INFO, "deleted forwarder for email address '" . $result['email'] . "'"); - } - } else { - $number_forwarders = 0; + try { + Emails::getLocal($userinfo, array( + 'id' => $id + ))->delete(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - - if (isset($_POST['delete_userfiles']) - && (int)$_POST['delete_userfiles'] == 1 - ) { - inserttask('7', $userinfo['loginname'], $result['email_full']); - } - - $stmt = Database::prepare("DELETE FROM `" . TABLE_MAIL_VIRTUAL . "` - WHERE `customerid`= :customerid - AND `id`= :id" - ); - Database::pexecute($stmt, array("customerid" => $userinfo['customerid'], "id" => $id)); - - $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` - SET `emails_used`=`emails_used` - 1 , - `email_forwarders_used` = `email_forwarders_used` - :nforwarders - $update_users_query_addon - WHERE `customerid`= :customerid" - ); - Database::pexecute($stmt, array("nforwarders" => $number_forwarders, "customerid" => $userinfo['customerid'])); - - $log->logAction(USR_ACTION, LOG_INFO, "deleted email address '" . $result['email'] . "'"); redirectTo($filename, array('page' => $page, 's' => $s)); } else { if ($result['popaccountid'] != '0') { @@ -244,14 +203,14 @@ if ($page == 'overview') { standard_error('allresourcesused'); } } elseif ($action == 'edit' && $id != 0) { - $stmt = Database::prepare("SELECT `v`.`id`, `v`.`email`, `v`.`email_full`, `v`.`iscatchall`, `v`.`destination`, `v`.`customerid`, `v`.`popaccountid`, `u`.`quota` - FROM `" . TABLE_MAIL_VIRTUAL . "` `v` - LEFT JOIN `" . TABLE_MAIL_USERS . "` `u` - ON(`v`.`popaccountid` = `u`.`id`) - WHERE `v`.`customerid`= :cid - AND `v`.`id`= :id" - ); - $result = Database::pexecute_first($stmt, array("cid" => $userinfo['customerid'], "id" => $id)); + try { + $json_result = Emails::getLocal($userinfo, array( + 'id' => $id + ))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; if (isset($result['email']) && $result['email'] != '') { $result['email'] = $idna_convert->decode($result['email']); @@ -289,58 +248,24 @@ if ($page == 'overview') { eval("echo \"" . getTemplate("email/emails_edit") . "\";"); } } elseif ($action == 'togglecatchall' && $id != 0) { - if (Settings::Get('catchall.catchall_enabled') == '1') { - $stmt = Database::prepare("SELECT `id`, `email`, `email_full`, `iscatchall`, `destination`, `customerid`, `popaccountid` FROM `" . TABLE_MAIL_VIRTUAL . "` - WHERE `customerid`= :cid - AND `id`= :id" - ); - $result = Database::pexecute_first($stmt, array("cid" => $userinfo['customerid'], "id" => $id)); - - if (isset($result['email']) && $result['email'] != '') { - if ($result['iscatchall'] == '1') { - $stmt = Database::prepare("UPDATE `" . TABLE_MAIL_VIRTUAL . "` - SET `email` = :email, `iscatchall` = '0' - WHERE `customerid`= :cid - AND `id`= :id" - ); - $params = array( - "email" => $result['email_full'], - "cid" => $userinfo['customerid'], - "id" => $id - ); - Database::pexecute($stmt, $params); - } else { - $email_parts = explode('@', $result['email_full']); - $email = '@' . $email_parts[1]; - $stmt = Database::prepare("SELECT `id`, `email`, `email_full`, `iscatchall`, `destination`, `customerid` FROM `" . TABLE_MAIL_VIRTUAL . "` - WHERE `email`= :email - AND `customerid`= :cid" - ); - $email_check = Database::pexecute_first($stmt, array("email" => $email, "cid" => $userinfo['customerid'])); - - if ($email_check['email'] == $email) { - standard_error('youhavealreadyacatchallforthisdomain'); - } else { - $stmt = Database::prepare("UPDATE `" . TABLE_MAIL_VIRTUAL . "` - SET `email` = :email , `iscatchall` = '1' - WHERE `customerid`= :cid - AND `id`= :id" - ); - $params = array( - "email" => $email, - "cid" => $userinfo['customerid'], - "id" => $id - ); - Database::pexecute($stmt, $params); - $log->logAction(USR_ACTION, LOG_INFO, "edited email address '" . $email . "'"); - } - } - - redirectTo($filename, array('page' => $page, 'action' => 'edit', 'id' => $id, 's' => $s)); - } - } else { - standard_error(array('operationnotpermitted', 'featureisdisabled'), 'Catchall'); + try { + $json_result = Emails::getLocal($userinfo, array( + 'id' => $id + ))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } + $result = json_decode($json_result, true)['data']; + + try { + Emails::getLocal($userinfo, array( + 'id' => $id, + 'iscatchall' => ($result['iscatchall'] == '1' ? 0 : 1) + ))->update(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + redirectTo($filename, array('page' => $page, 'action' => 'edit', 'id' => $id, 's' => $s)); } } elseif ($page == 'accounts') { if ($action == 'add' && $id != 0) { diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index e3677f9a..0528a79c 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -106,7 +106,7 @@ abstract class ApiCommand $this->version = $version; $this->dbversion = $dbversion; $this->branding = $branding; - + if (! is_null($params)) { $params = $this->trimArray($params); } @@ -417,8 +417,9 @@ abstract class ApiCommand /** * returns an array of customers the current user can access * - * @param string $customer_hide_option optional, when called as customer, some options might be hidden due to the panel.customer_hide_options ettings - * + * @param string $customer_hide_option + * optional, when called as customer, some options might be hidden due to the panel.customer_hide_options ettings + * * @throws Exception * @return array */ @@ -430,7 +431,7 @@ abstract class ApiCommand // or optionally for one specific customer identified by id or loginname $customerid = $this->getParam('customerid', true, 0); $loginname = $this->getParam('loginname', true, ''); - + if (! empty($customerid) || ! empty($loginname)) { $_result = $this->apiCall('Customers.get', array( 'id' => $customerid, @@ -447,7 +448,7 @@ abstract class ApiCommand $customer_ids[] = $customer['customerid']; } } else { - if (!empty($customer_hide_option) && Settings::IsInList('panel.customer_hide_options', $customer_hide_option)) { + if (! empty($customer_hide_option) && Settings::IsInList('panel.customer_hide_options', $customer_hide_option)) { throw new Exception("You cannot access this resource", 405); } $customer_ids = array( @@ -467,11 +468,11 @@ abstract class ApiCommand * @param string $resource * @param string $extra */ - protected static function updateResourceUsage($table = null, $keyfield = null, $key = null, $operator = '+', $resource = null, $extra = null) + protected static function updateResourceUsage($table = null, $keyfield = null, $key = null, $operator = '+', $resource = null, $extra = null, $step = 1) { $stmt = Database::prepare(" UPDATE `" . $table . "` - SET `" . $resource . "` = `" . $resource . "` " . $operator . " 1 " . $extra . " + SET `" . $resource . "` = `" . $resource . "` " . $operator . " " . (int)$step . " " . $extra . " WHERE `" . $keyfield . "` = :key "); Database::pexecute($stmt, array( diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index fdbd42ce..7291dea3 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -1547,9 +1547,9 @@ class Customers extends ApiCommand implements ResourceEntity * @param string $extra * optional, default empty */ - public static function increaseUsage($customerid = 0, $resource = null, $extra = '') + public static function increaseUsage($customerid = 0, $resource = null, $extra = '', $increase_by = 1) { - self::updateResourceUsage(TABLE_PANEL_CUSTOMERS, 'customerid', $customerid, '+', $resource, $extra); + self::updateResourceUsage(TABLE_PANEL_CUSTOMERS, 'customerid', $customerid, '+', $resource, $extra, $increase_by); } /** @@ -1560,8 +1560,8 @@ class Customers extends ApiCommand implements ResourceEntity * @param string $extra * optional, default empty */ - public static function decreaseUsage($customerid = 0, $resource = null, $extra = '') + public static function decreaseUsage($customerid = 0, $resource = null, $extra = '', $decrease_by = 1) { - self::updateResourceUsage(TABLE_PANEL_CUSTOMERS, 'customerid', $customerid, '-', $resource, $extra); + self::updateResourceUsage(TABLE_PANEL_CUSTOMERS, 'customerid', $customerid, '-', $resource, $extra, $decrease_by); } } diff --git a/lib/classes/api/commands/class.Emails.php b/lib/classes/api/commands/class.Emails.php index ce45ab72..8f712c31 100644 --- a/lib/classes/api/commands/class.Emails.php +++ b/lib/classes/api/commands/class.Emails.php @@ -17,7 +17,7 @@ */ class Emails extends ApiCommand implements ResourceEntity { - + /** * add a new email address * @@ -30,7 +30,7 @@ class Emails extends ApiCommand implements ResourceEntity if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'email')) { throw new Exception("You cannot access this resource", 405); } - + if ($this->getUserDetail('emails_used') < $this->getUserDetail('emails') || $this->getUserDetail('emails') == '-1') { // required parameters @@ -39,13 +39,13 @@ class Emails extends ApiCommand implements ResourceEntity // parameters $iscatchall = $this->getParam('iscatchall', true, 0); - + // validation if (substr($domain, 0, 4) != 'xn--') { $idna_convert = new idna_convert_wrapper(); $domain = $idna_convert->encode(validate($domain, 'domain', '', '', array(), true)); } - + // check domain and whether it's an email-enabled domain $domain_check = $this->apiCall('SubDomains.get', array( 'domainname' => $domain @@ -53,7 +53,11 @@ class Emails extends ApiCommand implements ResourceEntity if ($domain_check['isemaildomain'] == 0) { standard_error('maindomainnonexist', $domain, true); } - + + if (Settings::Get('catchall.catchall_enabled') != '1') { + $iscatchall = 0; + } + // check for catchall-flag if ($iscatchall) { $iscatchall = '1'; @@ -62,15 +66,15 @@ class Emails extends ApiCommand implements ResourceEntity $iscatchall = '0'; $email = $email_part . '@' . $domain; } - + // full email value $email_full = $email_part . '@' . $domain; - + // validate it - if (!validateEmail($email_full)) { + if (! validateEmail($email_full)) { standard_error('emailiswrong', $email_full, true); } - + // get needed customer info to reduce the email-address-counter by one if ($this->isAdmin()) { // get customer id @@ -86,7 +90,7 @@ class Emails extends ApiCommand implements ResourceEntity $customer_id = $this->getUserDetail('customerid'); $customer = $this->getUserData(); } - + // duplicate check $stmt = Database::prepare(" SELECT `id`, `email`, `email_full`, `iscatchall`, `destination`, `customerid` FROM `" . TABLE_MAIL_VIRTUAL . "` @@ -99,13 +103,13 @@ class Emails extends ApiCommand implements ResourceEntity "cid" => $customer['customerid'] ); $email_check = Database::pexecute_first($stmt, $params, true, true); - + if (strtolower($email_check['email_full']) == strtolower($email_full)) { standard_error('emailexistalready', $email_full, true); } elseif ($email_check['email'] == $email) { standard_error('youhavealreadyacatchallforthisdomain', '', true); } - + $stmt = Database::prepare(" INSERT INTO `" . TABLE_MAIL_VIRTUAL . "` SET `customerid` = :cid, @@ -123,15 +127,15 @@ class Emails extends ApiCommand implements ResourceEntity ); Database::pexecute($stmt, $params, true, true); $address_id = Database::lastInsertId(); - + // update customer usage Customers::increaseUsage($customer_id, 'emails_used'); - + // update admin usage Admins::increaseUsage($customer['adminid'], 'emails_used'); - + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] added email address '" . $email_full . "'"); - + $result = $this->apiCall('Emails.get', array( 'emailaddr' => $email_full )); @@ -139,7 +143,7 @@ class Emails extends ApiCommand implements ResourceEntity } throw new Exception("No more resources available", 406); } - + /** * return a email-address entry by either id or email-address * @@ -147,7 +151,7 @@ class Emails extends ApiCommand implements ResourceEntity * optional, the customer-id * @param string $emailaddr * optional, the email-address - * + * * @access admin, customer * @throws Exception * @return array @@ -177,11 +181,93 @@ class Emails extends ApiCommand implements ResourceEntity $key = ($id > 0 ? "id #" . $id : "emailaddr '" . $emailaddr . "'"); throw new Exception("Email address with " . $key . " could not be found", 404); } - + + /** + * toggle catchall flag of given email address either by id or email-address + * + * @param int $id + * optional, the customer-id + * @param string $emailaddr + * optional, the email-address + * @param boolean $iscatchall + * optional + * @param int $customerid + * optional, required when called as admin/reseller + * + * @access admin, customer + * @throws Exception + * @return array + */ public function update() { + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'email')) { + throw new Exception("You cannot access this resource", 405); + } + + // if enabling catchall is not allowed by settings, we do not need + // to run update() + if (Settings::Get('catchall.catchall_enabled') != '1') { + standard_error(array( + 'operationnotpermitted', + 'featureisdisabled' + ), 'catchall', true); + } + + $id = $this->getParam('id', true, 0); + $ea_optional = ($id <= 0 ? false : true); + $emailaddr = $this->getParam('emailaddr', $ea_optional, ''); + + $result = $this->apiCall('Emails.get', array( + 'id' => $id, + 'emailaddr' => $emailaddr + )); + $id = $result['id']; + + // parameters + $iscatchall = $this->getParam('iscatchall', true, $result['iscatchall']); + + // get needed customer info to reduce the email-address-counter by one + if ($this->isAdmin()) { + // get customer id + $customer_id = $this->getParam('customerid'); + $customer = $this->apiCall('Customers.get', array( + 'id' => $customer_id + )); + } else { + $customer_id = $this->getUserDetail('customerid'); + $customer = $this->getUserData(); + } + + // check for catchall-flag + if ($iscatchall) { + $iscatchall = '1'; + $email_parts = explode('@', $result['email_full']); + $email = '@' . $email_parts[1]; + } else { + $iscatchall = '0'; + $email = $result['email_full']; + } + + $stmt = Database::prepare(" + UPDATE `" . TABLE_MAIL_VIRTUAL . "` + SET `email` = :email , `iscatchall` = :caflag + WHERE `customerid`= :cid AND `id`= :id + "); + $params = array( + "email" => $email, + "caflag" => $iscatchall, + "cid" => $customer['customerid'], + "id" => $id + ); + Database::pexecute($stmt, $params, true, true); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] toggled catchall-flag for email address '" . $result['email_full'] . "'"); + + $result = $this->apiCall('Emails.get', array( + 'emailaddr' => $result['email_full'] + )); + return $this->response(200, "successfull", $result); } - + /** * list all email addresses, if called from an admin, list all email addresses of all customers you are allowed to view, or specify id or loginname for one specific customer * @@ -189,7 +275,7 @@ class Emails extends ApiCommand implements ResourceEntity * optional, admin-only, select ftp-users of a specific customer by id * @param string $loginname * optional, admin-only, select ftp-users of a specific customer by loginname - * + * * @access admin, customer * @throws Exception * @return array count|list @@ -216,15 +302,100 @@ class Emails extends ApiCommand implements ResourceEntity 'list' => $result )); } - + /** * delete an email address by either id or username * + * @param int $id + * optional, the customer-id + * @param string $emailaddr + * optional, the email-address + * @param boolean $delete_userfiles + * optional, delete email data from filesystem, default: no + * @param int $customerid + * optional, required when called as admin/reseller + * * @access admin, customer * @throws Exception * @return array */ public function delete() { + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'email')) { + throw new Exception("You cannot access this resource", 405); + } + + $id = $this->getParam('id', true, 0); + $ea_optional = ($id <= 0 ? false : true); + $emailaddr = $this->getParam('emailaddr', $ea_optional, ''); + + $result = $this->apiCall('Emails.get', array( + 'id' => $id, + 'emailaddr' => $emailaddr + )); + $id = $result['id']; + + // parameters + $delete_userfiles = $this->getParam('delete_userfiles', true, 0); + + // get needed customer info to reduce the email-address-counter by one + if ($this->isAdmin()) { + // get customer id + $customer_id = $this->getParam('customerid'); + $customer = $this->apiCall('Customers.get', array( + 'id' => $customer_id + )); + } else { + $customer_id = $this->getUserDetail('customerid'); + $customer = $this->getUserData(); + } + + // check for forwarders + $number_forwarders = 0; + if ($result['destination'] != '') { + $result['destination'] = explode(' ', $result['destination']); + $number_forwarders = count($result['destination']); + Customers::decreaseUsage($customer['customerid'], 'email_forwarders_used', '', $number_forwarders); + Admins::decreaseUsage($customer['customerid'], 'email_forwarders_used', '', $number_forwarders); + } + // check whether this address is an account + if ($result['popaccountid'] != 0) { + // Free the Quota used by the email account + if (Settings::Get('system.mail_quota_enabled') == 1) { + $stmt = Database::prepare("SELECT `quota` FROM `" . TABLE_MAIL_USERS . "` WHERE `customerid`= :customerid AND `id`= :id"); + $res_quota = Database::pexecute_first($stmt, array( + "customerid" => $customer_id, + "id" => $result['popaccountid'] + ), true, true); + Customers::decreaseUsage($customer['customerid'], 'email_quota_used', '', $res_quota['quota']); + Admins::decreaseUsage($customer['customerid'], 'email_quota_used', '', $res_quota['quota']); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] deleted quota entries for email address '" . $result['email_full'] . "'"); + } + // delete account + $stmt = Database::prepare("DELETE FROM `" . TABLE_MAIL_USERS . "` WHERE `customerid`= :customerid AND `id`= :id"); + Database::pexecute($stmt, array( + "customerid" => $customer_id, + "id" => $result['popaccountid'] + ), true, true); + Customers::decreaseUsage($customer['customerid'], 'email_accounts_used'); + Admins::decreaseUsage($customer['customerid'], 'email_accounts_used'); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] deleted email account '" . $result['email_full'] . "'"); + } + + if ($delete_userfiles) { + inserttask('7', $customer['loginname'], $result['email_full']); + } + + // delete address + $stmt = Database::prepare("DELETE FROM `" . TABLE_MAIL_VIRTUAL . "` WHERE `customerid`= :customerid AND `id`= :id"); + Database::pexecute($stmt, array( + "customerid" => $customer_id, + "id" => $id + ), true, true); + Customers::decreaseUsage($customer['customerid'], 'emails_used'); + Admins::decreaseUsage($customer['customerid'], 'emails_used'); + + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] deleted email address '" . $result['email_full'] . "'"); + return $this->response(200, "successfull", $result); } } diff --git a/lib/classes/settings/class.Settings.php b/lib/classes/settings/class.Settings.php index e6b8a37b..3ab57ecb 100644 --- a/lib/classes/settings/class.Settings.php +++ b/lib/classes/settings/class.Settings.php @@ -28,6 +28,13 @@ * @author Froxlor team (2010-) * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt * @package Classes + * + * @method static mixed Get ($setting = null) return a setting-value by its group and varname separated by a dot (group.varname) + * @method static boolean Set ($setting = null, $value = null, $instant_save = true) update a setting / set a new value + * @method static boolean IsInList ($setting = null, $entry = null) tests if a setting-value that i s a comma separated list contains an entry + * @method static boolean AddNew ($setting = null, $value = null) add a new setting to the database (mainly used in updater) + * @method static boolean Flush () Store all un-saved changes to the database and re-read in all settings + * @method static void Stash () forget all un-saved changes to settings */ class Settings { @@ -148,6 +155,8 @@ class Settings { * @param string $setting a group and a varname separated by a dot (group.varname) * @param string $value * @param boolean $instant_save + * + * @return bool */ public function pSet($setting = null, $value = null, $instant_save = true) { // check whether the setting exists From 7b52c0c78c2c53507f1700f4429b1a677e0191e2 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 8 Mar 2018 17:03:17 +0100 Subject: [PATCH 130/746] fix default value of panel.no_robots settings; add phpdoc for Database-class-methods Signed-off-by: Michael Kaufmann (d00p) --- actions/admin/settings/100.panel.php | 2 +- lib/classes/database/class.Database.php | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/actions/admin/settings/100.panel.php b/actions/admin/settings/100.panel.php index 49308bed..cae04cf9 100644 --- a/actions/admin/settings/100.panel.php +++ b/actions/admin/settings/100.panel.php @@ -71,7 +71,7 @@ return array( 'settinggroup' => 'panel', 'varname' => 'no_robots', 'type' => 'bool', - 'default' => false, + 'default' => true, 'save_method' => 'storeSettingField', ), 'panel_paging' => array( diff --git a/lib/classes/database/class.Database.php b/lib/classes/database/class.Database.php index 3174c2b3..80ef61bc 100644 --- a/lib/classes/database/class.Database.php +++ b/lib/classes/database/class.Database.php @@ -28,6 +28,11 @@ * @author Froxlor team (2010-) * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt * @package Classes + * + * @method static \PDOStatement prepare($statement, array $driver_options = null) Prepares a statement for execution and returns a statement object + * @method static \PDOStatement query ($statement) Executes an SQL statement, returning a result set as a PDOStatement object + * @method static string lastInsertId ($name = null) Returns the ID of the last inserted row or sequence value + * @method static string quote ($string, $parameter_type = null) Quotes a string for use in a query. */ class Database { From b205f8ea5df1cdebd25bef661b6963b30d2872b9 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sun, 11 Mar 2018 10:24:17 +0100 Subject: [PATCH 131/746] add EmailFowarders ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- customer_email.php | 86 ++----- lib/classes/api/abstract.ApiCommand.php | 15 +- .../api/commands/class.EmailForwarders.php | 226 ++++++++++++++++++ lib/classes/api/commands/class.Emails.php | 15 +- 4 files changed, 274 insertions(+), 68 deletions(-) create mode 100644 lib/classes/api/commands/class.EmailForwarders.php diff --git a/customer_email.php b/customer_email.php index 870f243c..8be1013c 100644 --- a/customer_email.php +++ b/customer_email.php @@ -658,48 +658,21 @@ if ($page == 'overview') { } elseif ($page == 'forwarders') { if ($action == 'add' && $id != 0) { if ($userinfo['email_forwarders_used'] < $userinfo['email_forwarders'] || $userinfo['email_forwarders'] == '-1') { - $stmt = Database::prepare("SELECT `id`, `email`, `email_full`, `iscatchall`, `destination`, `customerid`, `popaccountid`, `domainid` FROM `" . TABLE_MAIL_VIRTUAL . "` - WHERE `customerid`= :cid - AND `id`= :id" - ); - $result = Database::pexecute_first($stmt, array("cid" => $userinfo['customerid'], "id" => $id)); + try { + $json_result = Emails::getLocal($userinfo, array('id' => $id))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; if (isset($result['email']) && $result['email'] != '') { if (isset($_POST['send']) && $_POST['send'] == 'send') { - $destination = $idna_convert->encode($_POST['destination']); - $result['destination_array'] = explode(' ', $result['destination']); - - if ($destination == '') { - standard_error('destinationnonexist'); - } elseif (!validateEmail($destination)) { - standard_error('destinationiswrong', $destination); - } elseif ($destination == $result['email']) { - standard_error('destinationalreadyexistasmail', $destination); - } elseif (in_array($destination, $result['destination_array'])) { - standard_error('destinationalreadyexist', $destination); - } else { - $result['destination'].= ' ' . $destination; - $stmt = Database::prepare("UPDATE `" . TABLE_MAIL_VIRTUAL . "` - SET `destination` = :dest - WHERE `customerid`= :cid - AND `id`= :id" - ); - $params = array( - "dest" => makeCorrectDestination($result['destination']), - "cid" => $userinfo['customerid'], - "id" => $id - ); - Database::pexecute($stmt, $params); - - $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` - SET `email_forwarders_used` = `email_forwarders_used` + 1 - WHERE `customerid`= :cid" - ); - Database::pexecute($stmt, array("cid" => $userinfo['customerid'])); - - $log->logAction(USR_ACTION, LOG_INFO, "added email forwarder for '" . $result['email_full'] . "'"); - redirectTo($filename, array('page' => 'emails', 'action' => 'edit', 'id' => $id, 's' => $s)); + try { + EmailForwarders::getLocal($userinfo, $_POST)->add(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } + redirectTo($filename, array('page' => 'emails', 'action' => 'edit', 'id' => $id, 's' => $s)); } else { $result['email_full'] = $idna_convert->decode($result['email_full']); $result = htmlentities_array($result); @@ -717,11 +690,12 @@ if ($page == 'overview') { standard_error('allresourcesused'); } } elseif ($action == 'delete' && $id != 0) { - $stmt = Database::prepare("SELECT `id`, `email`, `email_full`, `iscatchall`, `destination`, `customerid`, `popaccountid` FROM `" . TABLE_MAIL_VIRTUAL . "` - WHERE `customerid`='" . (int)$userinfo['customerid'] . "' - AND `id`='" . (int)$id . "'" - ); - $result = Database::pexecute_first($stmt, array("cid" => $userinfo['customerid'])); + try { + $json_result = Emails::getLocal($userinfo, array('id' => $id))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; if (isset($result['destination']) && $result['destination'] != '') { if (isset($_POST['forwarderid'])) { @@ -738,27 +712,11 @@ if ($page == 'overview') { $forwarder = $result['destination'][$forwarderid]; if (isset($_POST['send']) && $_POST['send'] == 'send') { - unset($result['destination'][$forwarderid]); - $result['destination'] = implode(' ', $result['destination']); - $stmt = Database::prepare("UPDATE `" . TABLE_MAIL_VIRTUAL . "` - SET `destination` = :dest - WHERE `customerid`= :cid - AND `id`= :id" - ); - $params = array( - "dest" => makeCorrectDestination($result['destination']), - "cid" => $userinfo['customerid'], - "id" => $id - ); - Database::pexecute($stmt, $params); - - $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` - SET `email_forwarders_used` = `email_forwarders_used` - 1 - WHERE `customerid`= :cid" - ); - Database::pexecute($stmt, array("cid" => $userinfo['customerid'])); - - $log->logAction(USR_ACTION, LOG_INFO, "deleted email forwarder for '" . $result['email_full'] . "'"); + try { + EmailForwarders::getLocal($userinfo, $_POST)->delete(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } redirectTo($filename, array('page' => 'emails', 'action' => 'edit', 'id' => $id, 's' => $s)); } else { ask_yesno('email_reallydelete_forwarder', $filename, array('id' => $id, 'forwarderid' => $forwarderid, 'page' => $page, 'action' => $action), $idna_convert->decode($result['email_full']) . ' -> ' . $idna_convert->decode($forwarder)); diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index 0528a79c..da3f0430 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -349,6 +349,16 @@ abstract class ApiCommand throw new Exception("Unable to update parameter '" . $param . "' as it does not exist", 500); } + /** + * return list of all parameters + * + * @return array + */ + protected function getParamList() + { + return $this->cmd_params; + } + /** * return logger instance * @@ -455,6 +465,9 @@ abstract class ApiCommand $this->getUserDetail('customerid') ); } + if (empty($customer_ids)) { + throw new Exception("Required resource unsatisfied.", 405); + } return $customer_ids; } @@ -472,7 +485,7 @@ abstract class ApiCommand { $stmt = Database::prepare(" UPDATE `" . $table . "` - SET `" . $resource . "` = `" . $resource . "` " . $operator . " " . (int)$step . " " . $extra . " + SET `" . $resource . "` = `" . $resource . "` " . $operator . " " . (int) $step . " " . $extra . " WHERE `" . $keyfield . "` = :key "); Database::pexecute($stmt, array( diff --git a/lib/classes/api/commands/class.EmailForwarders.php b/lib/classes/api/commands/class.EmailForwarders.php new file mode 100644 index 00000000..f4658c9b --- /dev/null +++ b/lib/classes/api/commands/class.EmailForwarders.php @@ -0,0 +1,226 @@ + (2010-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package API + * @since 0.10.0 + * + */ +class EmailForwarders extends ApiCommand implements ResourceEntity +{ + + /** + * add new email-forwarder entry for given email-address by either id or email-address + * + * @param int $id + * optional, the email-address-id + * @param string $emailaddr + * optional, the email-address to add the forwarder for + * @param string $destination + * email-address to add as forwarder + * @param int $customerid + * optional, required when called as admin/reseller + * + * @access admin,customer + * @throws Exception + * @return array + */ + public function add() + { + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'email')) { + throw new Exception("You cannot access this resource", 405); + } + + if ($this->getUserDetail('email_forwarders_used') < $this->getUserDetail('email_forwarders') || $this->getUserDetail('email_forwarders') == '-1') { + + // parameter + $id = $this->getParam('id', true, 0); + $ea_optional = ($id <= 0 ? false : true); + $emailaddr = $this->getParam('emailaddr', $ea_optional, ''); + $destination = $this->getParam('destination'); + + // validation + $idna_convert = new idna_convert_wrapper(); + $destination = $idna_convert->encode($destination); + + $result = $this->apiCall('Emails.get', array( + 'id' => $id, + 'emailaddr' => $emailaddr + )); + $id = $result['id']; + + // current destination array + $result['destination_array'] = explode(' ', $result['destination']); + + if (! validateEmail($destination)) { + standard_error('destinationiswrong', $destination, true); + } elseif ($destination == $result['email']) { + standard_error('destinationalreadyexistasmail', $destination, true); + } elseif (in_array($destination, $result['destination_array'])) { + standard_error('destinationalreadyexist', $destination, true); + } + + // get needed customer info to reduce the email-address-counter by one + if ($this->isAdmin()) { + // get customer id + $customer_id = $this->getParam('customerid'); + $customer = $this->apiCall('Customers.get', array( + 'id' => $customer_id + )); + // check whether the customer has enough resources to get the mail-forwarder added + if ($customer['email_forwarders_used'] >= $customer['email_forwarders'] && $customer['email_forwarders'] != '-1') { + throw new Exception("Customer has no more resources available", 406); + } + } else { + $customer_id = $this->getUserDetail('customerid'); + $customer = $this->getUserData(); + } + + // add destination to address + $result['destination'] .= ' ' . $destination; + $stmt = Database::prepare(" + UPDATE `" . TABLE_MAIL_VIRTUAL . "` SET `destination` = :dest + WHERE `customerid`= :cid AND `id`= :id + "); + $params = array( + "dest" => makeCorrectDestination($result['destination']), + "cid" => $customer_id, + "id" => $id + ); + Database::pexecute($stmt, $params, true, true); + + // update customer usage + Customers::increaseUsage($customer_id, 'email_forwarders_used'); + + // update admin usage + Admins::increaseUsage($customer['adminid'], 'email_forwarders_used'); + + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] added email forwarder for '" . $result['email_full'] . "'"); + + $result = $this->apiCall('Emails.get', array( + 'emailaddr' => $result['email_full'] + )); + return $this->response(200, "successfull", $result); + } + throw new Exception("No more resources available", 406); + } + + public function get() + { + throw new Exception('You cannot directly get an email forwarder. You need to call Emails.get()', 303); + } + + public function update() + { + throw new Exception('You cannot update an email forwarder. You need to delete the entry and create a new one.', 303); + } + + public function listing() + { + throw new Exception('You cannot directly list email forwarders. You need to call Emails.listing()', 303); + } + + /** + * delete email-forwarder entry for given email-address by either id or email-address and forwarder-id + * + * @param int $id + * optional, the email-address-id + * @param string $emailaddr + * optional, the email-address to add the forwarder for + * @param int $forwarderid + * id of the forwarder to delete + * @param int $customerid + * optional, required when called as admin/reseller + * + * @access admin,customer + * @throws Exception + * @return array + */ + public function delete() + { + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'email')) { + throw new Exception("You cannot access this resource", 405); + } + + if ($this->getUserDetail('email_forwarders_used') < $this->getUserDetail('email_forwarders') || $this->getUserDetail('email_forwarders') == '-1') { + + // parameter + $id = $this->getParam('id', true, 0); + $ea_optional = ($id <= 0 ? false : true); + $emailaddr = $this->getParam('emailaddr', $ea_optional, ''); + $forwarderid = $this->getParam('forwarderid'); + + // validation + $result = $this->apiCall('Emails.get', array( + 'id' => $id, + 'emailaddr' => $emailaddr + )); + $id = $result['id']; + + $result['destination'] = explode(' ', $result['destination']); + if (isset($result['destination'][$forwarderid]) && $result['email'] != $result['destination'][$forwarderid]) { + + // get needed customer info to reduce the email-address-counter by one + if ($this->isAdmin()) { + // get customer id + $customer_id = $this->getParam('customer_id'); + $customer = $this->apiCall('Customers.get', array( + 'id' => $customer_id + )); + } else { + $customer_id = $this->getUserDetail('customerid'); + $customer = $this->getUserData(); + } + + // get specific forwarder + $forwarder = $result['destination'][$forwarderid]; + // unset it from array + unset($result['destination'][$forwarderid]); + // rebuild destination-string + $result['destination'] = implode(' ', $result['destination']); + // update in DB + $stmt = Database::prepare(" + UPDATE `" . TABLE_MAIL_VIRTUAL . "` SET `destination` = :dest + WHERE `customerid`= :cid AND `id`= :id + "); + $params = array( + "dest" => makeCorrectDestination($result['destination']), + "cid" => $customer['customerid'], + "id" => $id + ); + Database::pexecute($stmt, $params, true, true); + + $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` + SET `email_forwarders_used` = `email_forwarders_used` - 1 + WHERE `customerid`= :cid"); + Database::pexecute($stmt, array( + "cid" => $userinfo['customerid'] + )); + + // update customer usage + Customers::decreaseUsage($customer_id, 'email_forwarders_used'); + + // update admin usage + Admins::decreaseUsage($customer['adminid'], 'email_forwarders_used'); + + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] deleted email forwarder for '" . $result['email_full'] . "'"); + + $result = $this->apiCall('Emails.get', array( + 'emailaddr' => $result['email_full'] + )); + return $this->response(200, "successfull", $result); + } + throw new Exception("Unknown forwarder id", 404); + } + throw new Exception("No more resources available", 406); + } +} diff --git a/lib/classes/api/commands/class.Emails.php b/lib/classes/api/commands/class.Emails.php index 8f712c31..b72bfbeb 100644 --- a/lib/classes/api/commands/class.Emails.php +++ b/lib/classes/api/commands/class.Emails.php @@ -21,6 +21,15 @@ class Emails extends ApiCommand implements ResourceEntity /** * add a new email address * + * @param string $email_part + * name of the address before @ + * @param string $domain + * domain-name for the email-address + * @param boolean $iscatchall + * optional, make this address a catchall address, default: no + * @param int $customerid + * optional, required when called as admin/reseller + * * @access admin, customer * @throws Exception * @return array @@ -78,11 +87,11 @@ class Emails extends ApiCommand implements ResourceEntity // get needed customer info to reduce the email-address-counter by one if ($this->isAdmin()) { // get customer id - $customer_id = $this->getParam('customer_id'); + $customer_id = $this->getParam('customerid'); $customer = $this->apiCall('Customers.get', array( 'id' => $customer_id )); - // check whether the customer has enough resources to get the ftp-user added + // check whether the customer has enough resources to get the mail-address added if ($customer['emails_used'] >= $customer['emails'] && $customer['emails'] != '-1') { throw new Exception("Customer has no more resources available", 406); } @@ -148,7 +157,7 @@ class Emails extends ApiCommand implements ResourceEntity * return a email-address entry by either id or email-address * * @param int $id - * optional, the customer-id + * optional, the email-address-id * @param string $emailaddr * optional, the email-address * From 81bd9d945d46e746b57263bb2922506707c32159 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sun, 11 Mar 2018 10:26:05 +0100 Subject: [PATCH 132/746] fix parameter customerid for Emails.add() in unit-test Signed-off-by: Michael Kaufmann (d00p) --- tests/Emails/EmailsTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Emails/EmailsTest.php b/tests/Emails/EmailsTest.php index 5a07bc1e..4ba60987 100644 --- a/tests/Emails/EmailsTest.php +++ b/tests/Emails/EmailsTest.php @@ -34,7 +34,7 @@ class MailsTest extends TestCase 'email_part' => 'catchall', 'domain' => 'test2.local', 'iscatchall' => 1, - 'customer_id' => 1 + 'customerid' => 1 ]; $json_result = Emails::getLocal($admin_userdata, $data)->add(); $result = json_decode($json_result, true)['data']; From 349fa7a761029adbaadbb872d1ee9962797d4e9f Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 12 Mar 2018 10:22:50 +0100 Subject: [PATCH 133/746] avoid possible undefined index if no issuer-organisation is set in a certificate Signed-off-by: Michael Kaufmann (d00p) --- templates/Sparkle/ssl_certificates/certs_cert.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/Sparkle/ssl_certificates/certs_cert.tpl b/templates/Sparkle/ssl_certificates/certs_cert.tpl index e383f54f..f0755efc 100644 --- a/templates/Sparkle/ssl_certificates/certs_cert.tpl +++ b/templates/Sparkle/ssl_certificates/certs_cert.tpl @@ -8,7 +8,7 @@
    SAN: {$san_list}
    - {$cert_data['issuer']['O']} + {$cert_data['issuer']['O']} {$validFrom} From c920bf6a63edcc2beed16bcca7187c705df42bad Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 12 Mar 2018 10:45:46 +0100 Subject: [PATCH 134/746] some code-reduction Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/abstract.ApiCommand.php | 185 ++++-------------- lib/classes/api/abstract.ApiParameter.php | 180 +++++++++++++++++ .../api/commands/class.EmailForwarders.php | 36 +--- lib/classes/api/commands/class.Emails.php | 45 +---- lib/classes/api/commands/class.Ftps.php | 36 +--- lib/classes/api/commands/class.Mysqls.php | 53 ++--- lib/classes/api/commands/class.SubDomains.php | 61 ++---- 7 files changed, 261 insertions(+), 335 deletions(-) create mode 100644 lib/classes/api/abstract.ApiParameter.php diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index da3f0430..a0c66726 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -15,7 +15,7 @@ * @since 0.10.0 * */ -abstract class ApiCommand +abstract class ApiCommand extends ApiParameter { /** @@ -53,13 +53,6 @@ abstract class ApiCommand */ private $mail = null; - /** - * array of parameters passed to the command - * - * @var array - */ - private $cmd_params = null; - /** * language strings array * @@ -103,14 +96,12 @@ abstract class ApiCommand { global $lng, $version, $dbversion, $branding; + parent::__construct($params); + $this->version = $version; $this->dbversion = $dbversion; $this->branding = $branding; - if (! is_null($params)) { - $params = $this->trimArray($params); - } - $this->cmd_params = $params; if (! empty($header)) { $this->readUserData($header); } elseif (! empty($userinfo)) { @@ -268,97 +259,6 @@ abstract class ApiCommand return $this->user_data; } - /** - * get specific parameter from the parameterlist; - * check for existence and != empty if needed. - * Maybe more in the future - * - * @param string $param - * parameter to get out of the request-parameter list - * @param bool $optional - * default: false - * @param mixed $default - * value which is returned if optional=true and param is not set - * - * @throws Exception - * @return mixed - */ - protected function getParam($param = null, $optional = false, $default = '') - { - // does it exist? - if (! isset($this->cmd_params[$param])) { - if ($optional === false) { - // get module + function for better error-messages - $inmod = $this->getModFunctionString(); - throw new Exception('Requested parameter "' . $param . '" could not be found for "' . $inmod . '"', 404); - } - return $default; - } - // is it empty? - test really on string, as value 0 is being seen as empty by php - if ($this->cmd_params[$param] === "") { - if ($optional === false) { - // get module + function for better error-messages - $inmod = $this->getModFunctionString(); - throw new Exception('Requested parameter "' . $param . '" is empty where it should not be for "' . $inmod . '"', 406); - } - return ''; - } - // everything else is fine - return $this->cmd_params[$param]; - } - - /** - * get specific parameter which also has and unlimited-field - * - * @param string $param - * parameter to get out of the request-parameter list - * @param string $ul_field - * parameter to get out of the request-parameter list - * @param bool $optional - * default: false - * @param mixed $default - * value which is returned if optional=true and param is not set - * - * @return mixed - */ - protected function getUlParam($param = null, $ul_field = null, $optional = false, $default = 0) - { - $param_value = intval_ressource($this->getParam($param, $optional, $default)); - $ul_field_value = $this->getParam($ul_field, true, 0); - if ($ul_field_value != 0) { - $param_value = - 1; - } - return $param_value; - } - - /** - * update value of parameter - * - * @param string $param - * @param mixed $value - * - * @throws Exception - * @return boolean - */ - protected function updateParam($param, $value = null) - { - if (isset($this->cmd_params[$param])) { - $this->cmd_params[$param] = $value; - return true; - } - throw new Exception("Unable to update parameter '" . $param . "' as it does not exist", 500); - } - - /** - * return list of all parameters - * - * @return array - */ - protected function getParamList() - { - return $this->cmd_params; - } - /** * return logger instance * @@ -471,6 +371,38 @@ abstract class ApiCommand return $customer_ids; } + /** + * returns an array of customer data for customer, or by customer-id/loginname for admin/reseller + * + * @param int $customerid + * optional, required if loginname is empty + * @param string $loginname + * optional, required of customerid is empty + * @param string $customer_resource_check + * optional, when called as admin, check the resources of the target customer + * + * @throws Exception + * @return array + */ + protected function getCustomerData($customer_resource_check = '') + { + if ($this->isAdmin()) { + $customerid = $this->getParam('customerid', true, 0); + $loginname = $this->getParam('loginname', true, ''); + $customer = $this->apiCall('Customers.get', array( + 'id' => $customerid, + 'loginname' => $loginname + )); + // check whether the customer has enough resources + if (! empty($customer_resource_check) && $customer[$customer_resource_check . '_used'] >= $customer[$customer_resource_check] && $customer[$customer_resource_check] != '-1') { + throw new Exception("Customer has no more resources available", 406); + } + } else { + $customer = $this->getUserData(); + } + return $customer; + } + /** * increase/decrease a resource field for customers/admins * @@ -493,35 +425,6 @@ abstract class ApiCommand ), true, true); } - /** - * returns "module::function()" for better error-messages (missing parameter etc.) - * makes debugging a whole lot more comfortable - * - * @return string - */ - private function getModFunctionString() - { - $_class = get_called_class(); - $level = 2; - if (version_compare(PHP_VERSION, "5.4.0", "<")) { - $trace = debug_backtrace(); - } else { - $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); - } - while (true) { - $class = $trace[$level]['class']; - $func = $trace[$level]['function']; - if ($class != $_class) { - $level ++; - if ($level > 5) { - break; - } - continue; - } - return $class . ':' . $func; - } - } - /** * read user data from database by api-request-header fields * @@ -563,22 +466,4 @@ abstract class ApiCommand } throw new Exception("Invalid API credentials", 400); } - - /** - * run 'trim' function on an array recursively - * - * @param array $input - * - * @return array - */ - private function trimArray($input) - { - if (! is_array($input)) { - return trim($input); - } - return array_map(array( - $this, - 'trimArray' - ), $input); - } } diff --git a/lib/classes/api/abstract.ApiParameter.php b/lib/classes/api/abstract.ApiParameter.php new file mode 100644 index 00000000..749b4958 --- /dev/null +++ b/lib/classes/api/abstract.ApiParameter.php @@ -0,0 +1,180 @@ + (2010-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package API + * @since 0.10.0 + * + */ +abstract class ApiParameter +{ + + /** + * array of parameters passed to the command + * + * @var array + */ + private $cmd_params = null; + + /** + * + * @param array $params + * optional, array of parameters (var=>value) for the command + * + * @throws Exception + */ + public function __construct($params = null) + { + if (! is_null($params)) { + $params = $this->trimArray($params); + } + $this->cmd_params = $params; + } + + /** + * get specific parameter from the parameterlist; + * check for existence and != empty if needed. + * Maybe more in the future + * + * @param string $param + * parameter to get out of the request-parameter list + * @param bool $optional + * default: false + * @param mixed $default + * value which is returned if optional=true and param is not set + * + * @throws Exception + * @return mixed + */ + protected function getParam($param = null, $optional = false, $default = '') + { + // does it exist? + if (! isset($this->cmd_params[$param])) { + if ($optional === false) { + // get module + function for better error-messages + $inmod = $this->getModFunctionString(); + throw new Exception('Requested parameter "' . $param . '" could not be found for "' . $inmod . '"', 404); + } + return $default; + } + // is it empty? - test really on string, as value 0 is being seen as empty by php + if ($this->cmd_params[$param] === "") { + if ($optional === false) { + // get module + function for better error-messages + $inmod = $this->getModFunctionString(); + throw new Exception('Requested parameter "' . $param . '" is empty where it should not be for "' . $inmod . '"', 406); + } + return ''; + } + // everything else is fine + return $this->cmd_params[$param]; + } + + /** + * get specific parameter which also has and unlimited-field + * + * @param string $param + * parameter to get out of the request-parameter list + * @param string $ul_field + * parameter to get out of the request-parameter list + * @param bool $optional + * default: false + * @param mixed $default + * value which is returned if optional=true and param is not set + * + * @return mixed + */ + protected function getUlParam($param = null, $ul_field = null, $optional = false, $default = 0) + { + $param_value = intval_ressource($this->getParam($param, $optional, $default)); + $ul_field_value = $this->getParam($ul_field, true, 0); + if ($ul_field_value != 0) { + $param_value = - 1; + } + return $param_value; + } + + /** + * update value of parameter + * + * @param string $param + * @param mixed $value + * + * @throws Exception + * @return boolean + */ + protected function updateParam($param, $value = null) + { + if (isset($this->cmd_params[$param])) { + $this->cmd_params[$param] = $value; + return true; + } + throw new Exception("Unable to update parameter '" . $param . "' as it does not exist", 500); + } + + /** + * return list of all parameters + * + * @return array + */ + protected function getParamList() + { + return $this->cmd_params; + } + + /** + * returns "module::function()" for better error-messages (missing parameter etc.) + * makes debugging a whole lot more comfortable + * + * @return string + */ + private function getModFunctionString() + { + $_class = get_called_class(); + $level = 2; + if (version_compare(PHP_VERSION, "5.4.0", "<")) { + $trace = debug_backtrace(); + } else { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + } + while (true) { + $class = $trace[$level]['class']; + $func = $trace[$level]['function']; + if ($class != $_class) { + $level ++; + if ($level > 5) { + break; + } + continue; + } + return $class . ':' . $func; + } + } + + /** + * run 'trim' function on an array recursively + * + * @param array $input + * + * @return array + */ + private function trimArray($input) + { + if (! is_array($input)) { + return trim($input); + } + return array_map(array( + $this, + 'trimArray' + ), $input); + } +} diff --git a/lib/classes/api/commands/class.EmailForwarders.php b/lib/classes/api/commands/class.EmailForwarders.php index f4658c9b..ee95f349 100644 --- a/lib/classes/api/commands/class.EmailForwarders.php +++ b/lib/classes/api/commands/class.EmailForwarders.php @@ -69,21 +69,8 @@ class EmailForwarders extends ApiCommand implements ResourceEntity standard_error('destinationalreadyexist', $destination, true); } - // get needed customer info to reduce the email-address-counter by one - if ($this->isAdmin()) { - // get customer id - $customer_id = $this->getParam('customerid'); - $customer = $this->apiCall('Customers.get', array( - 'id' => $customer_id - )); - // check whether the customer has enough resources to get the mail-forwarder added - if ($customer['email_forwarders_used'] >= $customer['email_forwarders'] && $customer['email_forwarders'] != '-1') { - throw new Exception("Customer has no more resources available", 406); - } - } else { - $customer_id = $this->getUserDetail('customerid'); - $customer = $this->getUserData(); - } + // get needed customer info to reduce the email-forwarder-counter by one + $customer = $this->getCustomerData('email_forwarders'); // add destination to address $result['destination'] .= ' ' . $destination; @@ -93,13 +80,13 @@ class EmailForwarders extends ApiCommand implements ResourceEntity "); $params = array( "dest" => makeCorrectDestination($result['destination']), - "cid" => $customer_id, + "cid" => $customer['customerid'], "id" => $id ); Database::pexecute($stmt, $params, true, true); // update customer usage - Customers::increaseUsage($customer_id, 'email_forwarders_used'); + Customers::increaseUsage($customer['customerid'], 'email_forwarders_used'); // update admin usage Admins::increaseUsage($customer['adminid'], 'email_forwarders_used'); @@ -169,17 +156,8 @@ class EmailForwarders extends ApiCommand implements ResourceEntity $result['destination'] = explode(' ', $result['destination']); if (isset($result['destination'][$forwarderid]) && $result['email'] != $result['destination'][$forwarderid]) { - // get needed customer info to reduce the email-address-counter by one - if ($this->isAdmin()) { - // get customer id - $customer_id = $this->getParam('customer_id'); - $customer = $this->apiCall('Customers.get', array( - 'id' => $customer_id - )); - } else { - $customer_id = $this->getUserDetail('customerid'); - $customer = $this->getUserData(); - } + // get needed customer info to reduce the email-forwarder-counter by one + $customer = $this->getCustomerData(); // get specific forwarder $forwarder = $result['destination'][$forwarderid]; @@ -207,7 +185,7 @@ class EmailForwarders extends ApiCommand implements ResourceEntity )); // update customer usage - Customers::decreaseUsage($customer_id, 'email_forwarders_used'); + Customers::decreaseUsage($customer['customerid'], 'email_forwarders_used'); // update admin usage Admins::decreaseUsage($customer['adminid'], 'email_forwarders_used'); diff --git a/lib/classes/api/commands/class.Emails.php b/lib/classes/api/commands/class.Emails.php index b72bfbeb..9fe3273e 100644 --- a/lib/classes/api/commands/class.Emails.php +++ b/lib/classes/api/commands/class.Emails.php @@ -85,20 +85,7 @@ class Emails extends ApiCommand implements ResourceEntity } // get needed customer info to reduce the email-address-counter by one - if ($this->isAdmin()) { - // get customer id - $customer_id = $this->getParam('customerid'); - $customer = $this->apiCall('Customers.get', array( - 'id' => $customer_id - )); - // check whether the customer has enough resources to get the mail-address added - if ($customer['emails_used'] >= $customer['emails'] && $customer['emails'] != '-1') { - throw new Exception("Customer has no more resources available", 406); - } - } else { - $customer_id = $this->getUserDetail('customerid'); - $customer = $this->getUserData(); - } + $customer = $this->getCustomerData('emails'); // duplicate check $stmt = Database::prepare(" @@ -138,7 +125,7 @@ class Emails extends ApiCommand implements ResourceEntity $address_id = Database::lastInsertId(); // update customer usage - Customers::increaseUsage($customer_id, 'emails_used'); + Customers::increaseUsage($customer['customerid'], 'emails_used'); // update admin usage Admins::increaseUsage($customer['adminid'], 'emails_used'); @@ -236,16 +223,7 @@ class Emails extends ApiCommand implements ResourceEntity $iscatchall = $this->getParam('iscatchall', true, $result['iscatchall']); // get needed customer info to reduce the email-address-counter by one - if ($this->isAdmin()) { - // get customer id - $customer_id = $this->getParam('customerid'); - $customer = $this->apiCall('Customers.get', array( - 'id' => $customer_id - )); - } else { - $customer_id = $this->getUserDetail('customerid'); - $customer = $this->getUserData(); - } + $customer = $this->getCustomerData(); // check for catchall-flag if ($iscatchall) { @@ -348,16 +326,7 @@ class Emails extends ApiCommand implements ResourceEntity $delete_userfiles = $this->getParam('delete_userfiles', true, 0); // get needed customer info to reduce the email-address-counter by one - if ($this->isAdmin()) { - // get customer id - $customer_id = $this->getParam('customerid'); - $customer = $this->apiCall('Customers.get', array( - 'id' => $customer_id - )); - } else { - $customer_id = $this->getUserDetail('customerid'); - $customer = $this->getUserData(); - } + $customer = $this->getCustomerData(); // check for forwarders $number_forwarders = 0; @@ -373,7 +342,7 @@ class Emails extends ApiCommand implements ResourceEntity if (Settings::Get('system.mail_quota_enabled') == 1) { $stmt = Database::prepare("SELECT `quota` FROM `" . TABLE_MAIL_USERS . "` WHERE `customerid`= :customerid AND `id`= :id"); $res_quota = Database::pexecute_first($stmt, array( - "customerid" => $customer_id, + "customerid" => $customer['customerid'], "id" => $result['popaccountid'] ), true, true); Customers::decreaseUsage($customer['customerid'], 'email_quota_used', '', $res_quota['quota']); @@ -383,7 +352,7 @@ class Emails extends ApiCommand implements ResourceEntity // delete account $stmt = Database::prepare("DELETE FROM `" . TABLE_MAIL_USERS . "` WHERE `customerid`= :customerid AND `id`= :id"); Database::pexecute($stmt, array( - "customerid" => $customer_id, + "customerid" => $customer['customerid'], "id" => $result['popaccountid'] ), true, true); Customers::decreaseUsage($customer['customerid'], 'email_accounts_used'); @@ -398,7 +367,7 @@ class Emails extends ApiCommand implements ResourceEntity // delete address $stmt = Database::prepare("DELETE FROM `" . TABLE_MAIL_VIRTUAL . "` WHERE `customerid`= :customerid AND `id`= :id"); Database::pexecute($stmt, array( - "customerid" => $customer_id, + "customerid" => $customer['customerid'], "id" => $id ), true, true); Customers::decreaseUsage($customer['customerid'], 'emails_used'); diff --git a/lib/classes/api/commands/class.Ftps.php b/lib/classes/api/commands/class.Ftps.php index 56ef099b..09c4f193 100644 --- a/lib/classes/api/commands/class.Ftps.php +++ b/lib/classes/api/commands/class.Ftps.php @@ -83,20 +83,7 @@ class Ftps extends ApiCommand implements ResourceEntity $params = array(); // get needed customer info to reduce the ftp-user-counter by one - if ($this->isAdmin()) { - // get customer id - $customer_id = $this->getParam('customer_id'); - $customer = $this->apiCall('Customers.get', array( - 'id' => $customer_id - )); - // check whether the customer has enough resources to get the ftp-user added - if ($customer['ftps_used'] >= $customer['ftps'] && $customer['ftps'] != '-1') { - throw new Exception("Customer has no more resources available", 406); - } - } else { - $customer_id = $this->getUserDetail('customerid'); - $customer = $this->getUserData(); - } + $customer = $this->getCustomerData('ftps'); if ($sendinfomail != 1) { $sendinfomail = 0; @@ -114,7 +101,7 @@ class Ftps extends ApiCommand implements ResourceEntity AND `customerid` = :customerid"); $ftpdomain_check = Database::pexecute_first($ftpdomain_check_stmt, array( "domain" => $ftpdomain, - "customerid" => $customer_id + "customerid" => $customer['customerid'] ), true, true); if ($ftpdomain_check && $ftpdomain_check['domain'] != $ftpdomain) { @@ -144,7 +131,7 @@ class Ftps extends ApiCommand implements ResourceEntity (`customerid`, `username`, `description`, `password`, `homedir`, `login_enabled`, `uid`, `gid`, `shell`) VALUES (:customerid, :username, :description, :password, :homedir, 'y', :guid, :guid, :shell)"); $params = array( - "customerid" => $customer_id, + "customerid" => $customer['customerid'], "username" => $username, "description" => $description, "password" => $cryptPassword, @@ -179,14 +166,14 @@ class Ftps extends ApiCommand implements ResourceEntity "); $params = array( "username" => $username, - "customerid" => $customer_id, + "customerid" => $customer['customerid'], "guid" => $customer['guid'] ); Database::pexecute($stmt, $params, true, true); // update customer usage - Customers::increaseUsage($customer_id, 'ftps_used'); - Customers::increaseUsage($customer_id, 'ftp_lastaccountnumber'); + Customers::increaseUsage($customer['customerid'], 'ftps_used'); + Customers::increaseUsage($customer['customerid'], 'ftp_lastaccountnumber'); // update admin usage Admins::increaseUsage($customer['adminid'], 'ftps_used'); @@ -360,16 +347,7 @@ class Ftps extends ApiCommand implements ResourceEntity } // get needed customer info to reduce the ftp-user-counter by one - if ($this->isAdmin()) { - // get customer id - $customer_id = $this->getParam('customer_id'); - $customer = $this->apiCall('Customers.get', array( - 'id' => $customer_id - )); - } else { - $customer_id = $this->getUserDetail('customerid'); - $customer = $this->getUserData(); - } + $customer = $this->getCustomerData(); // password update? if ($password != '') { diff --git a/lib/classes/api/commands/class.Mysqls.php b/lib/classes/api/commands/class.Mysqls.php index dfb1cd55..2954660b 100644 --- a/lib/classes/api/commands/class.Mysqls.php +++ b/lib/classes/api/commands/class.Mysqls.php @@ -68,19 +68,7 @@ class Mysqls extends ApiCommand implements ResourceEntity } // get needed customer info to reduce the mysql-usage-counter by one - if ($this->isAdmin()) { - // get customer id - $customer_id = $this->getParam('customer_id'); - $customer = $this->apiCall('Customers.get', array( - 'id' => $customer_id - )); - // check whether the customer has enough resources to get the database added - if ($customer['mysqls_used'] >= $customer['mysqls'] && $customer['mysqls'] != '-1') { - throw new Exception("Customer has no more resources available", 406); - } - } else { - $customer_id = $this->getUserDetail('customerid'); - } + $customer = $this->getCustomerData('mysqls'); $newdb_params = array( 'loginname' => ($this->isAdmin() ? $customer['loginname'] : $this->getUserDetail('loginname')), @@ -105,7 +93,7 @@ class Mysqls extends ApiCommand implements ResourceEntity `dbserver` = :dbserver "); $params = array( - "customerid" => ($this->isAdmin() ? $customer['customerid'] : $this->getUserDetail('customerid')), + "customerid" => $customer['customerid'], "databasename" => $username, "description" => $databasedescription, "dbserver" => $dbserver @@ -115,8 +103,8 @@ class Mysqls extends ApiCommand implements ResourceEntity $params['id'] = $databaseid; // update customer usage - Customers::increaseUsage(($this->isAdmin() ? $customer['customerid'] : $this->getUserDetail('customerid')), 'mysqls_used'); - Customers::increaseUsage(($this->isAdmin() ? $customer['customerid'] : $this->getUserDetail('customerid')), 'mysql_lastaccountnumber'); + Customers::increaseUsage($customer['customerid'], 'mysqls_used'); + Customers::increaseUsage($customer['customerid'], 'mysql_lastaccountnumber'); // update admin usage Admins::increaseUsage($this->getUserDetail('adminid'), 'mysqls_used'); @@ -132,7 +120,7 @@ class Mysqls extends ApiCommand implements ResourceEntity Database::needSqlData(); $sql_root = Database::getSqlData(); Database::needRoot(false); - $userinfo = ($this->isAdmin() ? $customer : $this->getUserData()); + $userinfo = $customer; $replace_arr = array( 'SALUTATION' => getCorrectUserSalutation($userinfo), @@ -351,19 +339,7 @@ class Mysqls extends ApiCommand implements ResourceEntity } // get needed customer info to reduce the mysql-usage-counter by one - if ($this->isAdmin()) { - // get customer id - $customer_id = $this->getParam('customer_id'); - $customer = $this->apiCall('Customers.get', array( - 'id' => $customer_id - )); - // check whether the customer has enough resources to get the database added - if ($customer['mysqls_used'] >= $customer['mysqls'] && $customer['mysqls'] != '-1') { - throw new Exception("Customer has no more resources available", 406); - } - } else { - $customer_id = $this->getUserDetail('customerid'); - } + $customer = $this->getCustomerData(); if ($password != '') { // validate password @@ -398,7 +374,7 @@ class Mysqls extends ApiCommand implements ResourceEntity "); $params = array( "desc" => $databasedescription, - "customerid" => ($this->isAdmin() ? $customer['customerid'] : $this->getUserDetail('customerid')), + "customerid" => $customer['customerid'], "id" => $id ); Database::pexecute($stmt, $params, true, true); @@ -519,19 +495,12 @@ class Mysqls extends ApiCommand implements ResourceEntity ), true, true); // get needed customer info to reduce the mysql-usage-counter by one - if ($this->isAdmin()) { - $customer = $this->apiCall('Customers.get', array( - 'id' => $result['customerid'] - )); - $mysql_used = $customer['mysqls_used']; - $customer_id = $customer['customer_id']; - } else { - $mysql_used = $this->getUserDetail('mysqls_used'); - $customer_id = $this->getUserDetail('customerid'); - } + $customer = $this->getCustomerData(); + $mysql_used = $customer['mysqls_used']; + // reduce mysql-usage-counter $resetaccnumber = ($mysql_used == '1') ? " , `mysql_lastaccountnumber` = '0' " : ''; - Customers::decreaseUsage($customer_id, 'mysqls_used', $resetaccnumber); + Customers::decreaseUsage($customer['customerid'], 'mysqls_used', $resetaccnumber); // update admin usage Admins::decreaseUsage(($this->isAdmin() ? $customer['adminid'] : $this->getUserDetail('adminid')), 'mysqls_used'); diff --git a/lib/classes/api/commands/class.SubDomains.php b/lib/classes/api/commands/class.SubDomains.php index ba257882..306d7ae9 100644 --- a/lib/classes/api/commands/class.SubDomains.php +++ b/lib/classes/api/commands/class.SubDomains.php @@ -78,20 +78,7 @@ class SubDomains extends ApiCommand implements ResourceEntity } // get needed customer info to reduce the subdomain-usage-counter by one - if ($this->isAdmin()) { - // get customer id - $customer_id = $this->getParam('customer_id'); - $customer = $this->apiCall('Customers.get', array( - 'id' => $customer_id - )); - // check whether the customer has enough resources to get the subdomain added - if ($customer['subdomains_used'] >= $customer['subdomains'] && $customer['subdomains'] != '-1') { - throw new Exception("Customer has no more resources available", 406); - } - } else { - $customer_id = $this->getUserDetail('customerid'); - $customer = $this->getUserData(); - } + $customer = $this->getCustomerData('subdomains'); // validation if (substr($subdomain, 0, 4) == 'xn--') { @@ -127,7 +114,7 @@ class SubDomains extends ApiCommand implements ResourceEntity "); $completedomain_check = Database::pexecute_first($completedomain_stmt, array( "domain" => $completedomain, - "customerid" => $customer_id + "customerid" => $customer['customerid'] ), true, true); if ($completedomain_check) { @@ -153,7 +140,7 @@ class SubDomains extends ApiCommand implements ResourceEntity "); $aliasdomain_check = Database::pexecute_first($aliasdomain_stmt, array( "id" => $aliasdomain, - "customerid" => $customer_id + "customerid" => $customer['customerid'] ), true, true); if ($aliasdomain_check['id'] != $aliasdomain) { standard_error('domainisaliasorothercustomer', '', true); @@ -461,20 +448,7 @@ class SubDomains extends ApiCommand implements ResourceEntity } // get needed customer info to reduce the subdomain-usage-counter by one - if ($this->isAdmin()) { - // get customer id - $customer_id = $this->getParam('customer_id'); - $customer = $this->apiCall('Customers.get', array( - 'id' => $customer_id - )); - // check whether the customer has enough resources to get the subdomain added - if ($customer['subdomains_used'] >= $customer['subdomains'] && $customer['subdomains'] != '-1') { - throw new Exception("Customer has no more resources available", 406); - } - } else { - $customer_id = $this->getUserDetail('customerid'); - $customer = $this->getUserData(); - } + $customer = $this->getCustomerData(); $alias_stmt = Database::prepare("SELECT COUNT(`id`) AS count FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `aliasdomain`= :aliasdomain"); $alias_check = Database::pexecute_first($alias_stmt, array( @@ -494,7 +468,7 @@ class SubDomains extends ApiCommand implements ResourceEntity "); $aliasdomain_check = Database::pexecute_first($aliasdomain_stmt, array( "id" => $aliasdomain, - "customerid" => $customer_id + "customerid" => $customer['customerid'] ), true, true); if ($aliasdomain_check['id'] != $aliasdomain) { standard_error('domainisaliasorothercustomer', '', true); @@ -743,20 +717,13 @@ class SubDomains extends ApiCommand implements ResourceEntity $id = $result['id']; // get needed customer info to reduce the subdomain-usage-counter by one - if ($this->isAdmin()) { - $customer = $this->apiCall('Customers.get', array( - 'id' => $result['customerid'] - )); - $subdomains_used = $customer['subdomains_used']; - $customer_id = $customer['customer_id']; - } else { - if ($result['caneditdomain'] == 0) { - throw new Exception("You cannot edit this resource", 405); - } - $subdomains_used = $this->getUserDetail('subdomains_used'); - $customer_id = $this->getUserDetail('customerid'); + $customer = $this->getCustomerData(); + $subdomains_used = $customer['subdomains_used']; + + if (!$this->isAdmin() && $result['caneditdomain'] == 0) { + throw new Exception("You cannot edit this resource", 405); } - + if ($result['isemaildomain'] == '1') { // check for e-mail addresses $emails_stmt = Database::prepare(" @@ -764,7 +731,7 @@ class SubDomains extends ApiCommand implements ResourceEntity WHERE `customerid` = :customerid AND `domainid` = :domainid "); $emails = Database::pexecute_first($emails_stmt, array( - "customerid" => $customer_id, + "customerid" => $customer['customerid'], "domainid" => $id ), true, true); @@ -780,7 +747,7 @@ class SubDomains extends ApiCommand implements ResourceEntity DELETE FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `customerid` = :customerid AND `id` = :id "); Database::pexecute($stmt, array( - "customerid" => $customer_id, + "customerid" => $customer['customerid'], "id" => $id ), true, true); @@ -825,7 +792,7 @@ class SubDomains extends ApiCommand implements ResourceEntity inserttask('4'); // reduce subdomain-usage-counter - Customers::decreaseUsage($customer_id, 'subdomains_used'); + Customers::decreaseUsage($customer['customerid'], 'subdomains_used'); // update admin usage Admins::decreaseUsage(($this->isAdmin() ? $customer['adminid'] : $this->getUserDetail('adminid')), 'subdomains_used'); From ff611fa8dc6c647e40c05ff2987533a766cb79f3 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 12 Mar 2018 10:57:31 +0100 Subject: [PATCH 135/746] fix parameter for ApiCommand::getCustomerData() Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Ftps.php | 2 +- lib/classes/api/commands/class.SubDomains.php | 2 +- tests/Admins/AdminsTest.php | 1 + tests/Certificates/CertificatesTest.php | 1 + tests/Customers/CustomersTest.php | 1 + tests/Domains/DomainsTest.php | 1 + tests/Emails/EmailsTest.php | 1 + tests/Ftps/FtpsTest.php | 9 +++++---- tests/IpsAndPorts/IpsAndPortsTest.php | 1 + tests/SubDomains/SubDomainsTest.php | 5 +++-- 10 files changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/classes/api/commands/class.Ftps.php b/lib/classes/api/commands/class.Ftps.php index 09c4f193..5fde2ed3 100644 --- a/lib/classes/api/commands/class.Ftps.php +++ b/lib/classes/api/commands/class.Ftps.php @@ -35,7 +35,7 @@ class Ftps extends ApiCommand implements ResourceEntity * optional if customer.ftpatdomain is allowed, specify an username * @param string $ftp_domain * optional if customer.ftpatdomain is allowed, specify a domain (customer must be owner) - * @param int $customer_id + * @param int $customerid * required when called as admin, not needed when called as customer * * @access admin, customer diff --git a/lib/classes/api/commands/class.SubDomains.php b/lib/classes/api/commands/class.SubDomains.php index 306d7ae9..c122a607 100644 --- a/lib/classes/api/commands/class.SubDomains.php +++ b/lib/classes/api/commands/class.SubDomains.php @@ -41,7 +41,7 @@ class SubDomains extends ApiCommand implements ResourceEntity * optional, whether to generate a https-redirect or not, default false; requires SSL to be enabled * @param bool $letsencrypt * optional, whether to generate a Let's Encrypt certificate for this domain, default false; requires SSL to be enabled - * @param int $customer_id + * @param int $customerid * required when called as admin, not needed when called as customer * * @access admin, customer diff --git a/tests/Admins/AdminsTest.php b/tests/Admins/AdminsTest.php index c6a63308..79b110a2 100644 --- a/tests/Admins/AdminsTest.php +++ b/tests/Admins/AdminsTest.php @@ -3,6 +3,7 @@ use PHPUnit\Framework\TestCase; /** * @covers ApiCommand + * @covers ApiParameter * @covers Admins */ class AdminsTest extends TestCase diff --git a/tests/Certificates/CertificatesTest.php b/tests/Certificates/CertificatesTest.php index 85a307a4..b3924916 100644 --- a/tests/Certificates/CertificatesTest.php +++ b/tests/Certificates/CertificatesTest.php @@ -3,6 +3,7 @@ use PHPUnit\Framework\TestCase; /** * @covers ApiCommand + * @covers ApiParameter * @covers Certificates */ class CertificatesTest extends TestCase diff --git a/tests/Customers/CustomersTest.php b/tests/Customers/CustomersTest.php index 3d7592c6..a127178e 100644 --- a/tests/Customers/CustomersTest.php +++ b/tests/Customers/CustomersTest.php @@ -3,6 +3,7 @@ use PHPUnit\Framework\TestCase; /** * @covers ApiCommand + * @covers ApiParameter * @covers Customers */ class CustomersTest extends TestCase diff --git a/tests/Domains/DomainsTest.php b/tests/Domains/DomainsTest.php index 964b29e4..efc41901 100644 --- a/tests/Domains/DomainsTest.php +++ b/tests/Domains/DomainsTest.php @@ -3,6 +3,7 @@ use PHPUnit\Framework\TestCase; /** * @covers ApiCommand + * @covers ApiParameter * @covers SubDomains * @covers Domains */ diff --git a/tests/Emails/EmailsTest.php b/tests/Emails/EmailsTest.php index 4ba60987..77f71c12 100644 --- a/tests/Emails/EmailsTest.php +++ b/tests/Emails/EmailsTest.php @@ -3,6 +3,7 @@ use PHPUnit\Framework\TestCase; /** * @covers ApiCommand + * @covers ApiParameter * @covers Emails */ class MailsTest extends TestCase diff --git a/tests/Ftps/FtpsTest.php b/tests/Ftps/FtpsTest.php index f4dc2ed2..38389a44 100644 --- a/tests/Ftps/FtpsTest.php +++ b/tests/Ftps/FtpsTest.php @@ -3,6 +3,7 @@ use PHPUnit\Framework\TestCase; /** * @covers ApiCommand + * @covers ApiParameter * @covers Ftps */ class FtpsTest extends TestCase @@ -170,8 +171,8 @@ class FtpsTest extends TestCase 'sendinfomail' => 1 ]; - $this->expectExceptionCode(404); - $this->expectExceptionMessage('Requested parameter "customer_id" could not be found for "Ftps:add"'); + $this->expectExceptionCode(406); + $this->expectExceptionMessage('Requested parameter "loginname" is empty where it should not be for "Customers:get"'); $json_result = Ftps::getLocal($admin_userdata, $data)->add(); } @@ -207,7 +208,7 @@ class FtpsTest extends TestCase $customer_userdata = json_decode($json_result, true)['data']; $data = [ 'username' => 'test1ftp1', - 'customer_id' => 1, + 'customerid' => 1, 'ftp_password' => 'h4xXx0r2', 'path' => '/anotherfolder', 'ftp_description' => 'testing3' @@ -229,7 +230,7 @@ class FtpsTest extends TestCase $customer_userdata = json_decode($json_result, true)['data']; $data = [ - 'customer_id' => $customer_userdata['customerid'], + 'customerid' => $customer_userdata['customerid'], 'ftp_password' => 'h4xXx0r', 'path' => '/', 'ftp_description' => 'testing', diff --git a/tests/IpsAndPorts/IpsAndPortsTest.php b/tests/IpsAndPorts/IpsAndPortsTest.php index 94d32f27..951b22a1 100644 --- a/tests/IpsAndPorts/IpsAndPortsTest.php +++ b/tests/IpsAndPorts/IpsAndPortsTest.php @@ -3,6 +3,7 @@ use PHPUnit\Framework\TestCase; /** * @covers ApiCommand + * @covers ApiParameter * @covers IpsAndPorts */ class IpsAndPortsTest extends TestCase diff --git a/tests/SubDomains/SubDomainsTest.php b/tests/SubDomains/SubDomainsTest.php index f930ca5e..d1aa4af0 100644 --- a/tests/SubDomains/SubDomainsTest.php +++ b/tests/SubDomains/SubDomainsTest.php @@ -3,6 +3,7 @@ use PHPUnit\Framework\TestCase; /** * @covers ApiCommand + * @covers ApiParameter * @covers SubDomains * @covers Domains */ @@ -41,7 +42,7 @@ class SubDomainsTest extends TestCase $data = [ 'subdomain' => 'mysub2', 'domain' => 'test2.local', - 'customer_id' => 1 + 'customerid' => 1 ]; $json_result = SubDomains::getLocal($reseller_userdata, $data)->add(); $result = json_decode($json_result, true)['data']; @@ -161,7 +162,7 @@ class SubDomainsTest extends TestCase 'domainname' => 'mysub.test2.local', 'path' => 'mysub.test2.local', 'isemaildomain' => 1, - 'customer_id' => $customer_userdata['customerid'] + 'customerid' => $customer_userdata['customerid'] ]; $json_result = SubDomains::getLocal($admin_userdata, $data)->update(); $result = json_decode($json_result, true)['data']; From 2e597ef7d944ae8633253fdf5a890c7e8555fa3a Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 12 Mar 2018 11:09:30 +0100 Subject: [PATCH 136/746] remove unused local variables Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.EmailForwarders.php | 13 ++----------- lib/classes/api/commands/class.Emails.php | 1 - lib/classes/api/commands/class.SubDomains.php | 1 - 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/lib/classes/api/commands/class.EmailForwarders.php b/lib/classes/api/commands/class.EmailForwarders.php index ee95f349..ad325e92 100644 --- a/lib/classes/api/commands/class.EmailForwarders.php +++ b/lib/classes/api/commands/class.EmailForwarders.php @@ -158,9 +158,7 @@ class EmailForwarders extends ApiCommand implements ResourceEntity // get needed customer info to reduce the email-forwarder-counter by one $customer = $this->getCustomerData(); - - // get specific forwarder - $forwarder = $result['destination'][$forwarderid]; + // unset it from array unset($result['destination'][$forwarderid]); // rebuild destination-string @@ -176,14 +174,7 @@ class EmailForwarders extends ApiCommand implements ResourceEntity "id" => $id ); Database::pexecute($stmt, $params, true, true); - - $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` - SET `email_forwarders_used` = `email_forwarders_used` - 1 - WHERE `customerid`= :cid"); - Database::pexecute($stmt, array( - "cid" => $userinfo['customerid'] - )); - + // update customer usage Customers::decreaseUsage($customer['customerid'], 'email_forwarders_used'); diff --git a/lib/classes/api/commands/class.Emails.php b/lib/classes/api/commands/class.Emails.php index 9fe3273e..759cc3b4 100644 --- a/lib/classes/api/commands/class.Emails.php +++ b/lib/classes/api/commands/class.Emails.php @@ -122,7 +122,6 @@ class Emails extends ApiCommand implements ResourceEntity "domainid" => $domain_check['id'] ); Database::pexecute($stmt, $params, true, true); - $address_id = Database::lastInsertId(); // update customer usage Customers::increaseUsage($customer['customerid'], 'emails_used'); diff --git a/lib/classes/api/commands/class.SubDomains.php b/lib/classes/api/commands/class.SubDomains.php index c122a607..9159587f 100644 --- a/lib/classes/api/commands/class.SubDomains.php +++ b/lib/classes/api/commands/class.SubDomains.php @@ -718,7 +718,6 @@ class SubDomains extends ApiCommand implements ResourceEntity // get needed customer info to reduce the subdomain-usage-counter by one $customer = $this->getCustomerData(); - $subdomains_used = $customer['subdomains_used']; if (!$this->isAdmin() && $result['caneditdomain'] == 0) { throw new Exception("You cannot edit this resource", 405); From 6fc8cce8f5cf1156fe27b1d49f228c80cbd42a85 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 12 Mar 2018 13:44:44 +0100 Subject: [PATCH 137/746] add EmailAccounts.add; added wrapper function ApiCommand.getMailTemplate() to reduce code-duplication Signed-off-by: Michael Kaufmann (d00p) --- customer_email.php | 210 ++----------------- lib/classes/api/abstract.ApiCommand.php | 28 +++ lib/classes/api/commands/class.Customers.php | 44 ++-- lib/classes/api/commands/class.Emails.php | 7 +- lib/classes/api/commands/class.Ftps.php | 35 +--- lib/classes/api/commands/class.Mysqls.php | 34 +-- 6 files changed, 74 insertions(+), 284 deletions(-) diff --git a/customer_email.php b/customer_email.php index 8be1013c..cf2d043c 100644 --- a/customer_email.php +++ b/customer_email.php @@ -269,210 +269,24 @@ if ($page == 'overview') { } } elseif ($page == 'accounts') { if ($action == 'add' && $id != 0) { - // ensure the int is a positive one - if (isset($_POST['email_quota'])) { - $quota = validate($_POST['email_quota'], 'email_quota', '/^\d+$/', 'vmailquotawrong'); - } - if ($userinfo['email_accounts'] == '-1' || ($userinfo['email_accounts_used'] < $userinfo['email_accounts'])) { - - // check for imap||pop3 == 1, see #1298 - if ($userinfo['imap'] != '1' && $userinfo['pop3'] != '1') { - standard_error('notallowedtouseaccounts'); + try { + $json_result = Emails::getLocal($userinfo, array( + 'id' => $id + ))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - - $stmt = Database::prepare(" - SELECT `id`, `email`, `email_full`, `iscatchall`, `destination`, `customerid`, `popaccountid`, `domainid` - FROM `" . TABLE_MAIL_VIRTUAL . "` - WHERE `customerid`= :cid AND `id`= :id - "); - $result = Database::pexecute_first($stmt, array("cid" => $userinfo['customerid'], "id" => $id)); + $result = json_decode($json_result, true)['data']; if (isset($result['email']) && $result['email'] != '' && $result['popaccountid'] == '0') { if (isset($_POST['send']) && $_POST['send'] == 'send') { - $email_full = $result['email_full']; - $username = $idna_convert->decode($email_full); - $password = validate($_POST['email_password'], 'password'); - $password = validatePassword($password); - - if (Settings::Get('panel.sendalternativemail') == 1) { - $alternative_email = $idna_convert->encode(validate($_POST['alternative_email'], 'alternative_email')); - } else { - $alternative_email = ''; - } - - if (Settings::Get('system.mail_quota_enabled') == 1) { - if ($userinfo['email_quota'] != '-1' && ($quota == 0 || ($quota + $userinfo['email_quota_used']) > $userinfo['email_quota'])) { - standard_error('allocatetoomuchquota', $quota); - } - } else { - $quota = 0; - } - - if ($email_full == '') { - standard_error(array('stringisempty', 'emailadd')); - } - elseif ($password == '' && !(Settings::Get('panel.sendalternativemail') == 1 && validateEmail($alternative_email))) { - standard_error(array('stringisempty', 'mypassword')); - } - elseif ($password == $email_full) { - standard_error('passwordshouldnotbeusername'); - } else { - if ($password == '') { - $password = generatePassword(); - } - - $cryptPassword = makeCryptPassword($password); - - $email_user=substr($email_full,0,strrpos($email_full,"@")); - $email_domain=substr($email_full,strrpos($email_full,"@")+1); - $maildirname=trim(Settings::Get('system.vmail_maildirname')); - // Add trailing slash to Maildir if needed - $maildirpath=$maildirname; - if (!empty($maildirname) && substr($maildirname,-1) != "/") { - $maildirpath.="/"; - } - - $stmt = Database::prepare("INSERT INTO `" . TABLE_MAIL_USERS . "` - (`customerid`, `email`, `username`, " . (Settings::Get('system.mailpwcleartext') == '1' ? '`password`, ' : '') . " `password_enc`, `homedir`, `maildir`, `uid`, `gid`, `domainid`, `postfix`, `quota`, `imap`, `pop3`) ". - "VALUES (:cid, :email, :username, " . (Settings::Get('system.mailpwcleartext') == '1' ? ":password, " : '') . ":password_enc, :homedir, :maildir, :uid, :gid, :domainid, 'y', :quota, :imap, :pop3)" - ); - $params = array( - "cid" => $userinfo['customerid'], - "email" => $email_full, - "username" => $username, - "password_enc" => $cryptPassword, - "homedir" => Settings::Get('system.vmail_homedir'), - "maildir" => $userinfo['loginname'] . '/' . $email_domain . "/" . $email_user . "/" . $maildirpath, - "uid" => Settings::Get('system.vmail_uid'), - "gid" => Settings::Get('system.vmail_gid'), - "domainid" => $result['domainid'], - "quota" => $quota, - "imap" => $userinfo['imap'], - "pop3" => $userinfo['pop3'] - ); - if (Settings::Get('system.mailpwcleartext') == '1') { $params["password"] = $password; } - Database::pexecute($stmt, $params); - - $popaccountid = Database::lastInsertId(); - $result['destination'].= ' ' . $email_full; - $stmt = Database::prepare("UPDATE `" . TABLE_MAIL_VIRTUAL . "` - SET `destination` = :destination, - `popaccountid` = :popaccountid - WHERE `customerid`= :cid - AND `id`= :id" - ); - $params = array( - "destination" => makeCorrectDestination($result['destination']), - "popaccountid" => $popaccountid, - "cid" => $userinfo['customerid'], - "id" => $id - ); - Database::pexecute($stmt, $params); - - $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` - SET `email_accounts_used`=`email_accounts_used`+1, - `email_quota_used`=`email_quota_used`+ :quota - WHERE `customerid`= :cid" - ); - Database::pexecute($stmt, array("quota" => $quota, "cid" => $userinfo['customerid'])); - - $log->logAction(USR_ACTION, LOG_INFO, "added email account for '" . $email_full . "'"); - $replace_arr = array( - 'EMAIL' => $email_full, - 'USERNAME' => $username, - 'PASSWORD' => $password - ); - - $stmt = Database::prepare("SELECT `name`, `email` FROM `" . TABLE_PANEL_ADMINS . "` WHERE `adminid`= :adminid"); - $admin = Database::pexecute_first($stmt, array("adminid" => $userinfo['adminid'])); - - $stmt = Database::prepare("SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` - WHERE `adminid`= :adminid - AND `language`= :lang - AND `templategroup`= 'mails' - AND `varname`= 'pop_success_subject'" - ); - $result = Database::pexecute_first($stmt, array("adminid" => $userinfo['adminid'], "lang" => $userinfo['def_language'])); - $mail_subject = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $lng['mails']['pop_success']['subject']), $replace_arr)); - - $stmt = Database::prepare("SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` - WHERE `adminid`= :adminid - AND `language`= :lang - AND `templategroup`= 'mails' - AND `varname`= 'pop_success_mailbody'" - ); - $result = Database::pexecute_first($stmt, array("adminid" => $userinfo['adminid'], "lang" => $userinfo['def_language'])); - $mail_body = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $lng['mails']['pop_success']['mailbody']), $replace_arr)); - - $_mailerror = false; - try { - $mail->SetFrom($admin['email'], getCorrectUserSalutation($admin)); - $mail->Subject = $mail_subject; - $mail->AltBody = $mail_body; - $mail->MsgHTML(str_replace("\n", "
    ", $mail_body)); - $mail->AddAddress($email_full); - $mail->Send(); - } catch(phpmailerException $e) { - $mailerr_msg = $e->errorMessage(); - $_mailerror = true; - } catch (Exception $e) { - $mailerr_msg = $e->getMessage(); - $_mailerror = true; - } - - if ($_mailerror) { - $log->logAction(USR_ACTION, LOG_ERR, "Error sending mail: " . $mailerr_msg); - standard_error('errorsendingmail', $email_full); - } - - $mail->ClearAddresses(); - - if (validateEmail($alternative_email) && Settings::Get('panel.sendalternativemail') == 1) { - $stmt = Database::prepare("SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` - WHERE `adminid`= :adminid - AND `language`= :lang - AND `templategroup`= 'mails' - AND `varname`= 'pop_success_alternative_subject'" - ); - $result = Database::pexecute_first($stmt, array("adminid" => $userinfo['adminid'], "lang" => $userinfo['def_language'])); - $mail_subject = replace_variables((($result['value'] != '') ? $result['value'] : $lng['mails']['pop_success_alternative']['subject']), $replace_arr); - - $stmt = Database::prepare("SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` - WHERE `adminid`= :adminid - AND `language`= :lang - AND `templategroup`= 'mails' - AND `varname`= 'pop_success_alternative_mailbody'" - ); - $result = Database::pexecute_first($stmt, array("adminid" => $userinfo['adminid'], "lang" => $userinfo['def_language'])); - $mail_body = replace_variables((($result['value'] != '') ? $result['value'] : $lng['mails']['pop_success_alternative']['mailbody']), $replace_arr); - - $_mailerror = false; - try { - $mail->SetFrom($admin['email'], getCorrectUserSalutation($admin)); - $mail->Subject = $mail_subject; - $mail->AltBody = $mail_body; - $mail->MsgHTML(str_replace("\n", "
    ", $mail_body)); - $mail->AddAddress($idna_convert->encode($alternative_email), getCorrectUserSalutation($userinfo)); - $mail->Send(); - } catch(phpmailerException $e) { - $mailerr_msg = $e->errorMessage(); - $_mailerror = true; - } catch (Exception $e) { - $mailerr_msg = $e->getMessage(); - $_mailerror = true; - } - - if ($_mailerror) { - $log->logAction(USR_ACTION, LOG_ERR, "Error sending mail: " . $mailerr_msg); - standard_error(array('errorsendingmail'), $alternative_email); - } - - $mail->ClearAddresses(); - } - - redirectTo($filename, array('page' => 'emails', 'action' => 'edit', 'id' => $id, 's' => $s)); + try { + EmailAccounts::getLocal($userinfo, $_POST)->add(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } + redirectTo($filename, array('page' => 'emails', 'action' => 'edit', 'id' => $id, 's' => $s)); } else { if (checkMailAccDeletionState($result['email_full'])) { diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index a0c66726..77b3c58d 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -425,6 +425,34 @@ abstract class ApiCommand extends ApiParameter ), true, true); } + /** + * return email template content from database or global language file if not found in DB + * + * @param array $customerdata + * @param string $group + * @param string $varname + * @param array $replace_arr + * @param string $default + * + * @return string + */ + protected function getMailTemplate($customerdata = null, $group = null, $varname = null, $replace_arr = array(), $default = "") + { + // get template + $stmt = Database::prepare(" + SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` WHERE `adminid`= :adminid + AND `language`= :lang AND `templategroup`= :group AND `varname`= :var + "); + $result = Database::pexecute_first($stmt, array( + "adminid" => $customerdata['adminid'], + "lang" => $customerdata['def_language'], + "group" => $group, + "var" => $varname + ), true, true); + $content = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $default), $replace_arr)); + return $content; + } + /** * read user data from database by api-request-header fields * diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index 7291dea3..ae02b9c5 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -589,25 +589,17 @@ class Customers extends ApiCommand implements ResourceEntity 'DOMAINNAME' => $_stdsubdomain ); - // Get mail templates from database; the ones from 'admin' are fetched for fallback - $result_stmt = Database::prepare(" - SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` - WHERE `adminid` = :adminid AND `language` = :deflang AND `templategroup` = 'mails' AND `varname` = 'createcustomer_subject'"); - $result = Database::pexecute_first($result_stmt, array( + // get template for mail subject + $mail_subject = $this->getMailTemplate(array( 'adminid' => $this->getUserDetail('adminid'), - 'deflang' => $def_language - ), true, true); - $mail_subject = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $this->lng['mails']['createcustomer']['subject']), $replace_arr)); - - $result_stmt = Database::prepare(" - SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` - WHERE `adminid` = :adminid AND `language` = :deflang AND `templategroup` = 'mails' AND `varname` = 'createcustomer_mailbody'"); - $result = Database::pexecute_first($result_stmt, array( + 'def_language' => $def_language + ), 'mails', 'createcustomer_subject', $replace_arr, $this->lng['mails']['createcustomer']['subject']); + // get template for mail body + $mail_body = $this->getMailTemplate(array( 'adminid' => $this->getUserDetail('adminid'), - 'deflang' => $def_language - ), true, true); - $mail_body = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $this->lng['mails']['createcustomer']['mailbody']), $replace_arr)); - + 'def_language' => $def_language + ), 'mails', 'createcustomer_mailbody', $replace_arr, $this->lng['mails']['createcustomer']['mailbody']); + $_mailerror = false; try { $this->mailer()->Subject = $mail_subject; @@ -665,7 +657,7 @@ class Customers extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); $loginname = $this->getParam('loginname', $ln_optional, ''); - + $result = $this->apiCall('Customers.get', array( 'id' => $id, 'loginname' => $loginname @@ -921,7 +913,7 @@ class Customers extends ApiCommand implements ResourceEntity // At last flush the new privileges $dbm->getManager()->flushPrivileges(); Database::needRoot(false); - + // reactivate/deactivate api-keys $valid_until = $deactivated ? 0 : - 1; $stmt = Database::prepare("UPDATE `" . TABLE_API_KEYS . "` SET `valid_until` = :vu WHERE `customerid` = :id"); @@ -929,7 +921,7 @@ class Customers extends ApiCommand implements ResourceEntity 'id' => $id, 'vu' => $valid_until ), true, true); - + $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] " . ($deactivated ? 'deactivated' : 'reactivated') . " user '" . $result['loginname'] . "'"); inserttask('1'); } @@ -1164,7 +1156,7 @@ class Customers extends ApiCommand implements ResourceEntity } } } - + $result = $this->apiCall('Customers.get', array( 'id' => $result['customerid'] )); @@ -1192,7 +1184,7 @@ class Customers extends ApiCommand implements ResourceEntity $ln_optional = ($id <= 0 ? false : true); $loginname = $this->getParam('loginname', $ln_optional, ''); $delete_userfiles = $this->getParam('delete_userfiles', true, 0); - + $result = $this->apiCall('Customers.get', array( 'id' => $id, 'loginname' => $loginname @@ -1331,7 +1323,7 @@ class Customers extends ApiCommand implements ResourceEntity Database::pexecute($stmt, array( 'id' => $id ), true, true); - + // Delete all waiting "create user" -tasks for this user, #276 // Note: the WHERE selects part of a serialized array, but it should be safe this way $del_stmt = Database::prepare(" @@ -1438,7 +1430,7 @@ class Customers extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); $loginname = $this->getParam('loginname', $ln_optional, ''); - + $result = $this->apiCall('Customers.get', array( 'id' => $id, 'loginname' => $loginname @@ -1482,7 +1474,7 @@ class Customers extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); $loginname = $this->getParam('loginname', $ln_optional, ''); - + $c_result = $this->apiCall('Customers.get', array( 'id' => $id, 'loginname' => $loginname @@ -1530,7 +1522,7 @@ class Customers extends ApiCommand implements ResourceEntity updateCounters(false); $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] moved user '" . $c_result['loginname'] . "' from admin/reseller '" . $c_result['adminname'] . " to admin/reseller '" . $a_result['loginname'] . "'"); - + $result = $this->apiCall('Customers.get', array( 'id' => $c_result['customerid'] )); diff --git a/lib/classes/api/commands/class.Emails.php b/lib/classes/api/commands/class.Emails.php index 759cc3b4..d4a36fc6 100644 --- a/lib/classes/api/commands/class.Emails.php +++ b/lib/classes/api/commands/class.Emails.php @@ -332,8 +332,6 @@ class Emails extends ApiCommand implements ResourceEntity if ($result['destination'] != '') { $result['destination'] = explode(' ', $result['destination']); $number_forwarders = count($result['destination']); - Customers::decreaseUsage($customer['customerid'], 'email_forwarders_used', '', $number_forwarders); - Admins::decreaseUsage($customer['customerid'], 'email_forwarders_used', '', $number_forwarders); } // check whether this address is an account if ($result['popaccountid'] != 0) { @@ -357,8 +355,13 @@ class Emails extends ApiCommand implements ResourceEntity Customers::decreaseUsage($customer['customerid'], 'email_accounts_used'); Admins::decreaseUsage($customer['customerid'], 'email_accounts_used'); $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] deleted email account '" . $result['email_full'] . "'"); + $number_forwarders --; } + // decrease forwarder counter + Customers::decreaseUsage($customer['customerid'], 'email_forwarders_used', '', $number_forwarders); + Admins::decreaseUsage($customer['customerid'], 'email_forwarders_used', '', $number_forwarders); + if ($delete_userfiles) { inserttask('7', $customer['loginname'], $result['email_full']); } diff --git a/lib/classes/api/commands/class.Ftps.php b/lib/classes/api/commands/class.Ftps.php index 5fde2ed3..30276706 100644 --- a/lib/classes/api/commands/class.Ftps.php +++ b/lib/classes/api/commands/class.Ftps.php @@ -189,36 +189,11 @@ class Ftps extends ApiCommand implements ResourceEntity 'USR_PASS' => $password, 'USR_PATH' => makeCorrectDir(str_replace($customer['documentroot'], "/", $path)) ); - - $def_language = $customer['def_language']; - $result_stmt = Database::prepare(" - SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` - WHERE `adminid` = :adminid - AND `language` = :lang - AND `templategroup`='mails' - AND `varname`='new_ftpaccount_by_customer_subject' - "); - Database::pexecute($result_stmt, array( - "adminid" => $customer['adminid'], - "lang" => $def_language - )); - $result = $result_stmt->fetch(PDO::FETCH_ASSOC); - $mail_subject = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $this->lng['mails']['new_ftpaccount_by_customer']['subject']), $replace_arr)); - - $def_language = $customer['def_language']; - $result_stmt = Database::prepare(" - SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` - WHERE `adminid` = :adminid - AND `language` = :lang - AND `templategroup`='mails' - AND `varname`='new_ftpaccount_by_customer_mailbody'"); - Database::pexecute($result_stmt, array( - "adminid" => $customer['adminid'], - "lang" => $def_language - )); - $result = $result_stmt->fetch(PDO::FETCH_ASSOC); - $mail_body = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $this->lng['mails']['new_ftpaccount_by_customer']['mailbody']), $replace_arr)); - + // get template for mail subject + $mail_subject = $this->getMailTemplate($customer, 'mails', 'new_ftpaccount_by_customer_subject', $replace_arr, $this->lng['mails']['new_ftpaccount_by_customer']['subject']); + // get template for mail body + $mail_body = $this->getMailTemplate($customer, 'mails', 'new_ftpaccount_by_customer_mailbody', $replace_arr, $this->lng['mails']['new_ftpaccount_by_customer']['mailbody']); + $_mailerror = false; try { $this->mailer()->Subject = $mail_subject; diff --git a/lib/classes/api/commands/class.Mysqls.php b/lib/classes/api/commands/class.Mysqls.php index 2954660b..a68f57eb 100644 --- a/lib/classes/api/commands/class.Mysqls.php +++ b/lib/classes/api/commands/class.Mysqls.php @@ -131,34 +131,12 @@ class Mysqls extends ApiCommand implements ResourceEntity 'DB_SRV' => $sql_root['host'], 'PMA_URI' => $pma ); - - $def_language = $userinfo['def_language']; - $result_stmt = Database::prepare(" - SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` - WHERE `adminid` = :adminid - AND `language` = :lang - AND `templategroup`='mails' - AND `varname`='new_database_by_customer_subject' - "); - $result = Database::pexecute_first($result_stmt, array( - "adminid" => $userinfo['adminid'], - "lang" => $def_language - ), true, true); - $mail_subject = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $this->lng['mails']['new_database_by_customer']['subject']), $replace_arr)); - - $result_stmt = Database::prepare(" - SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` - WHERE `adminid`= :adminid - AND `language`= :lang - AND `templategroup` = 'mails' - AND `varname` = 'new_database_by_customer_mailbody' - "); - $result = Database::pexecute_first($result_stmt, array( - "adminid" => $userinfo['adminid'], - "lang" => $def_language - )); - $mail_body = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $this->lng['mails']['new_database_by_customer']['mailbody']), $replace_arr)); - + + // get template for mail subject + $mail_subject = $this->getMailTemplate($userinfo, 'mails', 'new_database_by_customer_subject', $replace_arr, $this->lng['mails']['new_database_by_customer']['subject']); + // get template for mail body + $mail_body = $this->getMailTemplate($userinfo, 'mails', 'new_database_by_customer_mailbody', $replace_arr, $this->lng['mails']['new_database_by_customer']['mailbody']); + $_mailerror = false; try { $this->mail->Subject = $mail_subject; From fa7bb53d581cc7a3dfbdd3cb6ada6ca8f47029d8 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 12 Mar 2018 14:13:35 +0100 Subject: [PATCH 138/746] added EmailAccounts-ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- customer_email.php | 191 +++----- .../api/commands/class.EmailAccounts.php | 440 ++++++++++++++++++ .../api/commands/class.EmailForwarders.php | 92 ++-- 3 files changed, 538 insertions(+), 185 deletions(-) create mode 100644 lib/classes/api/commands/class.EmailAccounts.php diff --git a/customer_email.php b/customer_email.php index cf2d043c..4662656a 100644 --- a/customer_email.php +++ b/customer_email.php @@ -279,72 +279,49 @@ if ($page == 'overview') { } $result = json_decode($json_result, true)['data']; - if (isset($result['email']) && $result['email'] != '' && $result['popaccountid'] == '0') { - if (isset($_POST['send']) && $_POST['send'] == 'send') { - try { - EmailAccounts::getLocal($userinfo, $_POST)->add(); - } catch (Exception $e) { - dynamic_error($e->getMessage()); - } - redirectTo($filename, array('page' => 'emails', 'action' => 'edit', 'id' => $id, 's' => $s)); - } else { - - if (checkMailAccDeletionState($result['email_full'])) { - standard_error(array('mailaccistobedeleted'), $result['email_full']); - } - - $result['email_full'] = $idna_convert->decode($result['email_full']); - $result = htmlentities_array($result); - $quota = Settings::Get('system.mail_quota'); - - $account_add_data = include_once dirname(__FILE__) . '/lib/formfields/customer/email/formfield.emails_addaccount.php'; - $account_add_form = htmlform::genHTMLForm($account_add_data); - - $title = $account_add_data['emails_addaccount']['title']; - $image = $account_add_data['emails_addaccount']['image']; - - eval("echo \"" . getTemplate("email/account_add") . "\";"); + if (isset($_POST['send']) && $_POST['send'] == 'send') { + try { + EmailAccounts::getLocal($userinfo, $_POST)->add(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } + redirectTo($filename, array('page' => 'emails', 'action' => 'edit', 'id' => $id, 's' => $s)); + } else { + + if (checkMailAccDeletionState($result['email_full'])) { + standard_error(array('mailaccistobedeleted'), $result['email_full']); + } + + $result['email_full'] = $idna_convert->decode($result['email_full']); + $result = htmlentities_array($result); + $quota = Settings::Get('system.mail_quota'); + + $account_add_data = include_once dirname(__FILE__) . '/lib/formfields/customer/email/formfield.emails_addaccount.php'; + $account_add_form = htmlform::genHTMLForm($account_add_data); + + $title = $account_add_data['emails_addaccount']['title']; + $image = $account_add_data['emails_addaccount']['image']; + + eval("echo \"" . getTemplate("email/account_add") . "\";"); } } else { standard_error(array('allresourcesused', 'allocatetoomuchquota'), $quota); } } elseif ($action == 'changepw' && $id != 0) { - $stmt = Database::prepare("SELECT `id`, `email`, `email_full`, `iscatchall`, `destination`, `customerid`, `popaccountid` FROM `" . TABLE_MAIL_VIRTUAL . "` - WHERE `customerid`= :cid - AND `id`= :id" - ); - $result = Database::pexecute_first($stmt, array("cid" => $userinfo['customerid'], "id" => $id)); + try { + $json_result = Emails::getLocal($userinfo, array('id' => $id))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; if (isset($result['popaccountid']) && $result['popaccountid'] != '') { if (isset($_POST['send']) && $_POST['send'] == 'send') { - $password = validate($_POST['email_password'], 'password'); - - if ($password == '') { - standard_error(array('stringisempty', 'mypassword')); + try { + EmailAccounts::getLocal($userinfo, $_POST)->update(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - elseif ($password == $result['email_full']) { - standard_error('passwordshouldnotbeusername'); - } - - $password = validatePassword($password); - - $log->logAction(USR_ACTION, LOG_INFO, "changed email password for '" . $result['email_full'] . "'"); - $cryptPassword = makeCryptPassword($password); - $stmt = Database::prepare("UPDATE `" . TABLE_MAIL_USERS . "` - SET " . (Settings::Get('system.mailpwcleartext') == '1' ? "`password` = :password, " : '') . " - `password_enc`= :password_enc - WHERE `customerid`= :cid - AND `id`= :id" - ); - $params = array( - "password_enc" => $cryptPassword, - "cid" => $userinfo['customerid'], - "id" => $result['popaccountid'] - ); - if (Settings::Get('system.mailpwcleartext') == '1') { $params["password"] = $password; } - Database::pexecute($stmt, $params); - redirectTo($filename, array('page' => 'emails', 'action' => 'edit', 'id' => $id, 's' => $s)); } else { $result['email_full'] = $idna_convert->decode($result['email_full']); @@ -360,46 +337,21 @@ if ($page == 'overview') { } } } elseif ($action == 'changequota' && Settings::Get('system.mail_quota_enabled') == '1' && $id != 0) { - $stmt = Database::prepare("SELECT `v`.`id`, `v`.`email`, `v`.`email_full`, `v`.`iscatchall`, `v`.`destination`, `v`.`customerid`, `v`.`popaccountid`, `u`.`quota` - FROM `" . TABLE_MAIL_VIRTUAL . "` `v` - LEFT JOIN `" . TABLE_MAIL_USERS . "` `u` - ON(`v`.`popaccountid` = `u`.`id`) - WHERE `v`.`customerid`= :cid - AND `v`.`id`= :id" - ); - $result = Database::pexecute_first($stmt, array("cid" => $userinfo['customerid'], "id" => $id)); + try { + $json_result = Emails::getLocal($userinfo, array('id' => $id))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; if (isset($result['popaccountid']) && $result['popaccountid'] != '') { if (isset($_POST['send']) && $_POST['send'] == 'send') { - $quota = (int)validate($_POST['email_quota'], 'email_quota', '/^\d+$/', 'vmailquotawrong'); - - if ($userinfo['email_quota'] != '-1' && ($quota == 0 || ($quota + $userinfo['email_quota_used'] - $result['quota']) > $userinfo['email_quota'])) { - standard_error('allocatetoomuchquota', $quota); - } else { - $log->logAction(USR_ACTION, LOG_INFO, "updated quota for email address '" . $result['email'] . "' to " . $quota . " MB"); - $stmt = Database::prepare("UPDATE `" . TABLE_MAIL_USERS . "` - SET `quota` = :quota - WHERE `id` = :id - AND `customerid`= :cid" - ); - $params = array( - "quota" => $quota, - "id" => $result['popaccountid'], - "cid" => $userinfo['customerid'] - ); - Database::pexecute($stmt, $params); - - if ($userinfo['email_quota'] != '-1') { - $new_used_quota = $userinfo['email_quota_used'] + ($quota - $result['quota']); - $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` - SET `email_quota_used` = :used - WHERE `customerid` = :cid" - ); - Database::pexecute($stmt, array("used" => $new_used_quota, "cid" => $userinfo['customerid'])); - } - - redirectTo($filename, array('page' => 'emails', 'action' => 'edit', 'id' => $id, 's' => $s)); + try { + EmailAccounts::getLocal($userinfo, $_POST)->update(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } + redirectTo($filename, array('page' => 'emails', 'action' => 'edit', 'id' => $id, 's' => $s)); } else { $result['email_full'] = $idna_convert->decode($result['email_full']); $result = htmlentities_array($result); @@ -414,55 +366,20 @@ if ($page == 'overview') { } } } elseif ($action == 'delete' && $id != 0) { - $stmt = Database::prepare("SELECT `v`.`id`, `v`.`email`, `v`.`email_full`, `v`.`iscatchall`, `v`.`destination`, `v`.`customerid`, `v`.`popaccountid`, `u`.`quota` - FROM `" . TABLE_MAIL_VIRTUAL . "` `v` - LEFT JOIN `" . TABLE_MAIL_USERS . "` `u` - ON(`v`.`popaccountid` = `u`.`id`) - WHERE `v`.`customerid`='" . (int)$userinfo['customerid'] . "' - AND `v`.`id`='" . (int)$id . "'" - ); - $result = Database::pexecute_first($stmt, array("cid" => $userinfo['customerid'], "id" => $id)); + try { + $json_result = Emails::getLocal($userinfo, array('id' => $id))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; if (isset($result['popaccountid']) && $result['popaccountid'] != '') { if (isset($_POST['send']) && $_POST['send'] == 'send') { - $stmt = Database::prepare("DELETE FROM `" . TABLE_MAIL_USERS . "` - WHERE `customerid`= :cid - AND `id`= :id" - ); - Database::pexecute($stmt, array("cid" => $userinfo['customerid'], "id" => $result['popaccountid'])); - $result['destination'] = str_replace($result['email_full'], '', $result['destination']); - - $stmt = Database::prepare("UPDATE `" . TABLE_MAIL_VIRTUAL . "` - SET `destination` = :dest, - `popaccountid` = '0' - WHERE `customerid`= :cid - AND `id`= :id" - ); - $params = array( - "dest" => makeCorrectDestination($result['destination']), - "cid" => $userinfo['customerid'], - "id" => $id - ); - Database::pexecute($stmt, $params); - - if (Settings::Get('system.mail_quota_enabled') == '1' && $userinfo['email_quota'] != '-1') { - $quota = (int)$result['quota']; - } else { - $quota = 0; + try { + EmailAccounts::getLocal($userinfo, $_POST)->delete(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - - if (isset($_POST['delete_userfiles']) && (int)$_POST['delete_userfiles'] == 1) { - inserttask('7', $userinfo['loginname'], $result['email_full']); - } - - $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` - SET `email_accounts_used` = `email_accounts_used` - 1, - `email_quota_used` = `email_quota_used` - :quota - WHERE `customerid`= :cid" - ); - Database::pexecute($stmt, array("quota" => $quota, "cid" => $userinfo['customerid'])); - - $log->logAction(USR_ACTION, LOG_INFO, "deleted email account for '" . $result['email_full'] . "'"); redirectTo($filename, array('page' => 'emails', 'action' => 'edit', 'id' => $id, 's' => $s)); } else { ask_yesno_withcheckbox('email_reallydelete_account', 'admin_customer_alsoremovemail', $filename, array('id' => $id, 'page' => $page, 'action' => $action), $idna_convert->decode($result['email_full'])); diff --git a/lib/classes/api/commands/class.EmailAccounts.php b/lib/classes/api/commands/class.EmailAccounts.php new file mode 100644 index 00000000..aea8e70a --- /dev/null +++ b/lib/classes/api/commands/class.EmailAccounts.php @@ -0,0 +1,440 @@ + (2010-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package API + * @since 0.10.0 + * + */ +class EmailAccounts extends ApiCommand implements ResourceEntity +{ + + public function add() + { + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'email')) { + throw new Exception("You cannot access this resource", 405); + } + + if ($this->getUserDetail('email_accounts_used') < $this->getUserDetail('email_accounts') || $this->getUserDetail('email_accounts') == '-1') { + + // parameter + $id = $this->getParam('id', true, 0); + $ea_optional = ($id <= 0 ? false : true); + $emailaddr = $this->getParam('emailaddr', $ea_optional, ''); + $email_password = $this->getParam('email_password'); + $alternative_email = $this->getParam('alternative_email', true, ''); + $quota = $this->getParam('email_quota', true, 0); + + // validation + $quota = validate($quota, 'email_quota', '/^\d+$/', 'vmailquotawrong', array(), true); + + // get needed customer info to reduce the email-account-counter by one + $customer = $this->getCustomerData('email_accounts'); + + // check for imap||pop3 == 1, see #1298 + if ($customer['imap'] != '1' && $customer['pop3'] != '1') { + standard_error('notallowedtouseaccounts', '', true); + } + + // get email address + $result = $this->apiCall('Emails.get', array( + 'id' => $id, + 'emailaddr' => $emailaddr + )); + $id = $result['id']; + + $email_full = $result['email_full']; + $idna_convert = new idna_convert_wrapper(); + $username = $idna_convert->decode($email_full); + $password = validate($email_password, 'password', '', '', array(), true); + $password = validatePassword($password, true); + + if ($result['popaccountid'] != 0) { + throw new Exception("Email address '" . $email_full . "' has already an account assigned.", 406); + } + + if (checkMailAccDeletionState($email_full)) { + standard_error(array( + 'mailaccistobedeleted' + ), $email_full, true); + } + + // alternative email address to send info to + if (Settings::Get('panel.sendalternativemail') == 1) { + $alternative_email = $idna_convert->encode(validate($alternative_email, 'alternative_email', '', '', array(), true)); + if (! validateEmail($alternative_email)) { + standard_error('emailiswrong', $alternative_email, true); + } + } else { + $alternative_email = ''; + } + + // validate quota if enabled + if (Settings::Get('system.mail_quota_enabled') == 1) { + if ($customer['email_quota'] != '-1' && ($quota == 0 || ($quota + $customer['email_quota_used']) > $customer['email_quota'])) { + standard_error('allocatetoomuchquota', $quota, true); + } + } else { + // disable + $quota = 0; + } + + if ($password == $email_full) { + standard_error('passwordshouldnotbeusername', '', true); + } + + // encrypt the password + $cryptPassword = makeCryptPassword($password); + + $email_user = substr($email_full, 0, strrpos($email_full, "@")); + $email_domain = substr($email_full, strrpos($email_full, "@") + 1); + $maildirname = trim(Settings::Get('system.vmail_maildirname')); + // Add trailing slash to Maildir if needed + $maildirpath = $maildirname; + if (! empty($maildirname) && substr($maildirname, - 1) != "/") { + $maildirpath .= "/"; + } + + // insert data + $stmt = Database::prepare("INSERT INTO `" . TABLE_MAIL_USERS . "` SET + `customerid` = :cid, + `email` = :email, + `username` = :username," . (Settings::Get('system.mailpwcleartext') == '1' ? '`password` = :password, ' : '') . " + `password_enc` = :password_end, + `homedir` = :homedir, + `maildir` = :maildir, + `uid` = :uid, + `gid` = :gid, + `domainid` = :domainid, + `postfix` = 'y', + `quota` = :quota, + `imap` = :imap, + `pop3` = :pop3 + "); + $params = array( + "cid" => $customer['customerid'], + "email" => $email_full, + "username" => $username, + "password_enc" => $cryptPassword, + "homedir" => Settings::Get('system.vmail_homedir'), + "maildir" => $customer['loginname'] . '/' . $email_domain . "/" . $email_user . "/" . $maildirpath, + "uid" => Settings::Get('system.vmail_uid'), + "gid" => Settings::Get('system.vmail_gid'), + "domainid" => $result['domainid'], + "quota" => $quota, + "imap" => $customer['imap'], + "pop3" => $customer['pop3'] + ); + if (Settings::Get('system.mailpwcleartext') == '1') { + $params["password"] = $password; + } + Database::pexecute($stmt, $params, true, true); + $popaccountid = Database::lastInsertId(); + + // add email address to its destination field + $result['destination'] .= ' ' . $email_full; + $stmt = Database::prepare(" + UPDATE `" . TABLE_MAIL_VIRTUAL . "` SET `destination` = :destination, `popaccountid` = :popaccountid + WHERE `customerid`= :cid AND `id`= :id + "); + $params = array( + "destination" => makeCorrectDestination($result['destination']), + "popaccountid" => $popaccountid, + "cid" => $customer['customerid'], + "id" => $id + ); + Database::pexecute($stmt, $params, true, true); + + // update customer usage + Customers::increaseUsage($customer['customerid'], 'email_accounts_used'); + Customers::increaseUsage($customer['customerid'], 'email_quota_used', '', $quota); + + // update admin usage + Admins::increaseUsage($customer['adminid'], 'email_accounts_used'); + Admins::increaseUsage($customer['adminid'], 'email_quota_used', '', $quota); + + // replacer array for mail to create account on server + $replace_arr = array( + 'EMAIL' => $email_full, + 'USERNAME' => $username, + 'PASSWORD' => $password + ); + + // get the customers admin + $stmt = Database::prepare("SELECT `name`, `email` FROM `" . TABLE_PANEL_ADMINS . "` WHERE `adminid`= :adminid"); + $admin = Database::pexecute_first($stmt, array( + "adminid" => $customer['adminid'] + )); + + // get template for mail subject + $mail_subject = $this->getMailTemplate($customer, 'mails', 'pop_success_subject', $replace_arr, $this->lng['mails']['pop_success']['subject']); + // get template for mail body + $mail_body = $this->getMailTemplate($customer, 'mails', 'pop_success_mailbody', $replace_arr, $this->lng['mails']['pop_success']['mailbody']); + + $_mailerror = false; + try { + $this->mailer()->SetFrom($admin['email'], getCorrectUserSalutation($admin)); + $this->mailer()->Subject = $mail_subject; + $this->mailer()->AltBody = $mail_body; + $this->mailer()->MsgHTML(str_replace("\n", "
    ", $mail_body)); + $this->mailer()->AddAddress($email_full); + $this->mailer()->Send(); + } catch (phpmailerException $e) { + $mailerr_msg = $e->errorMessage(); + $_mailerror = true; + } catch (Exception $e) { + $mailerr_msg = $e->getMessage(); + $_mailerror = true; + } + + if ($_mailerror) { + $log->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_ERR, "[API] Error sending mail: " . $mailerr_msg); + standard_error('errorsendingmail', $email_full, true); + } + + $this->mailer()->ClearAddresses(); + + // customer wants to send the e-mail to an alternative email address too + if (Settings::Get('panel.sendalternativemail') == 1) { + // get template for mail subject + $mail_subject = $this->getMailTemplate($customer, 'mails', 'pop_success_alternative_subject', $replace_arr, $this->lng['mails']['pop_success_alternative']['subject']); + // get template for mail body + $mail_body = $this->getMailTemplate($customer, 'mails', 'pop_success_alternative_mailbody', $replace_arr, $this->lng['mails']['pop_success_alternative']['mailbody']); + + $_mailerror = false; + try { + $this->mailer()->SetFrom($admin['email'], getCorrectUserSalutation($admin)); + $this->mailer()->Subject = $mail_subject; + $this->mailer()->AltBody = $mail_body; + $this->mailer()->MsgHTML(str_replace("\n", "
    ", $mail_body)); + $this->mailer()->AddAddress($idna_convert->encode($alternative_email), getCorrectUserSalutation($customer)); + $this->mailer()->Send(); + } catch (phpmailerException $e) { + $mailerr_msg = $e->errorMessage(); + $_mailerror = true; + } catch (Exception $e) { + $mailerr_msg = $e->getMessage(); + $_mailerror = true; + } + + if ($_mailerror) { + $log->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_ERR, "[API] Error sending mail: " . $mailerr_msg); + standard_error(array( + 'errorsendingmail' + ), $alternative_email, true); + } + + $this->mailer()->ClearAddresses(); + } + + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] added email account for '" . $result['email_full'] . "'"); + $result = $this->apiCall('Emails.get', array( + 'emailaddr' => $result['email_full'] + )); + return $this->response(200, "successfull", $result); + } + throw new Exception("No more resources available", 406); + } + + public function get() + { + throw new Exception('You cannot directly get an email forwarder. You need to call Emails.get()', 303); + } + + /** + * update email-account entry for given email-address by either id or email-address + * + * @param int $id + * optional, the email-address-id + * @param string $emailaddr + * optional, the email-address to add the forwarder for + * @param int $customerid + * optional, required when called as admin/reseller + * @param int $email_quota + * optional, update quota + * @param string $email_password + * optional, update password + * + * @access admin,customer + * @throws Exception + * @return array + */ + public function update() + { + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'email')) { + throw new Exception("You cannot access this resource", 405); + } + + // parameter + $id = $this->getParam('id', true, 0); + $ea_optional = ($id <= 0 ? false : true); + $emailaddr = $this->getParam('emailaddr', $ea_optional, ''); + + // validation + $result = $this->apiCall('Emails.get', array( + 'id' => $id, + 'emailaddr' => $emailaddr + )); + $id = $result['id']; + + if (empty($result['popaccountid']) || $result['popaccountid'] == 0) { + throw new Exception("Email address '" . $result['email_full'] . "' has no account assigned.", 406); + } + + $email_password = $this->getParam('email_password', true, ''); + $quota = $this->getParam('email_quota', true, $result['quota']); + + // get needed customer info to reduce the email-account-counter by one + $customer = $this->getCustomerData(); + + // validation + $quota = validate($quota, 'email_quota', '/^\d+$/', 'vmailquotawrong', array(), true); + + $upd_query = ""; + $upd_params = array( + "id" => $result['popaccountid'], + "cid" => $customer['customerid'] + ); + if (! empty($password)) { + if ($password == $result['email_full']) { + standard_error('passwordshouldnotbeusername', '', true); + } + $password = validatePassword($password, true); + $cryptPassword = makeCryptPassword($password); + $upd_query .= (Settings::Get('system.mailpwcleartext') == '1' ? "`password` = :password, " : '') . "`password_enc`= :password_enc"; + $upd_params['password_enc'] = $cryptPassword; + if (Settings::Get('system.mailpwcleartext') == '1') { + $upd_params['password'] = $password; + } + } + + if ($quota != $result['quota']) { + if ($customer['email_quota'] != '-1' && ($quota == 0 || ($quota + $customer['email_quota_used'] - $result['quota']) > $customer['email_quota'])) { + standard_error('allocatetoomuchquota', $quota, true); + } + if (! empty($upd_query)) { + $upd_query .= ", "; + } + $upd_query .= "`quota` = :quota"; + $upd_params['quota'] = $quota; + } + + // build update query + if (! empty($upd_query)) { + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_MAIL_USERS . "` SET " . $upd_query . " WHERE `id` = :id AND `customerid`= :cid + "); + Database::pexecute($upd_stmt, $upd_params, true, true); + } + + if ($customer['email_quota'] != '-1') { + Customers::increaseUsage($customer['customerid'], 'email_quota_used', '', ($quota - $result['quota'])); + Admins::increaseUsage($customer['adminid'], 'email_quota_used', '', ($quota - $result['quota'])); + } + + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] updated email account '" . $result['email_full'] . "'"); + $result = $this->apiCall('Emails.get', array( + 'emailaddr' => $result['email_full'] + )); + return $this->response(200, "successfull", $result); + } + + public function listing() + { + throw new Exception('You cannot directly list email forwarders. You need to call Emails.listing()', 303); + } + + /** + * delete email-account entry for given email-address by either id or email-address + * + * @param int $id + * optional, the email-address-id + * @param string $emailaddr + * optional, the email-address to add the forwarder for + * @param bool $delete_userfiles + * optional, default false + * @param int $customerid + * optional, required when called as admin/reseller + * + * @access admin,customer + * @throws Exception + * @return array + */ + public function delete() + { + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'email')) { + throw new Exception("You cannot access this resource", 405); + } + + // parameter + $id = $this->getParam('id', true, 0); + $ea_optional = ($id <= 0 ? false : true); + $emailaddr = $this->getParam('emailaddr', $ea_optional, ''); + $delete_userfiles = $this->getParam('delete_userfiles', true, 0); + + // validation + $result = $this->apiCall('Emails.get', array( + 'id' => $id, + 'emailaddr' => $emailaddr + )); + $id = $result['id']; + + if (empty($result['popaccountid']) || $result['popaccountid'] == 0) { + throw new Exception("Email address '" . $result['email_full'] . "' has no account assigned.", 406); + } + + // get needed customer info to reduce the email-account-counter by one + $customer = $this->getCustomerData(); + + // delete entry + $stmt = Database::prepare(" + DELETE FROM `" . TABLE_MAIL_USERS . "` WHERE `customerid`= :cid AND `id`= :id + "); + Database::pexecute($stmt, array( + "cid" => $customer['customerid'], + "id" => $result['popaccountid'] + ), true, true); + + // update mail-virtual entry + $result['destination'] = str_replace($result['email_full'], '', $result['destination']); + + $stmt = Database::prepare(" + UPDATE `" . TABLE_MAIL_VIRTUAL . "` SET `destination` = :dest, `popaccountid` = '0' WHERE `customerid`= :cid AND `id`= :id + "); + $params = array( + "dest" => makeCorrectDestination($result['destination']), + "cid" => $customer['customerid'], + "id" => $id + ); + Database::pexecute($stmt, $params, true, true); + + if (Settings::Get('system.mail_quota_enabled') == '1' && $customer['email_quota'] != '-1') { + $quota = (int) $result['quota']; + } else { + $quota = 0; + } + + if ($delete_userfiles) { + inserttask('7', $customer['loginname'], $result['email_full']); + } + + // decrease usage for customer + Customers::decreaseUsage($customer['customerid'], 'email_accounts_used'); + Customers::decreaseUsage($customer['customerid'], 'email_quota_used', '', $quota); + // decrease admin usage + Admins::decreaseUsage($customer['adminid'], 'email_accounts_used'); + Admins::decreaseUsage($customer['adminid'], 'email_quota_used', '', $quota); + + $log->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] deleted email account for '" . $result['email_full'] . "'"); + return $this->response(200, "successfull", $result); + } +} diff --git a/lib/classes/api/commands/class.EmailForwarders.php b/lib/classes/api/commands/class.EmailForwarders.php index ad325e92..e91b96b8 100644 --- a/lib/classes/api/commands/class.EmailForwarders.php +++ b/lib/classes/api/commands/class.EmailForwarders.php @@ -138,58 +138,54 @@ class EmailForwarders extends ApiCommand implements ResourceEntity throw new Exception("You cannot access this resource", 405); } - if ($this->getUserDetail('email_forwarders_used') < $this->getUserDetail('email_forwarders') || $this->getUserDetail('email_forwarders') == '-1') { + // parameter + $id = $this->getParam('id', true, 0); + $ea_optional = ($id <= 0 ? false : true); + $emailaddr = $this->getParam('emailaddr', $ea_optional, ''); + $forwarderid = $this->getParam('forwarderid'); + + // validation + $result = $this->apiCall('Emails.get', array( + 'id' => $id, + 'emailaddr' => $emailaddr + )); + $id = $result['id']; + + $result['destination'] = explode(' ', $result['destination']); + if (isset($result['destination'][$forwarderid]) && $result['email'] != $result['destination'][$forwarderid]) { - // parameter - $id = $this->getParam('id', true, 0); - $ea_optional = ($id <= 0 ? false : true); - $emailaddr = $this->getParam('emailaddr', $ea_optional, ''); - $forwarderid = $this->getParam('forwarderid'); + // get needed customer info to reduce the email-forwarder-counter by one + $customer = $this->getCustomerData(); + + // unset it from array + unset($result['destination'][$forwarderid]); + // rebuild destination-string + $result['destination'] = implode(' ', $result['destination']); + // update in DB + $stmt = Database::prepare(" + UPDATE `" . TABLE_MAIL_VIRTUAL . "` SET `destination` = :dest + WHERE `customerid`= :cid AND `id`= :id + "); + $params = array( + "dest" => makeCorrectDestination($result['destination']), + "cid" => $customer['customerid'], + "id" => $id + ); + Database::pexecute($stmt, $params, true, true); + + // update customer usage + Customers::decreaseUsage($customer['customerid'], 'email_forwarders_used'); + + // update admin usage + Admins::decreaseUsage($customer['adminid'], 'email_forwarders_used'); + + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] deleted email forwarder for '" . $result['email_full'] . "'"); - // validation $result = $this->apiCall('Emails.get', array( - 'id' => $id, - 'emailaddr' => $emailaddr + 'emailaddr' => $result['email_full'] )); - $id = $result['id']; - - $result['destination'] = explode(' ', $result['destination']); - if (isset($result['destination'][$forwarderid]) && $result['email'] != $result['destination'][$forwarderid]) { - - // get needed customer info to reduce the email-forwarder-counter by one - $customer = $this->getCustomerData(); - - // unset it from array - unset($result['destination'][$forwarderid]); - // rebuild destination-string - $result['destination'] = implode(' ', $result['destination']); - // update in DB - $stmt = Database::prepare(" - UPDATE `" . TABLE_MAIL_VIRTUAL . "` SET `destination` = :dest - WHERE `customerid`= :cid AND `id`= :id - "); - $params = array( - "dest" => makeCorrectDestination($result['destination']), - "cid" => $customer['customerid'], - "id" => $id - ); - Database::pexecute($stmt, $params, true, true); - - // update customer usage - Customers::decreaseUsage($customer['customerid'], 'email_forwarders_used'); - - // update admin usage - Admins::decreaseUsage($customer['adminid'], 'email_forwarders_used'); - - $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] deleted email forwarder for '" . $result['email_full'] . "'"); - - $result = $this->apiCall('Emails.get', array( - 'emailaddr' => $result['email_full'] - )); - return $this->response(200, "successfull", $result); - } - throw new Exception("Unknown forwarder id", 404); + return $this->response(200, "successfull", $result); } - throw new Exception("No more resources available", 406); + throw new Exception("Unknown forwarder id", 404); } } From f0e084ef0eb2230a83a6bc50b4a54ccbd2a6547f Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 12 Mar 2018 14:17:45 +0100 Subject: [PATCH 139/746] minor fixes in EmailAccounts.update() Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.EmailAccounts.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/classes/api/commands/class.EmailAccounts.php b/lib/classes/api/commands/class.EmailAccounts.php index aea8e70a..1b8216de 100644 --- a/lib/classes/api/commands/class.EmailAccounts.php +++ b/lib/classes/api/commands/class.EmailAccounts.php @@ -290,7 +290,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity throw new Exception("Email address '" . $result['email_full'] . "' has no account assigned.", 406); } - $email_password = $this->getParam('email_password', true, ''); + $password = $this->getParam('email_password', true, ''); $quota = $this->getParam('email_quota', true, $result['quota']); // get needed customer info to reduce the email-account-counter by one @@ -434,7 +434,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity Admins::decreaseUsage($customer['adminid'], 'email_accounts_used'); Admins::decreaseUsage($customer['adminid'], 'email_quota_used', '', $quota); - $log->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] deleted email account for '" . $result['email_full'] . "'"); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] deleted email account for '" . $result['email_full'] . "'"); return $this->response(200, "successfull", $result); } } From 54deec87d08779d2a472da778ee260bd22287e09 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 12 Mar 2018 15:41:08 +0100 Subject: [PATCH 140/746] add a few emails-apicommand unit-tests Signed-off-by: Michael Kaufmann (d00p) --- lng/english.lng.php | 2 +- tests/Emails/EmailsTest.php | 200 +++++++++++++++++++++++++++++++++++- 2 files changed, 200 insertions(+), 2 deletions(-) diff --git a/lng/english.lng.php b/lng/english.lng.php index 507799e7..d514eddc 100644 --- a/lng/english.lng.php +++ b/lng/english.lng.php @@ -226,7 +226,7 @@ $lng['error']['emailexistalready'] = 'The email-address %s already exists.'; $lng['error']['maindomainnonexist'] = 'The main-domain %s does not exist.'; $lng['error']['destinationnonexist'] = 'Please create your forwarder in the field \'Destination\'.'; $lng['error']['destinationalreadyexistasmail'] = 'The forwarder to %s already exists as active email-address.'; -$lng['error']['destinationalreadyexist'] = 'You have already defined a forwarder to %s .'; +$lng['error']['destinationalreadyexist'] = 'You have already defined a forwarder to "%s"'; $lng['error']['destinationiswrong'] = 'The forwarder %s contains invalid character(s) or is incomplete.'; $lng['error']['ticketnotaccessible'] = 'You cannot access this ticket.'; diff --git a/tests/Emails/EmailsTest.php b/tests/Emails/EmailsTest.php index 77f71c12..73287358 100644 --- a/tests/Emails/EmailsTest.php +++ b/tests/Emails/EmailsTest.php @@ -5,9 +5,12 @@ use PHPUnit\Framework\TestCase; * @covers ApiCommand * @covers ApiParameter * @covers Emails + * @covers EmailForwarders + * @covers EmailAccounts */ class MailsTest extends TestCase { + public function testCustomerEmailsAdd() { global $admin_userdata; @@ -27,7 +30,7 @@ class MailsTest extends TestCase $this->assertEquals("info@test2.local", $result['email_full']); $this->assertEquals(0, $result['iscatchall']); } - + public function testAdminEmailsAdd() { global $admin_userdata; @@ -42,4 +45,199 @@ class MailsTest extends TestCase $this->assertEquals("catchall@test2.local", $result['email_full']); $this->assertEquals(1, $result['iscatchall']); } + + public function testAdminEmailsUpdate() + { + global $admin_userdata; + $data = [ + 'emailaddr' => 'catchall@test2.local', + 'iscatchall' => 0, + 'customerid' => 1 + ]; + $json_result = Emails::getLocal($admin_userdata, $data)->update(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(0, $result['iscatchall']); + } + + public function testCustomerEmailsUpdate() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'emailaddr' => 'catchall@test2.local', + 'iscatchall' => 1 + ]; + $json_result = Emails::getLocal($customer_userdata, $data)->update(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(1, $result['iscatchall']); + } + + public function testCustomerEmailForwardersAdd() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'emailaddr' => 'info@test2.local', + 'destination' => 'other@domain.tld' + ]; + $json_result = EmailForwarders::getLocal($customer_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('other@domain.tld', $result['destination']); + } + + public function testCustomerEmailForwardersAddAnother() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'emailaddr' => 'info@test2.local', + 'destination' => 'other2@domain.tld' + ]; + $json_result = EmailForwarders::getLocal($customer_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('other@domain.tld other2@domain.tld', $result['destination']); + } + + /** + * @depends testCustomerEmailForwardersAdd + */ + public function testCustomerEmailForwardersAddExistingAsMail() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'emailaddr' => 'info@test2.local', + 'destination' => 'info@test2.local' + ]; + $this->expectExceptionMessage("The forwarder to info@test2.local already exists as active email-address."); + EmailForwarders::getLocal($customer_userdata, $data)->add(); + } + + /** + * @depends testCustomerEmailForwardersAdd + */ + public function testCustomerEmailForwardersAddExistingAsDestination() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'emailaddr' => 'info@test2.local', + 'destination' => 'other@domain.tld' + ]; + $this->expectExceptionMessage('You have already defined a forwarder to "other@domain.tld"'); + EmailForwarders::getLocal($customer_userdata, $data)->add(); + } + + public function testCustomerEmailForwardersAddInvalid() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'emailaddr' => 'info@test2.local', + 'destination' => '@domain.com' + ]; + $this->expectExceptionMessage("The forwarder domain.com contains invalid character(s) or is incomplete."); + EmailForwarders::getLocal($customer_userdata, $data)->add(); + } + + public function testAdminEmailForwadersUndefinedGet() + { + global $admin_userdata; + $this->expectExceptionCode(303); + EmailForwarders::getLocal($admin_userdata)->get(); + } + + public function testAdminEmailForwadersUndefinedUpdate() + { + global $admin_userdata; + $this->expectExceptionCode(303); + EmailForwarders::getLocal($admin_userdata)->update(); + } + + public function testAdminEmailForwadersUndefinedListing() + { + global $admin_userdata; + $this->expectExceptionCode(303); + EmailForwarders::getLocal($admin_userdata)->listing(); + } + + /** + * @depends testCustomerEmailForwardersAddAnother + */ + public function testCustomerEmailForwardersDelete() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'emailaddr' => 'info@test2.local', + 'forwarderid' => 1 + ]; + $json_result = EmailForwarders::getLocal($customer_userdata, $data)->delete(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('other@domain.tld', $result['destination']); + } + + /** + * @depends testCustomerEmailForwardersAddAnother + */ + public function testCustomerEmailForwardersDeleteunknown() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'emailaddr' => 'info@test2.local', + 'forwarderid' => 1337 + ]; + $this->expectExceptionCode(404); + $this->expectExceptionMessage("Unknown forwarder id"); + EmailForwarders::getLocal($customer_userdata, $data)->delete(); + } } From a7523bbdea360fc14a5c9aeba96238a0983b3549 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 12 Mar 2018 15:52:34 +0100 Subject: [PATCH 141/746] add domainid to result-list of Emails.get(); fix typo in EmailAccounts.add(); enhance debugging in Database-class Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.EmailAccounts.php | 2 +- lib/classes/api/commands/class.Emails.php | 2 +- lib/classes/database/class.Database.php | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/classes/api/commands/class.EmailAccounts.php b/lib/classes/api/commands/class.EmailAccounts.php index 1b8216de..b4f18b0f 100644 --- a/lib/classes/api/commands/class.EmailAccounts.php +++ b/lib/classes/api/commands/class.EmailAccounts.php @@ -109,7 +109,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity `customerid` = :cid, `email` = :email, `username` = :username," . (Settings::Get('system.mailpwcleartext') == '1' ? '`password` = :password, ' : '') . " - `password_enc` = :password_end, + `password_enc` = :password_enc, `homedir` = :homedir, `maildir` = :maildir, `uid` = :uid, diff --git a/lib/classes/api/commands/class.Emails.php b/lib/classes/api/commands/class.Emails.php index d4a36fc6..5255cf33 100644 --- a/lib/classes/api/commands/class.Emails.php +++ b/lib/classes/api/commands/class.Emails.php @@ -162,7 +162,7 @@ class Emails extends ApiCommand implements ResourceEntity $params['customerid'] = implode(", ", $customer_ids); $params['idea'] = ($id <= 0 ? $emailaddr : $id); - $result_stmt = Database::prepare("SELECT v.`id`, v.`email`, v.`email_full`, v.`iscatchall`, v.`destination`, v.`customerid`, v.`popaccountid`, u.`quota` + $result_stmt = Database::prepare("SELECT v.`id`, v.`email`, v.`email_full`, v.`iscatchall`, v.`destination`, v.`customerid`, v.`popaccountid`, v.`domainid`, u.`quota` FROM `" . TABLE_MAIL_VIRTUAL . "` v LEFT JOIN `" . TABLE_MAIL_USERS . "` u ON v.`popaccountid` = u.`id` WHERE v.`customerid` IN (:customerid) diff --git a/lib/classes/database/class.Database.php b/lib/classes/database/class.Database.php index 3c7d5cb5..3c1e64c3 100644 --- a/lib/classes/database/class.Database.php +++ b/lib/classes/database/class.Database.php @@ -372,17 +372,17 @@ class Database { @fwrite($errlog, "|TRACE\n".$error_trace."\n"); @fclose($errlog); + if (empty($sql['debug'])) { + $error_trace = ''; + } elseif (!is_null($stmt)) { + $error_trace .= "

    ".$stmt->queryString; + } + if ($showerror && $json_response) { - throw new Exception($error_message, 500); + throw new Exception($error_message.($sql['debug'] ? "\n\n".$error_trace : ''), 500); } if ($showerror) { - if (empty($sql['debug'])) { - $error_trace = ''; - } elseif (!is_null($stmt)) { - $error_trace .= "

    ".$stmt->queryString; - } - // fallback $theme = 'Sparkle'; From 05857985f8b492b2dd591f1e597eed1625c42091 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 12 Mar 2018 21:29:03 +0100 Subject: [PATCH 142/746] add more tests for Email-ApiCommands Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/abstract.ApiParameter.php | 18 --- .../api/commands/class.EmailAccounts.php | 22 +-- tests/Emails/EmailsTest.php | 130 +++++++++++++++++- 3 files changed, 143 insertions(+), 27 deletions(-) diff --git a/lib/classes/api/abstract.ApiParameter.php b/lib/classes/api/abstract.ApiParameter.php index 749b4958..c51f4088 100644 --- a/lib/classes/api/abstract.ApiParameter.php +++ b/lib/classes/api/abstract.ApiParameter.php @@ -103,24 +103,6 @@ abstract class ApiParameter return $param_value; } - /** - * update value of parameter - * - * @param string $param - * @param mixed $value - * - * @throws Exception - * @return boolean - */ - protected function updateParam($param, $value = null) - { - if (isset($this->cmd_params[$param])) { - $this->cmd_params[$param] = $value; - return true; - } - throw new Exception("Unable to update parameter '" . $param . "' as it does not exist", 500); - } - /** * return list of all parameters * diff --git a/lib/classes/api/commands/class.EmailAccounts.php b/lib/classes/api/commands/class.EmailAccounts.php index b4f18b0f..ce40ea0d 100644 --- a/lib/classes/api/commands/class.EmailAccounts.php +++ b/lib/classes/api/commands/class.EmailAccounts.php @@ -317,15 +317,20 @@ class EmailAccounts extends ApiCommand implements ResourceEntity } } - if ($quota != $result['quota']) { - if ($customer['email_quota'] != '-1' && ($quota == 0 || ($quota + $customer['email_quota_used'] - $result['quota']) > $customer['email_quota'])) { - standard_error('allocatetoomuchquota', $quota, true); + if (Settings::Get('system.mail_quota_enabled') == 1) { + if ($quota != $result['quota']) { + if ($customer['email_quota'] != '-1' && ($quota == 0 || ($quota + $customer['email_quota_used'] - $result['quota']) > $customer['email_quota'])) { + standard_error('allocatetoomuchquota', $quota, true); + } + if (! empty($upd_query)) { + $upd_query .= ", "; + } + $upd_query .= "`quota` = :quota"; + $upd_params['quota'] = $quota; } - if (! empty($upd_query)) { - $upd_query .= ", "; - } - $upd_query .= "`quota` = :quota"; - $upd_params['quota'] = $quota; + } else { + // disable + $quota = 0; } // build update query @@ -416,6 +421,7 @@ class EmailAccounts extends ApiCommand implements ResourceEntity "id" => $id ); Database::pexecute($stmt, $params, true, true); + $result['popaccountid'] = 0; if (Settings::Get('system.mail_quota_enabled') == '1' && $customer['email_quota'] != '-1') { $quota = (int) $result['quota']; diff --git a/tests/Emails/EmailsTest.php b/tests/Emails/EmailsTest.php index 73287358..5ef1f0d8 100644 --- a/tests/Emails/EmailsTest.php +++ b/tests/Emails/EmailsTest.php @@ -222,7 +222,7 @@ class MailsTest extends TestCase /** * @depends testCustomerEmailForwardersAddAnother */ - public function testCustomerEmailForwardersDeleteunknown() + public function testCustomerEmailForwardersDeleteUnknown() { global $admin_userdata; @@ -240,4 +240,132 @@ class MailsTest extends TestCase $this->expectExceptionMessage("Unknown forwarder id"); EmailForwarders::getLocal($customer_userdata, $data)->delete(); } + + public function testCustomerEmailsListing() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $json_result = Emails::getLocal($customer_userdata)->listing(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(2, $result['count']); + $this->assertEquals("info@test2.local", $result['list'][0]['email']); + $this->assertEquals("@test2.local", $result['list'][1]['email']); + } + + public function testCustomerEmailAccountsAdd() + { + global $admin_userdata; + + Settings::Set('panel.sendalternativemail', 1, true); + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'emailaddr' => 'info@test2.local', + 'email_password' => generatePassword(), + 'alternative_email' => 'noone@example.com', + 'email_quota' => 1337 + ]; + $json_result = EmailAccounts::getLocal($customer_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(1, $result['popaccountid']); + } + + public function testAdminEmailAccountsUpdate() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'emailaddr' => 'info@test2.local', + 'email_password' => generatePassword(), + 'alternative_email' => 'noone@example.com', + 'email_quota' => 1338 + ]; + $json_result = EmailAccounts::getLocal($customer_userdata, $data)->update(); + $result = json_decode($json_result, true)['data']; + // quota is disabled + $this->assertEquals(0, $result['quota']); + } + + public function testAdminEmailAccountsUndefinedGet() + { + global $admin_userdata; + $this->expectExceptionCode(303); + EmailAccounts::getLocal($admin_userdata)->get(); + } + + public function testAdminEmailAccountsUndefinedListing() + { + global $admin_userdata; + $this->expectExceptionCode(303); + EmailAccounts::getLocal($admin_userdata)->listing(); + } + + public function testCustomerEmailAccountsDelete() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'emailaddr' => 'info@test2.local', + 'delete_userfiles' => 1 + ]; + $json_result = EmailAccounts::getLocal($customer_userdata, $data)->delete(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(0, $result['popaccountid']); + } + + public function testCustomerEmailsDelete() + { + global $admin_userdata; + + // remove possible existing delete tasks + Database::query("TRUNCATE `".TABLE_PANEL_TASKS."`"); + + Settings::Set('panel.sendalternativemail', 0, true); + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + // add account + $data = [ + 'emailaddr' => 'info@test2.local', + 'email_password' => generatePassword(), + 'alternative_email' => 'noone@example.com' + ]; + $json_result = EmailAccounts::getLocal($customer_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(2, $result['popaccountid']); + + // now delete the whole address + $data = [ + 'emailaddr' => 'info@test2.local', + 'delete_userfiles' => 1 + ]; + $json_result = Emails::getLocal($customer_userdata, $data)->delete(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals("info@test2.local", $result['email_full']); + } } From 309e613c83c6b09a620e762f28f79ad16d46c2da Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 12 Mar 2018 21:40:19 +0100 Subject: [PATCH 143/746] exclude some irritating pmd rules Signed-off-by: Michael Kaufmann (d00p) --- phpmd.xml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/phpmd.xml b/phpmd.xml index e9e42fd5..121e73d6 100644 --- a/phpmd.xml +++ b/phpmd.xml @@ -12,7 +12,9 @@ + + @@ -29,7 +31,14 @@ 1 - + + + + + + 1 + + From 52da7ad40f6bb2ed45c07d57d1267ddb78c8c080 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 12 Mar 2018 21:47:14 +0100 Subject: [PATCH 144/746] exclude some irritating pmd rules #2 Signed-off-by: Michael Kaufmann (d00p) --- phpmd.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpmd.xml b/phpmd.xml index 121e73d6..946e660e 100644 --- a/phpmd.xml +++ b/phpmd.xml @@ -13,6 +13,7 @@ + @@ -36,9 +37,8 @@ - 1 - + From ae3d95476642e320cb56e13ba9d7e9092bd2bd13 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 12 Mar 2018 22:07:51 +0100 Subject: [PATCH 145/746] started work on DirProtections-ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- customer_extras.php | 31 ++- .../api/commands/class.DirProtections.php | 183 ++++++++++++++++++ 2 files changed, 196 insertions(+), 18 deletions(-) create mode 100644 lib/classes/api/commands/class.DirProtections.php diff --git a/customer_extras.php b/customer_extras.php index 5800805d..0bc41701 100644 --- a/customer_extras.php +++ b/customer_extras.php @@ -77,27 +77,22 @@ if ($page == 'overview') { eval("echo \"" . getTemplate("extras/htpasswds") . "\";"); } elseif ($action == 'delete' && $id != 0) { - $result_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_HTPASSWDS . "` - WHERE `customerid`= :customerid - AND `id`= :id"); - Database::pexecute($result_stmt, array( - "customerid" => $userinfo['customerid'], - "id" => $id - )); - $result = $result_stmt->fetch(PDO::FETCH_ASSOC); + try { + $json_result = DirProtections::getLocal($userinfo, array( + 'id' => $id + ))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; if (isset($result['username']) && $result['username'] != '') { if (isset($_POST['send']) && $_POST['send'] == 'send') { - $stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_HTPASSWDS . "` - WHERE `customerid`= :customerid - AND `id`= :id"); - Database::pexecute($stmt, array( - "customerid" => $userinfo['customerid'], - "id" => $id - )); - - $log->logAction(USR_ACTION, LOG_INFO, "deleted htpasswd for '" . $result['username'] . " (" . $result['path'] . ")'"); - inserttask('1'); + try { + DirProtections::getLocal($userinfo, $_POST)->delete(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } redirectTo($filename, array( 'page' => $page, 's' => $s diff --git a/lib/classes/api/commands/class.DirProtections.php b/lib/classes/api/commands/class.DirProtections.php new file mode 100644 index 00000000..6521fe62 --- /dev/null +++ b/lib/classes/api/commands/class.DirProtections.php @@ -0,0 +1,183 @@ + (2010-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package API + * @since 0.10.0 + * + */ +class DirProtections extends ApiCommand implements ResourceEntity +{ + + public function add() + {} + + /** + * return a directory-protection entry by either id or username + * + * @param int $id + * optional, the customer-id + * @param string $username + * optional, the username + * + * @access admin, customer + * @throws Exception + * @return array + */ + public function get() + { + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras')) { + throw new Exception("You cannot access this resource", 405); + } + + $id = $this->getParam('id', true, 0); + $un_optional = ($id <= 0 ? false : true); + $username = $this->getParam('username', $un_optional, ''); + + $params = array(); + if ($this->isAdmin()) { + if ($this->getUserDetail('customers_see_all') == false) { + // if it's a reseller or an admin who cannot see all customers, we need to check + // whether the database belongs to one of his customers + $_custom_list_result = $this->apiCall('Customers.listing'); + $custom_list_result = $_custom_list_result['list']; + $customer_ids = array(); + foreach ($custom_list_result as $customer) { + $customer_ids[] = $customer['customerid']; + } + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_HTPASSWDS . "` + WHERE `customerid` IN (:customerid) + AND (`id` = :idun OR `username` = :idun) + "); + $params['customerid'] = implode(", ", $customer_ids); + } else { + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_HTPASSWDS . "` + WHERE (`id` = :idun OR `username` = :idun) + "); + } + } else { + if (Settings::IsInList('panel.customer_hide_options', 'extras.directoryprotection')) { + throw new Exception("You cannot access this resource", 405); + } + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_HTPASSWDS . "` + WHERE `customerid` = :customerid + AND (`id` = :idun OR `username` = :idun) + "); + $params['customerid'] = $this->getUserDetail('customerid'); + } + $params['idun'] = ($id <= 0 ? $username : $id); + $result = Database::pexecute_first($result_stmt, $params, true, true); + if ($result) { + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_NOTICE, "[API] get directory protection for '" . $result['path'] . "'"); + return $this->response(200, "successfull", $result); + } + $key = ($id > 0 ? "id #" . $id : "username '" . $username . "'"); + throw new Exception("Directory protection with " . $key . " could not be found", 404); + } + + public function update() + {} + + /** + * list all directory-protections, if called from an admin, list all directory-protections of all customers you are allowed to view, or specify id or loginname for one specific customer + * + * @param int $customerid + * optional, admin-only, select directory-protections of a specific customer by id + * @param string $loginname + * optional, admin-only, select directory-protections of a specific customer by loginname + * + * @access admin, customer + * @throws Exception + * @return array count|list + */ + public function listing() + { + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras')) { + throw new Exception("You cannot access this resource", 405); + } + $customer_ids = $this->getAllowedCustomerIds('extras.directoryprotection'); + + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_HTPASSWDS . "` + WHERE `customerid` IN (:customerids) + "); + Database::pexecute($result_stmt, array( + "customerids" => $customer_ids + ), true, true); + while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { + $result[] = $row; + } + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_NOTICE, "[API] list directory-protections"); + return $this->response(200, "successfull", array( + 'count' => count($result), + 'list' => $result + )); + } + + /** + * delete a directory-protection by either id or username + * + * @param int $id + * optional, the ftp-user-id + * @param string $username + * optional, the username + * + * @access admin, customer + * @throws Exception + * @return array + */ + public function delete() + { + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras')) { + throw new Exception("You cannot access this resource", 405); + } + + $id = $this->getParam('id', true, 0); + $un_optional = ($id <= 0 ? false : true); + $username = $this->getParam('username', $un_optional, ''); + + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras.directoryprotection')) { + throw new Exception("You cannot access this resource", 405); + } + + // get ftp-user + $result = $this->apiCall('DirProtections.get', array( + 'id' => $id, + 'username' => $username + )); + $id = $result['id']; + + if ($this->isAdmin()) { + // get customer-data + $customer_data = $this->apiCall('Customers.get', array( + 'id' => $result['customerid'] + )); + } else { + $customer_data = $this->getUserData(); + } + + $stmt = Database::prepare(" + DELETE FROM `" . TABLE_PANEL_HTPASSWDS . "` WHERE `customerid`= :customerid AND `id`= :id + "); + Database::pexecute($stmt, array( + "customerid" => $customer_data['customerid'], + "id" => $id + )); + + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] deleted htpasswd for '" . $result['username'] . " (" . $result['path'] . ")'"); + inserttask('1'); + return $this->response(200, "successfull", $result); + } +} From 7e47383ee34baf0aef0b6a873b9c150ecb9d5ef7 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 12 Mar 2018 22:12:16 +0100 Subject: [PATCH 146/746] ignore NumberOfChildren pmd warning Signed-off-by: Michael Kaufmann (d00p) --- phpmd.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/phpmd.xml b/phpmd.xml index 946e660e..2f9d2bf5 100644 --- a/phpmd.xml +++ b/phpmd.xml @@ -7,7 +7,9 @@ froxlor ruleset. - + + + From 38b57117e2a495a911699d4d98c30a5731f4686c Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 13 Mar 2018 10:40:45 +0100 Subject: [PATCH 147/746] minor fixes in froxlor-sql file Signed-off-by: Michael Kaufmann (d00p) --- install/froxlor.sql | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/install/froxlor.sql b/install/froxlor.sql index 0876cffd..bdad8357 100644 --- a/install/froxlor.sql +++ b/install/froxlor.sql @@ -280,11 +280,11 @@ CREATE TABLE `panel_ipsandports` ( `vhostcontainer_servername_statement` tinyint(1) NOT NULL default '0', `specialsettings` text, `ssl` tinyint(4) NOT NULL default '0', - `ssl_cert_file` varchar(255) NOT NULL, - `ssl_key_file` varchar(255) NOT NULL, - `ssl_ca_file` varchar(255) NOT NULL, + `ssl_cert_file` varchar(255) NOT NULL default '', + `ssl_key_file` varchar(255) NOT NULL default '', + `ssl_ca_file` varchar(255) NOT NULL default '', `default_vhostconf_domain` text, - `ssl_cert_chainfile` varchar(255) NOT NULL, + `ssl_cert_chainfile` varchar(255) NOT NULL default '', `docroot` varchar(255) NOT NULL default '', PRIMARY KEY (`id`), UNIQUE KEY `ip_port` (`ip`,`port`) @@ -999,8 +999,8 @@ DROP TABLE IF EXISTS `domain_ssl_settings`; CREATE TABLE IF NOT EXISTS `domain_ssl_settings` ( `id` int(5) NOT NULL auto_increment, `domainid` int(11) NOT NULL, - `ssl_cert_file` mediumtext NOT NULL, - `ssl_key_file` mediumtext NOT NULL, + `ssl_cert_file` mediumtext, + `ssl_key_file` mediumtext, `ssl_ca_file` mediumtext, `ssl_cert_chainfile` mediumtext, `ssl_csr_file` mediumtext, From 3c802038f21dabcc558eb756a59b1bd6e280ede4 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Wed, 14 Mar 2018 11:35:03 +0100 Subject: [PATCH 148/746] split touch-command for multiple files into single ones, fixes #535 Signed-off-by: Michael Kaufmann (d00p) --- lib/configfiles/jessie.xml | 4 +++- lib/configfiles/precise.xml | 4 +++- lib/configfiles/stretch.xml | 4 +++- lib/configfiles/trusty.xml | 4 +++- lib/configfiles/wheezy.xml | 4 +++- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/configfiles/jessie.xml b/lib/configfiles/jessie.xml index 062cdb9b..fa60dcc2 100644 --- a/lib/configfiles/jessie.xml +++ b/lib/configfiles/jessie.xml @@ -4651,7 +4651,9 @@ aliases: files - + + + - + + + - + + + - + + + - + + + Date: Wed, 14 Mar 2018 11:37:45 +0100 Subject: [PATCH 149/746] rename handler php5-fastcgi to php-fastcgi, just cosmetics; fixes #536 Signed-off-by: Michael Kaufmann (d00p) --- scripts/jobs/cron_tasks.inc.http.10.apache.php | 4 ++-- scripts/jobs/cron_tasks.inc.http.15.apache_fcgid.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/jobs/cron_tasks.inc.http.10.apache.php b/scripts/jobs/cron_tasks.inc.http.10.apache.php index e49ef34e..21225125 100644 --- a/scripts/jobs/cron_tasks.inc.http.10.apache.php +++ b/scripts/jobs/cron_tasks.inc.http.10.apache.php @@ -360,8 +360,8 @@ class apache extends HttpConfigBase // start block, cut off last pipe and close block $filesmatch = '('.str_replace(".", "\.", substr($filesmatch, 0, -1)).')'; $this->virtualhosts_data[$vhosts_filename] .= ' '. "\n"; - $this->virtualhosts_data[$vhosts_filename] .= ' AddHandler php5-fastcgi .php' . "\n"; - $this->virtualhosts_data[$vhosts_filename] .= ' Action php5-fastcgi /fastcgiphp' . "\n"; + $this->virtualhosts_data[$vhosts_filename] .= ' AddHandler php-fastcgi .php' . "\n"; + $this->virtualhosts_data[$vhosts_filename] .= ' Action php-fastcgi /fastcgiphp' . "\n"; $this->virtualhosts_data[$vhosts_filename] .= ' Options +ExecCGI' . "\n"; $this->virtualhosts_data[$vhosts_filename] .= ' ' . "\n"; // >=apache-2.4 enabled? diff --git a/scripts/jobs/cron_tasks.inc.http.15.apache_fcgid.php b/scripts/jobs/cron_tasks.inc.http.15.apache_fcgid.php index acf49948..fb20d742 100644 --- a/scripts/jobs/cron_tasks.inc.http.15.apache_fcgid.php +++ b/scripts/jobs/cron_tasks.inc.http.15.apache_fcgid.php @@ -91,8 +91,8 @@ class apache_fcgid extends apache // start block, cut off last pipe and close block $filesmatch = '('.str_replace(".", "\.", substr($filesmatch, 0, -1)).')'; $php_options_text.= ' '. "\n"; - $php_options_text.= ' SetHandler php5-fastcgi'. "\n"; - $php_options_text.= ' Action php5-fastcgi /fastcgiphp' . "\n"; + $php_options_text.= ' SetHandler php-fastcgi'. "\n"; + $php_options_text.= ' Action php-fastcgi /fastcgiphp' . "\n"; $php_options_text.= ' Options +ExecCGI' . "\n"; $php_options_text.= ' ' . "\n"; // >=apache-2.4 enabled? From 616fb77de5176461adc7cde20455e753d662114f Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Wed, 14 Mar 2018 18:13:32 +0100 Subject: [PATCH 150/746] check for installed/configured froxlor in api.php and return 404 if not Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/api_includes.inc.php | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/lib/classes/api/api_includes.inc.php b/lib/classes/api/api_includes.inc.php index f7446b51..daea22b7 100644 --- a/lib/classes/api/api_includes.inc.php +++ b/lib/classes/api/api_includes.inc.php @@ -17,6 +17,32 @@ */ if (! defined('FROXLOR_INSTALL_DIR')) { define('FROXLOR_INSTALL_DIR', dirname(dirname(dirname(__DIR__)))); + // ensure that default timezone is set + if (function_exists("date_default_timezone_set") && function_exists("date_default_timezone_get")) { + @date_default_timezone_set(@date_default_timezone_get()); + } + $installed = true; + // check whether the userdata file exists + if (! @file_exists(FROXLOR_INSTALL_DIR . '/lib/userdata.inc.php')) { + $installed = false; + } + // check whether we can read the userdata file + if ($installed && ! @is_readable(FROXLOR_INSTALL_DIR . '/lib/userdata.inc.php')) { + $installed = false; + } + if ($installed) { + // include userdata for content-check + require FROXLOR_INSTALL_DIR . '/lib/userdata.inc.php'; + if (! isset($sql) || ! is_array($sql)) { + $installed = false; + } + } + // do not try to do anything if we have no installed/configured froxlor + if (! $installed) { + header("Status: 404 Not found", 404); + header($_SERVER["SERVER_PROTOCOL"] . " 404 Not found", 404); + exit(); + } require_once FROXLOR_INSTALL_DIR . '/lib/tables.inc.php'; require_once FROXLOR_INSTALL_DIR . '/lib/functions.php'; } From f2809c47ac7810f6ae344154b8bbf42785841227 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Wed, 14 Mar 2018 19:41:12 +0100 Subject: [PATCH 151/746] finished DirProtections.add() and some basic tests Signed-off-by: Michael Kaufmann (d00p) --- .../api/commands/class.DirProtections.php | 80 +++++++++++- phpunit.xml | 1 + tests/Customers/CustomersTest.php | 2 +- tests/Extras/ExtrasTest.php | 120 ++++++++++++++++++ 4 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 tests/Extras/ExtrasTest.php diff --git a/lib/classes/api/commands/class.DirProtections.php b/lib/classes/api/commands/class.DirProtections.php index 6521fe62..c8b296a5 100644 --- a/lib/classes/api/commands/class.DirProtections.php +++ b/lib/classes/api/commands/class.DirProtections.php @@ -19,7 +19,85 @@ class DirProtections extends ApiCommand implements ResourceEntity { public function add() - {} + { + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras')) { + throw new Exception("You cannot access this resource", 405); + } + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras.directoryprotection')) { + throw new Exception("You cannot access this resource", 405); + } + + // get needed customer info to reduce the email-address-counter by one + $customer = $this->getCustomerData(); + + // required parameters + $path = $this->getParam('path'); + $username = $this->getParam('username'); + $password = $this->getParam('directory_password'); + + // parameters + $authname = $this->getParam('directory_authname', true, ''); + + // validation + $path = makeCorrectDir(validate($path, 'path', '', '', array(), true)); + $path = makeCorrectDir($customer['documentroot'] . '/' . $path); + $username = validate($username, 'username', '/^[a-zA-Z0-9][a-zA-Z0-9\-_]+\$?$/', '', array(), true); + $authname = validate($authname, 'directory_authname', '/^[a-zA-Z0-9][a-zA-Z0-9\-_ ]+\$?$/', '', array(), true); + validate($password, 'password', '', '', array(), true); + + // check for duplicate usernames for the path + $username_path_check_stmt = Database::prepare(" + SELECT `id`, `username`, `path` FROM `" . TABLE_PANEL_HTPASSWDS . "` + WHERE `username`= :username AND `path`= :path AND `customerid`= :customerid + "); + $params = array( + "username" => $username, + "path" => $path, + "customerid" => $customer['customerid'] + ); + $username_path_check = Database::pexecute_first($username_path_check_stmt, $params, true, true); + + // check whether we can used salted passwords + if (CRYPT_STD_DES == 1) { + $saltfordescrypt = substr(md5(uniqid(microtime(), 1)), 4, 2); + $password_enc = crypt($password, $saltfordescrypt); + } else { + $password_enc = crypt($password); + } + + // duplicate check + if ($username_path_check['username'] == $username && $username_path_check['path'] == $path) { + standard_error('userpathcombinationdupe', '', true); + } elseif ($password == $username) { + standard_error('passwordshouldnotbeusername', '', true); + } + + // insert the entry + $stmt = Database::prepare(" + INSERT INTO `" . TABLE_PANEL_HTPASSWDS . "` SET + `customerid` = :customerid, + `username` = :username, + `password` = :password, + `path` = :path, + `authname` = :authname + "); + $params = array( + "customerid" => $customer['customerid'], + "username" => $username, + "password" => $password_enc, + "path" => $path, + "authname" => $authname + ); + Database::pexecute($stmt, $params, true, true); + $id = Database::lastInsertId(); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] added directory-protection for '" . $username . " (" . $path . ")'"); + inserttask('1'); + + $result = $this->apiCall('DirProtections.get', array( + 'id' => $id + )); + return $this->response(200, "successfull", $result); + } /** * return a directory-protection entry by either id or username diff --git a/phpunit.xml b/phpunit.xml index f1754d3b..3f800929 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -17,6 +17,7 @@ tests/Certificates tests/Ftps tests/Emails + tests/Extras diff --git a/tests/Customers/CustomersTest.php b/tests/Customers/CustomersTest.php index a127178e..7ca85690 100644 --- a/tests/Customers/CustomersTest.php +++ b/tests/Customers/CustomersTest.php @@ -268,7 +268,7 @@ class CustomersTest extends TestCase /** * @depends testAdminCustomersAdd */ - public function testResellerCustomersUpdateAllocateMore() + public function testResellerCustomersAddAllocateMore() { global $admin_userdata; // get reseller diff --git a/tests/Extras/ExtrasTest.php b/tests/Extras/ExtrasTest.php new file mode 100644 index 00000000..704f2281 --- /dev/null +++ b/tests/Extras/ExtrasTest.php @@ -0,0 +1,120 @@ + 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'path' => '/test', + 'username' => 'testing', + 'directory_password' => generatePassword(), + 'directory_authname' => 'test1' + ]; + $json_result = DirProtections::getLocal($customer_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals($customer_userdata['documentroot'] . 'test/', $result['path']); + $this->assertEquals('test1', $result['authname']); + } + + public function testCustomerDirProtectionsAddSameUserPath() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'path' => '/test', + 'username' => 'testing', + 'directory_password' => generatePassword(), + 'directory_authname' => 'test2' + ]; + $this->expectExceptionMessage("Combination of username and path already exists"); + DirProtections::getLocal($customer_userdata, $data)->add(); + } + + public function testCustomerDirProtectionsAddPasswordEqualsUsername() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $up = generatePassword(); + $data = [ + 'path' => '/test', + 'username' => $up, + 'directory_password' => $up, + 'directory_authname' => 'test3' + ]; + $this->expectExceptionMessage("The password should not be the same as the username."); + DirProtections::getLocal($customer_userdata, $data)->add(); + } + + /** + * @depends testCustomerDirProtectionsAdd + */ + public function testAdminDirProtectionsGet() + { + global $admin_userdata; + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $data = [ + 'username' => 'testing', + 'customerid' => 1 + ]; + $json_result = DirProtections::getLocal($admin_userdata, $data)->get(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals($customer_userdata['documentroot'] . 'test/', $result['path']); + $this->assertEquals('test1', $result['authname']); + } + + /** + * @depends testCustomerDirProtectionsAdd + */ + public function testResellerDirProtectionsGet() + { + global $admin_userdata; + // get customer + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller' + ))->get(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $data = [ + 'username' => 'testing' + ]; + $json_result = DirProtections::getLocal($reseller_userdata, $data)->get(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals($customer_userdata['documentroot'] . 'test/', $result['path']); + $this->assertEquals('test1', $result['authname']); + } +} From 858a9ba6a49704e75adc726425fd1f1c15f082cf Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 15 Mar 2018 11:01:17 +0100 Subject: [PATCH 152/746] added DirProtections.update() and various unit-tests Signed-off-by: Michael Kaufmann (d00p) --- customer_extras.php | 146 +++--------------- .../api/commands/class.DirProtections.php | 67 +++++++- tests/Extras/ExtrasTest.php | 69 +++++++++ 3 files changed, 158 insertions(+), 124 deletions(-) diff --git a/customer_extras.php b/customer_extras.php index 0bc41701..104d4b2c 100644 --- a/customer_extras.php +++ b/customer_extras.php @@ -111,74 +111,15 @@ if ($page == 'overview') { } } elseif ($action == 'add') { if (isset($_POST['send']) && $_POST['send'] == 'send') { - $path = makeCorrectDir(validate($_POST['path'], 'path')); - $userpath = $path; - $path = makeCorrectDir($userinfo['documentroot'] . '/' . $path); - $username = validate($_POST['username'], 'username', '/^[a-zA-Z0-9][a-zA-Z0-9\-_]+\$?$/'); - $authname = validate($_POST['directory_authname'], 'directory_authname', '/^[a-zA-Z0-9][a-zA-Z0-9\-_ ]+\$?$/'); - validate($_POST['directory_password'], 'password'); - - $username_path_check_stmt = Database::prepare("SELECT `id`, `username`, `path` FROM `" . TABLE_PANEL_HTPASSWDS . "` - WHERE `username`= :username - AND `path`= :path - AND `customerid`= :customerid"); - $params = array( - "username" => $username, - "path" => $path, - "customerid" => $userinfo['customerid'] - ); - Database::pexecute($username_path_check_stmt, $params); - $username_path_check = $username_path_check_stmt->fetch(PDO::FETCH_ASSOC); - - if (CRYPT_STD_DES == 1) { - $saltfordescrypt = substr(md5(uniqid(microtime(), 1)), 4, 2); - $password = crypt($_POST['directory_password'], $saltfordescrypt); - } else { - $password = crypt($_POST['directory_password']); - } - - if (! $_POST['path']) { - standard_error('invalidpath'); - } - - if ($username == '') { - standard_error(array( - 'stringisempty', - 'myloginname' - )); - } elseif ($username_path_check['username'] == $username && $username_path_check['path'] == $path) { - standard_error('userpathcombinationdupe'); - } elseif ($_POST['directory_password'] == '') { - standard_error(array( - 'stringisempty', - 'mypassword' - )); - } elseif ($path == '') { - standard_error('patherror'); - } elseif ($_POST['directory_password'] == $username) { - standard_error('passwordshouldnotbeusername'); - } else { - $stmt = Database::prepare("INSERT INTO `" . TABLE_PANEL_HTPASSWDS . "` SET - `customerid` = :customerid, - `username` = :username, - `password` = :password, - `path` = :path, - `authname` = :authname"); - $params = array( - "customerid" => $userinfo['customerid'], - "username" => $username, - "password" => $password, - "path" => $path, - "authname" => $authname - ); - Database::pexecute($stmt, $params); - $log->logAction(USR_ACTION, LOG_INFO, "added htpasswd for '" . $username . " (" . $path . ")'"); - inserttask('1'); - redirectTo($filename, array( - 'page' => $page, - 's' => $s - )); + try { + DirProtections::getLocal($userinfo, $_POST)->add(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } + redirectTo($filename, array( + 'page' => $page, + 's' => $s + )); } else { $pathSelect = makePathfield($userinfo['documentroot'], $userinfo['guid'], $userinfo['guid']); @@ -191,65 +132,26 @@ if ($page == 'overview') { eval("echo \"" . getTemplate("extras/htpasswds_add") . "\";"); } } elseif ($action == 'edit' && $id != 0) { - $result_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_HTPASSWDS . "` - WHERE `customerid`= :customerid - AND `id`= :id"); - Database::pexecute($result_stmt, array( - "customerid" => $userinfo['customerid'], - "id" => $id - )); - $result = $result_stmt->fetch(PDO::FETCH_ASSOC); + try { + $json_result = DirProtections::getLocal($userinfo, array( + 'id' => $id + ))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; if (isset($result['username']) && $result['username'] != '') { if (isset($_POST['send']) && $_POST['send'] == 'send') { - validate($_POST['directory_password'], 'password'); - $authname = validate($_POST['directory_authname'], 'directory_authname', '/^[a-zA-Z0-9][a-zA-Z0-9\-_ ]+\$?$/'); - - if (CRYPT_STD_DES == 1) { - $saltfordescrypt = substr(md5(uniqid(microtime(), 1)), 4, 2); - $password = crypt($_POST['directory_password'], $saltfordescrypt); - } else { - $password = crypt($_POST['directory_password']); - } - - if ($_POST['directory_password'] == $result['username']) { - standard_error('passwordshouldnotbeusername'); - } - - $params = array( - "customerid" => $userinfo['customerid'], - "id" => $id - ); - - $pwd_sql = ''; - if ($_POST['directory_password'] != '') { - $pwd_sql = "`password`= :password "; - $params["password"] = $password; - } - - $auth_sql = ''; - if ($authname != $result['authname']) { - $auth_sql = "`authname`= :authname "; - $params["authname"] = $authname; - } - - if ($pwd_sql != '' || $auth_sql != '') { - if ($pwd_sql != '' && $auth_sql != '') { - $pwd_sql .= ', '; - } - - $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_HTPASSWDS . "` - SET " . $pwd_sql . $auth_sql . " - WHERE `customerid`= :customerid - AND `id`= :id"); - Database::pexecute($stmt, $params); - $log->logAction(USR_ACTION, LOG_INFO, "edited htpasswd for '" . $result['username'] . " (" . $result['path'] . ")'"); - inserttask('1'); - redirectTo($filename, array( - 'page' => $page, - 's' => $s - )); + try { + DirProtections::getLocal($userinfo, $_POST)->update(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } + redirectTo($filename, array( + 'page' => $page, + 's' => $s + )); } else { if (strpos($result['path'], $userinfo['documentroot']) === 0) { $result['path'] = str_replace($userinfo['documentroot'], "/", $result['path']); diff --git a/lib/classes/api/commands/class.DirProtections.php b/lib/classes/api/commands/class.DirProtections.php index c8b296a5..e654cd4b 100644 --- a/lib/classes/api/commands/class.DirProtections.php +++ b/lib/classes/api/commands/class.DirProtections.php @@ -166,7 +166,70 @@ class DirProtections extends ApiCommand implements ResourceEntity } public function update() - {} + { + $id = $this->getParam('id', true, 0); + $un_optional = ($id <= 0 ? false : true); + $username = $this->getParam('username', $un_optional, ''); + + // validation + $result = $this->apiCall('DirProtections.get', array( + 'id' => $id, + 'username' => $username + )); + $id = $result['id']; + + // parameters + $password = $this->getParam('directory_password', true, ''); + $authname = $this->getParam('directory_authname', true, $result['authname']); + + // get needed customer info + $customer = $this->getCustomerData(); + + // validation + $authname = validate($authname, 'directory_authname', '/^[a-zA-Z0-9][a-zA-Z0-9\-_ ]+\$?$/', '', array(), true); + validate($password, 'password', '', '', array(), true); + + $upd_query = ""; + $upd_params = array( + "id" => $result['id'], + "cid" => $customer['customerid'] + ); + if (! empty($password)) { + if ($password == $result['username']) { + standard_error('passwordshouldnotbeusername', '', true); + } + if (CRYPT_STD_DES == 1) { + $saltfordescrypt = substr(md5(uniqid(microtime(), 1)), 4, 2); + $password_enc = crypt($password, $saltfordescrypt); + } else { + $password_enc = crypt($password); + } + $upd_query .= "`password`= :password_enc"; + $upd_params['password_enc'] = $password_enc; + } + if ($authname != $result['authname']) { + if (! empty($upd_query)) { + $upd_query .= ", "; + } + $upd_query .= "`authname` = :authname"; + $upd_params['authname'] = $authname; + } + + // build update query + if (! empty($upd_query)) { + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_HTPASSWDS . "` SET " . $upd_query . " WHERE `id` = :id AND `customerid`= :cid + "); + Database::pexecute($upd_stmt, $upd_params, true, true); + inserttask('1'); + } + + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] updated directory-protection '" . $result['username'] . " (" . $result['path'] . ")'"); + $result = $this->apiCall('DirProtections.get', array( + 'id' => $result['id'] + )); + return $this->response(200, "successfull", $result); + } /** * list all directory-protections, if called from an admin, list all directory-protections of all customers you are allowed to view, or specify id or loginname for one specific customer @@ -192,7 +255,7 @@ class DirProtections extends ApiCommand implements ResourceEntity WHERE `customerid` IN (:customerids) "); Database::pexecute($result_stmt, array( - "customerids" => $customer_ids + "customerids" => implode(', ', $customer_ids) ), true, true); while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { $result[] = $row; diff --git a/tests/Extras/ExtrasTest.php b/tests/Extras/ExtrasTest.php index 704f2281..5e4f36cf 100644 --- a/tests/Extras/ExtrasTest.php +++ b/tests/Extras/ExtrasTest.php @@ -117,4 +117,73 @@ class ExtrasTest extends TestCase $this->assertEquals($customer_userdata['documentroot'] . 'test/', $result['path']); $this->assertEquals('test1', $result['authname']); } + + /** + * @depends testCustomerDirProtectionsAdd + */ + public function testCustomerDirProtectionsUpdate() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $json_result = DirProtections::getLocal($customer_userdata, array('id' => 1))->get(); + $data_old = json_decode($json_result, true)['data']; + + $data = [ + 'id' => 1, + 'directory_password' => generatePassword(), + 'directory_authname' => 'test1337' + ]; + $json_result = DirProtections::getLocal($customer_userdata, $data)->update(); + $result = json_decode($json_result, true)['data']; + $this->assertTrue($data_old['password'] != $result['password']); + $this->assertTrue($data_old['authname'] != $result['authname']); + $this->assertEquals('test1337', $result['authname']); + } + + /** + * @depends testCustomerDirProtectionsAdd + */ + public function testCustomerDirProtectionsList() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $json_result = DirProtections::getLocal($customer_userdata)->listing(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(2, $result['count']); + $this->assertEquals('test1', $result['list'][0]['username']); + $this->assertEquals('testing', $result['list'][1]['username']); + } + + /** + * @depends testCustomerDirProtectionsList + */ + public function testCustomerDirProtectionsDelete() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + DirProtections::getLocal($customer_userdata, array('username' => 'testing'))->delete(); + + $json_result = DirProtections::getLocal($customer_userdata)->listing(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(1, $result['count']); + $this->assertEquals('test1', $result['list'][0]['username']); + } } From 7a68dfc45053bca3fdb72e5c8c5b62e8571caad8 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 15 Mar 2018 19:35:56 +0100 Subject: [PATCH 153/746] DirProtections and DirOptions stuff Signed-off-by: Michael Kaufmann (d00p) --- customer_extras.php | 176 ++------- lib/classes/api/commands/class.DirOptions.php | 357 ++++++++++++++++++ .../api/commands/class.DirProtections.php | 4 +- .../froxlor/function.CorrectErrorDocument.php | 9 +- 4 files changed, 396 insertions(+), 150 deletions(-) create mode 100644 lib/classes/api/commands/class.DirOptions.php diff --git a/customer_extras.php b/customer_extras.php index 104d4b2c..0124b9b8 100644 --- a/customer_extras.php +++ b/customer_extras.php @@ -223,42 +223,22 @@ if ($page == 'overview') { eval("echo \"" . getTemplate("extras/htaccess") . "\";"); } elseif ($action == 'delete' && $id != 0) { - $result_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_HTACCESS . "` - WHERE `customerid` = :customerid - AND `id` = :id"); - Database::pexecute($result_stmt, array( - "customerid" => $userinfo['customerid'], - "id" => $id - )); - $result = $result_stmt->fetch(PDO::FETCH_ASSOC); + try { + $json_result = DirOptions::getLocal($userinfo, array( + 'id' => $id + ))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; if (isset($result['customerid']) && $result['customerid'] != '' && $result['customerid'] == $userinfo['customerid']) { if (isset($_POST['send']) && $_POST['send'] == 'send') { - // do we have to remove the symlink and folder in suexecpath? - if ((int) Settings::Get('perl.suexecworkaround') == 1) { - $loginname = getCustomerDetail($result['customerid'], 'loginname'); - $suexecpath = makeCorrectDir(Settings::Get('perl.suexecpath') . '/' . $loginname . '/' . md5($result['path']) . '/'); - $perlsymlink = makeCorrectFile($result['path'] . '/cgi-bin'); - // remove symlink - if (file_exists($perlsymlink)) { - safe_exec('rm -f ' . escapeshellarg($perlsymlink)); - $log->logAction(USR_ACTION, LOG_DEBUG, "deleted suexecworkaround symlink '" . $perlsymlink . "'"); - } - // remove folder in suexec-path - if (file_exists($suexecpath)) { - safe_exec('rm -rf ' . escapeshellarg($suexecpath)); - $log->logAction(USR_ACTION, LOG_DEBUG, "deleted suexecworkaround path '" . $suexecpath . "'"); - } + try { + DirOptions::getLocal($userinfo, $_POST)->delete(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - $stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_HTACCESS . "` - WHERE `customerid`= :customerid - AND `id`= :id"); - Database::pexecute($stmt, array( - "customerid" => $userinfo['customerid'], - "id" => $id - )); - $log->logAction(USR_ACTION, LOG_INFO, "deleted htaccess for '" . str_replace($userinfo['documentroot'], '/', $result['path']) . "'"); - inserttask('1'); redirectTo($filename, array( 'page' => $page, 's' => $s @@ -273,74 +253,15 @@ if ($page == 'overview') { } } elseif ($action == 'add') { if (isset($_POST['send']) && $_POST['send'] == 'send') { - $path = makeCorrectDir(validate($_POST['path'], 'path')); - $userpath = $path; - $path = makeCorrectDir($userinfo['documentroot'] . '/' . $path); - $path_dupe_check_stmt = Database::prepare("SELECT `id`, `path` FROM `" . TABLE_PANEL_HTACCESS . "` - WHERE `path`= :path - AND `customerid`= :customerid"); - Database::pexecute($path_dupe_check_stmt, array( - "path" => $path, - "customerid" => $userinfo['customerid'] + try { + DirOptions::getLocal($userinfo, $_POST)->add(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + redirectTo($filename, array( + 'page' => $page, + 's' => $s )); - $path_dupe_check = $path_dupe_check_stmt->fetch(PDO::FETCH_ASSOC); - - if (! $_POST['path']) { - standard_error('invalidpath'); - } - - if (isset($_POST['options_cgi']) && (int) $_POST['options_cgi'] != 0) { - $options_cgi = '1'; - } else { - $options_cgi = '0'; - } - - $error404path = ''; - if (isset($_POST['error404path'])) { - $error404path = correctErrorDocument($_POST['error404path']); - } - - $error403path = ''; - if (isset($_POST['error403path'])) { - $error403path = correctErrorDocument($_POST['error403path']); - } - - $error500path = ''; - if (isset($_POST['error500path'])) { - $error500path = correctErrorDocument($_POST['error500path']); - } - - if ($path_dupe_check['path'] == $path) { - standard_error('errordocpathdupe', $userpath); - } elseif ($path == '') { - standard_error('patherror'); - } else { - $stmt = Database::prepare('INSERT INTO `' . TABLE_PANEL_HTACCESS . '` SET - `customerid` = :customerid, - `path` = :path, - `options_indexes` = :options_indexes, - `error404path` = :error404path, - `error403path` = :error403path, - `error500path` = :error500path, - `options_cgi` = :options_cgi'); - $params = array( - "customerid" => $userinfo['customerid'], - "path" => $path, - "options_indexes" => $_POST['options_indexes'] == '1' ? '1' : '0', - "error403path" => $error403path, - "error404path" => $error404path, - "error500path" => $error500path, - "options_cgi" => $options_cgi - ); - Database::pexecute($stmt, $params); - - $log->logAction(USR_ACTION, LOG_INFO, "added htaccess for '" . $path . "'"); - inserttask('1'); - redirectTo($filename, array( - 'page' => $page, - 's' => $s - )); - } } else { $pathSelect = makePathfield($userinfo['documentroot'], $userinfo['guid'], $userinfo['guid']); $cperlenabled = customerHasPerlEnabled($userinfo['customerid']); @@ -354,55 +275,22 @@ if ($page == 'overview') { eval("echo \"" . getTemplate("extras/htaccess_add") . "\";"); } } elseif (($action == 'edit') && ($id != 0)) { - $result_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_HTACCESS . "` - WHERE `customerid` = :customerid - AND `id` = :id"); - Database::pexecute($result_stmt, array( - "customerid" => $userinfo['customerid'], - "id" => $id - )); - $result = $result_stmt->fetch(PDO::FETCH_ASSOC); + try { + $json_result = DirOptions::getLocal($userinfo, array( + 'id' => $id + ))->get(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; if ((isset($result['customerid'])) && ($result['customerid'] != '') && ($result['customerid'] == $userinfo['customerid'])) { if (isset($_POST['send']) && $_POST['send'] == 'send') { - $option_indexes = intval($_POST['options_indexes']); - $options_cgi = isset($_POST['options_cgi']) ? intval($_POST['options_cgi']) : 0; - - if ($option_indexes != '1') { - $option_indexes = '0'; + try { + DirOptions::getLocal($userinfo, $_POST)->update(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - - if ($options_cgi != '1') { - $options_cgi = '0'; - } - - $error404path = correctErrorDocument($_POST['error404path']); - $error403path = correctErrorDocument($_POST['error403path']); - $error500path = correctErrorDocument($_POST['error500path']); - - if (($option_indexes != $result['options_indexes']) || ($error404path != $result['error404path']) || ($error403path != $result['error403path']) || ($error500path != $result['error500path']) || ($options_cgi != $result['options_cgi'])) { - inserttask('1'); - $stmt = Database::prepare("UPDATE `" . TABLE_PANEL_HTACCESS . "` - SET `options_indexes` = :options_indexes, - `error404path` = :error404path, - `error403path` = :error403path, - `error500path` = :error500path, - `options_cgi` = :options_cgi - WHERE `customerid` = :customerid - AND `id` = :id"); - $params = array( - "customerid" => $userinfo['customerid'], - "options_indexes" => $_POST['options_indexes'] == '1' ? '1' : '0', - "error403path" => $error403path, - "error404path" => $error404path, - "error500path" => $error500path, - "options_cgi" => $options_cgi, - "id" => $id - ); - Database::pexecute($stmt, $params); - $log->logAction(USR_ACTION, LOG_INFO, "edited htaccess for '" . str_replace($userinfo['documentroot'], '/', $result['path']) . "'"); - } - redirectTo($filename, array( 'page' => $page, 's' => $s diff --git a/lib/classes/api/commands/class.DirOptions.php b/lib/classes/api/commands/class.DirOptions.php new file mode 100644 index 00000000..8325f9f9 --- /dev/null +++ b/lib/classes/api/commands/class.DirOptions.php @@ -0,0 +1,357 @@ + (2010-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package API + * @since 0.10.0 + * + */ +class DirOptions extends ApiCommand implements ResourceEntity +{ + + public function add() + { + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras')) { + throw new Exception("You cannot access this resource", 405); + } + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras.pathoptions')) { + throw new Exception("You cannot access this resource", 405); + } + + // get needed customer info to reduce the email-address-counter by one + $customer = $this->getCustomerData(); + + // required parameters + $path = $this->getParam('path'); + + // parameters + $options_indexes = $this->getParam('options_indexes', true, 0); + $options_cgi = $this->getParam('options_cgi', true, 0); + $error404path = $this->getParam('error404path', true, ''); + $error403path = $this->getParam('error403path', true, ''); + $error500path = $this->getParam('error500path', true, ''); + + // validation + $path = makeCorrectDir(validate($path, 'path', '', '', array(), true)); + $userpath = $path; + $path = makeCorrectDir($customer['documentroot'] . '/' . $path); + + if ($options_indexes != 0) { + $options_indexes = '1'; + } else { + $options_indexes = '0'; + } + + if ($options_cgi != 0) { + $options_cgi = '1'; + } else { + $options_cgi = '0'; + } + + if (! empty($error404path)) { + $error404path = correctErrorDocument($error404path, true); + } + + if (! empty($error403path)) { + $error403path = correctErrorDocument($error403path, true); + } + + if (! empty($error500path)) { + $error500path = correctErrorDocument($error500path, true); + } + + // check for duplicate path + $path_dupe_check_stmt = Database::prepare(" + SELECT `id`, `path` FROM `" . TABLE_PANEL_HTACCESS . "` + WHERE `path`= :path AND `customerid`= :customerid + "); + $path_dupe_check = Database::pexecute($path_dupe_check_stmt, array( + "path" => $path, + "customerid" => $customer['customerid'] + ), true, true); + + // duplicate check + if ($path_dupe_check['path'] == $path) { + standard_error('errordocpathdupe', $userpath, true); + } + + // insert the entry + $stmt = Database::prepare(' + INSERT INTO `' . TABLE_PANEL_HTACCESS . '` SET + `customerid` = :customerid, + `path` = :path, + `options_indexes` = :options_indexes, + `error404path` = :error404path, + `error403path` = :error403path, + `error500path` = :error500path, + `options_cgi` = :options_cgi + '); + $params = array( + "customerid" => $customer['customerid'], + "path" => $path, + "options_indexes" => $options_indexes, + "error403path" => $error403path, + "error404path" => $error404path, + "error500path" => $error500path, + "options_cgi" => $options_cgi + ); + Database::pexecute($stmt, $params, true, true); + $id = Database::lastInsertId(); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] added directory-protection for '" . $username . " (" . $path . ")'"); + inserttask('1'); + + $result = $this->apiCall('DirOptions.get', array( + 'id' => $id + )); + return $this->response(200, "successfull", $result); + } + + /** + * return a directory-protection entry by either id or username + * + * @param int $id + * optional, the customer-id + * @param string $username + * optional, the username + * + * @access admin, customer + * @throws Exception + * @return array + */ + public function get() + { + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras')) { + throw new Exception("You cannot access this resource", 405); + } + + $id = $this->getParam('id', true, 0); + + $params = array(); + if ($this->isAdmin()) { + if ($this->getUserDetail('customers_see_all') == false) { + // if it's a reseller or an admin who cannot see all customers, we need to check + // whether the database belongs to one of his customers + $_custom_list_result = $this->apiCall('Customers.listing'); + $custom_list_result = $_custom_list_result['list']; + $customer_ids = array(); + foreach ($custom_list_result as $customer) { + $customer_ids[] = $customer['customerid']; + } + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_HTACCESS . "` + WHERE `customerid` IN (:customerid) + AND `id` = :id + "); + $params['customerid'] = implode(", ", $customer_ids); + } else { + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_HTACCESS . "` + WHERE `id` = :id + "); + } + } else { + if (Settings::IsInList('panel.customer_hide_options', 'extras.pathoptions')) { + throw new Exception("You cannot access this resource", 405); + } + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_HTACCESS . "` + WHERE `customerid` = :customerid + AND `id` = :id + "); + $params['customerid'] = $this->getUserDetail('customerid'); + } + $params['id'] = $id; + $result = Database::pexecute_first($result_stmt, $params, true, true); + if ($result) { + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_NOTICE, "[API] get directory options for '" . $result['path'] . "'"); + return $this->response(200, "successfull", $result); + } + $key = "id #" . $id; + throw new Exception("Directory option with " . $key . " could not be found", 404); + } + + public function update() + { + $id = $this->getParam('id', true, 0); + + // validation + $result = $this->apiCall('DirOptions.get', array( + 'id' => $id + )); + + // get needed customer info to reduce the email-address-counter by one + $customer = $this->getCustomerData(); + + // parameters + $options_indexes = $this->getParam('options_indexes', true, $result['options_indexes']); + $options_cgi = $this->getParam('options_cgi', true, $result['options_cgi']); + $error404path = $this->getParam('error404path', true, $result['error404path']); + $error403path = $this->getParam('error403path', true, $result['error403path']); + $error500path = $this->getParam('error500path', true, $result['error500path']); + + if ($options_indexes != 0) { + $options_indexes = '1'; + } else { + $options_indexes = '0'; + } + + if ($options_cgi != 0) { + $options_cgi = '1'; + } else { + $options_cgi = '0'; + } + + if (! empty($error404path)) { + $error404path = correctErrorDocument($error404path, true); + } + + if (! empty($error403path)) { + $error403path = correctErrorDocument($error403path, true); + } + + if (! empty($error500path)) { + $error500path = correctErrorDocument($error500path, true); + } + + if (($option_indexes != $result['options_indexes']) || ($error404path != $result['error404path']) || ($error403path != $result['error403path']) || ($error500path != $result['error500path']) || ($options_cgi != $result['options_cgi'])) { + inserttask('1'); + $stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_HTACCESS . "` + SET `options_indexes` = :options_indexes, + `error404path` = :error404path, + `error403path` = :error403path, + `error500path` = :error500path, + `options_cgi` = :options_cgi + WHERE `customerid` = :customerid + AND `id` = :id + "); + $params = array( + "customerid" => $customer['customerid'], + "options_indexes" => $option_indexes, + "error403path" => $error403path, + "error404path" => $error404path, + "error500path" => $error500path, + "options_cgi" => $options_cgi, + "id" => $id + ); + Database::pexecute($stmt, $params, true, true); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] edited directory options for '" . str_replace($customer['documentroot'], '/', $result['path']) . "'"); + } + + $result = $this->apiCall('DirOptions.get', array( + 'id' => $id + )); + return $this->response(200, "successfull", $result); + } + + /** + * list all directory-options, if called from an admin, list all directory-options of all customers you are allowed to view, or specify id or loginname for one specific customer + * + * @param int $customerid + * optional, admin-only, select directory-protections of a specific customer by id + * @param string $loginname + * optional, admin-only, select directory-protections of a specific customer by loginname + * + * @access admin, customer + * @throws Exception + * @return array count|list + */ + public function listing() + { + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras')) { + throw new Exception("You cannot access this resource", 405); + } + $customer_ids = $this->getAllowedCustomerIds('extras.pathoptions'); + + $result_stmt = Database::prepare(" + SELECT * FROM `" . TABLE_PANEL_HTACCESS . "` + WHERE `customerid` IN (:customerids) + "); + Database::pexecute($result_stmt, array( + "customerids" => implode(', ', $customer_ids) + ), true, true); + while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { + $result[] = $row; + } + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_NOTICE, "[API] list directory-options"); + return $this->response(200, "successfull", array( + 'count' => count($result), + 'list' => $result + )); + } + + /** + * delete a directory-options by id + * + * @param int $id + * optional, the directory-option-id + * + * @access admin, customer + * @throws Exception + * @return array + */ + public function delete() + { + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras')) { + throw new Exception("You cannot access this resource", 405); + } + + $id = $this->getParam('id'); + + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras.pathoptions')) { + throw new Exception("You cannot access this resource", 405); + } + + // get directory-option + $result = $this->apiCall('DirOptions.get', array( + 'id' => $id + )); + + if ($this->isAdmin()) { + // get customer-data + $customer_data = $this->apiCall('Customers.get', array( + 'id' => $result['customerid'] + )); + } else { + $customer_data = $this->getUserData(); + } + + // do we have to remove the symlink and folder in suexecpath? + if ((int) Settings::Get('perl.suexecworkaround') == 1) { + $loginname = $customer_data['loginname']; + $suexecpath = makeCorrectDir(Settings::Get('perl.suexecpath') . '/' . $loginname . '/' . md5($result['path']) . '/'); + $perlsymlink = makeCorrectFile($result['path'] . '/cgi-bin'); + // remove symlink + if (file_exists($perlsymlink)) { + safe_exec('rm -f ' . escapeshellarg($perlsymlink)); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_DEBUG, "[API] deleted suexecworkaround symlink '" . $perlsymlink . "'"); + } + // remove folder in suexec-path + if (file_exists($suexecpath)) { + safe_exec('rm -rf ' . escapeshellarg($suexecpath)); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_DEBUG, "[API] deleted suexecworkaround path '" . $suexecpath . "'"); + } + } + $stmt = Database::prepare(" + DELETE FROM `" . TABLE_PANEL_HTACCESS . "` + WHERE `customerid`= :customerid + AND `id`= :id + "); + Database::pexecute($stmt, array( + "customerid" => $customer_data['customerid'], + "id" => $id + )); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] deleted directory-option for '" . str_replace($userinfo['documentroot'], '/', $result['path']) . "'"); + inserttask('1'); + return $this->response(200, "successfull", $result); + } +} diff --git a/lib/classes/api/commands/class.DirProtections.php b/lib/classes/api/commands/class.DirProtections.php index e654cd4b..95a8e1bc 100644 --- a/lib/classes/api/commands/class.DirProtections.php +++ b/lib/classes/api/commands/class.DirProtections.php @@ -271,7 +271,7 @@ class DirProtections extends ApiCommand implements ResourceEntity * delete a directory-protection by either id or username * * @param int $id - * optional, the ftp-user-id + * optional, the directory-protection-id * @param string $username * optional, the username * @@ -293,7 +293,7 @@ class DirProtections extends ApiCommand implements ResourceEntity throw new Exception("You cannot access this resource", 405); } - // get ftp-user + // get directory protection $result = $this->apiCall('DirProtections.get', array( 'id' => $id, 'username' => $username diff --git a/lib/functions/froxlor/function.CorrectErrorDocument.php b/lib/functions/froxlor/function.CorrectErrorDocument.php index 7df4bf37..72c995d4 100644 --- a/lib/functions/froxlor/function.CorrectErrorDocument.php +++ b/lib/functions/froxlor/function.CorrectErrorDocument.php @@ -20,13 +20,14 @@ * refs #267 * * @param string error-document-string + * @param bool $throw_exception * * @return string error-document-string * */ -function correctErrorDocument($errdoc = null) { +function correctErrorDocument($errdoc = null, $throw_exception = false) { - global $idna_convert; + $idna_convert = new idna_convert_wrapper(); if ($errdoc !== null && $errdoc != '') { // not a URL @@ -46,14 +47,14 @@ function correctErrorDocument($errdoc = null) { else { // string won't work for lighty if (Settings::Get('system.webserver') == 'lighttpd') { - standard_error('stringerrordocumentnotvalidforlighty'); + standard_error('stringerrordocumentnotvalidforlighty', '', $throw_exception); } elseif(substr($errdoc, -1) != '"') { $errdoc .= '"'; } } } else { if (Settings::Get('system.webserver') == 'lighttpd') { - standard_error('urlerrordocumentnotvalidforlighty'); + standard_error('urlerrordocumentnotvalidforlighty', '', $throw_exception); } } } From be0099bf01d502943f341eb42f0b613eaac5ebb4 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Fri, 16 Mar 2018 09:18:02 +0100 Subject: [PATCH 154/746] add duplicate check to switch-server-ip script Signed-off-by: Michael Kaufmann (d00p) --- install/scripts/switch-server-ip.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/install/scripts/switch-server-ip.php b/install/scripts/switch-server-ip.php index b8fcbe4c..362d1add 100755 --- a/install/scripts/switch-server-ip.php +++ b/install/scripts/switch-server-ip.php @@ -164,6 +164,7 @@ class Action } if (count($ips_to_switch) > 0) { + $check_stmt = Database::prepare("SELECT `id` FROM panel_ipsandports WHERE `ip` = :newip"); $upd_stmt = Database::prepare("UPDATE panel_ipsandports SET `ip` = :newip WHERE `ip` = :oldip"); // system.ipaddress @@ -180,6 +181,13 @@ class Action foreach ($ips_to_switch as $ip_pair) { echo "Switching IP \033[1m" . $ip_pair[0] . "\033[0m to IP \033[1m" . $ip_pair[1] . "\033[0m" . PHP_EOL; + + $ip_check = Database::pexecute_first($check_stmt, array('newip' => $ip_pair[1])); + if ($ip_check) { + CmdLineHandler::printwarn("Note: " . $ip_pair[0] . " not updated to " . $ip_pair[1] . " - IP already exists in froxlor's database"); + continue; + } + Database::pexecute($upd_stmt, array( 'newip' => $ip_pair[1], 'oldip' => $ip_pair[0] @@ -235,6 +243,9 @@ class Action if (! file_exists(FROXLOR_INSTALL_DIR . '/lib/userdata.inc.php')) { throw new Exception("Could not find froxlor's userdata.inc.php file. You should use this script only with a fully installed and setup froxlor system."); } + require FROXLOR_INSTALL_DIR . '/lib/functions/filedir/function.makeSecurePath.php'; + require FROXLOR_INSTALL_DIR . '/lib/functions/filedir/function.makeCorrectDir.php'; + require FROXLOR_INSTALL_DIR . '/lib/functions/filedir/function.makeCorrectFile.php'; require FROXLOR_INSTALL_DIR . '/lib/classes/database/class.Database.php'; } From f5654d5931e96af03060ee3a3c8bf49b54bac90c Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sun, 18 Mar 2018 08:42:22 +0100 Subject: [PATCH 155/746] fix var-names in DirOptions-ApiCommand; fix pmd issues Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/abstract.ApiCommand.php | 7 +++++-- lib/classes/api/commands/class.DirOptions.php | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index 77b3c58d..6f1351ca 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -119,7 +119,6 @@ abstract class ApiCommand extends ApiParameter } $this->initLang(); - $this->lng = $lng; $this->initMail(); if ($this->debug) { @@ -133,7 +132,8 @@ abstract class ApiCommand extends ApiParameter */ private function initLang() { - global $lng; + $lng = array(); + // query the whole table $result_stmt = Database::query("SELECT * FROM `" . TABLE_PANEL_LANGUAGE . "`"); @@ -174,6 +174,9 @@ abstract class ApiCommand extends ApiParameter // last but not least include language references file include_once makeSecurePath(FROXLOR_INSTALL_DIR . '/lng/lng_references.php'); + + // set array for ApiCommand + $this->lng = $lng; } /** diff --git a/lib/classes/api/commands/class.DirOptions.php b/lib/classes/api/commands/class.DirOptions.php index 8325f9f9..2bb75e83 100644 --- a/lib/classes/api/commands/class.DirOptions.php +++ b/lib/classes/api/commands/class.DirOptions.php @@ -106,7 +106,7 @@ class DirOptions extends ApiCommand implements ResourceEntity ); Database::pexecute($stmt, $params, true, true); $id = Database::lastInsertId(); - $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] added directory-protection for '" . $username . " (" . $path . ")'"); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] added directory-option for '" . $userpath . "'"); inserttask('1'); $result = $this->apiCall('DirOptions.get', array( @@ -350,7 +350,7 @@ class DirOptions extends ApiCommand implements ResourceEntity "customerid" => $customer_data['customerid'], "id" => $id )); - $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] deleted directory-option for '" . str_replace($userinfo['documentroot'], '/', $result['path']) . "'"); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] deleted directory-option for '" . str_replace($customer_data['documentroot'], '/', $result['path']) . "'"); inserttask('1'); return $this->response(200, "successfull", $result); } From dfcb7160cbee95c01615d1481b6160dfbf772a68 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sun, 18 Mar 2018 08:45:20 +0100 Subject: [PATCH 156/746] fix global lng-array Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/abstract.ApiCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/classes/api/abstract.ApiCommand.php b/lib/classes/api/abstract.ApiCommand.php index 6f1351ca..2806a1d6 100644 --- a/lib/classes/api/abstract.ApiCommand.php +++ b/lib/classes/api/abstract.ApiCommand.php @@ -132,7 +132,7 @@ abstract class ApiCommand extends ApiParameter */ private function initLang() { - $lng = array(); + global $lng; // query the whole table $result_stmt = Database::query("SELECT * FROM `" . TABLE_PANEL_LANGUAGE . "`"); From 715e5f7a642902d6aada70fbd7ddd863faf0f69b Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 19 Mar 2018 10:45:12 +0100 Subject: [PATCH 157/746] fix update of domain as admin if domain is a std-subdomain; fix update of mysql-entry; add CustomerBackups-ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- customer_extras.php | 68 +++------ .../api/commands/class.CustomerBackups.php | 139 ++++++++++++++++++ lib/classes/api/commands/class.Domains.php | 3 +- lib/classes/api/commands/class.Mysqls.php | 12 +- 4 files changed, 159 insertions(+), 63 deletions(-) create mode 100644 lib/classes/api/commands/class.CustomerBackups.php diff --git a/customer_extras.php b/customer_extras.php index 0124b9b8..80bb01db 100644 --- a/customer_extras.php +++ b/customer_extras.php @@ -331,74 +331,42 @@ if ($page == 'overview') { { if ($action == 'abort' && isset($_POST['send']) && $_POST['send'] == 'send') { $log->logAction(USR_ACTION, LOG_NOTICE, "customer_extras::backup - aborted scheduled backupjob"); - $entry = isset($_POST['backup_job_entry']) ? (int)$_POST['backup_job_entry'] : 0; - if ($entry > 0) { - $del_stmt = Database::prepare("DELETE FROM `".TABLE_PANEL_TASKS."` WHERE `id` = :tid"); - Database::pexecute($del_stmt, array('tid' => $entry)); - standard_success('backupaborted'); + try { + CustomerBackups::getLocal($userinfo, $_POST)->delete(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } + standard_success('backupaborted'); redirectTo($filename, array('page' => $page, 'action' => '', 's' => $s)); } if ($action == '') { $log->logAction(USR_ACTION, LOG_NOTICE, "viewed customer_extras::backup"); // check whether there is a backup-job for this customer - $sel_stmt = Database::prepare("SELECT * FROM `".TABLE_PANEL_TASKS."` WHERE `type` = '20'"); - Database::pexecute($sel_stmt); + try { + $json_result = CustomerBackups::getLocal($userinfo)->listing(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } + $result = json_decode($json_result, true)['data']; $existing_backupJob = null; - while ($entry = $sel_stmt->fetch()) + if ($result['count'] > 0) { - $data = unserialize($entry['data']); - if ($data['customerid'] == $userinfo['customerid']) { - $existing_backupJob = $entry; - break; - } + $existing_backupJob = array_shift($result['list']); } if (isset($_POST['send']) && $_POST['send'] == 'send') { - - if (! $_POST['path']) { - standard_error('invalidpath'); + try { + CustomerBackups::getLocal($userinfo, $_POST)->add(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } - - $path = makeCorrectDir(validate($_POST['path'], 'path')); - $path = makeCorrectDir($userinfo['documentroot'] . '/' . $path); - - $backup_dbs = isset($_POST['backup_dbs']) ? intval($_POST['backup_dbs']) : 0; - $backup_mail = isset($_POST['backup_mail']) ? intval($_POST['backup_mail']) : 0; - $backup_web = isset($_POST['backup_web']) ? intval($_POST['backup_web']) : 0; - - if ($backup_dbs != '1') { - $backup_dbs = '0'; - } - - if ($backup_mail != '1') { - $backup_mail = '0'; - } - - if ($backup_web != '1') { - $backup_web = '0'; - } - - $task_data = array( - 'customerid' => $userinfo['customerid'], - 'uid' => $userinfo['guid'], - 'gid' => $userinfo['guid'], - 'loginname' => $userinfo['loginname'], - 'destdir' => $path, - 'backup_dbs' => $backup_dbs, - 'backup_mail' => $backup_mail, - 'backup_web' => $backup_web - ); - // schedule backup job - inserttask('20', $task_data); - standard_success('backupscheduled'); } else { if (!empty($existing_backupJob)) { $action = "abort"; - $row = unserialize($entry['data']); + $row = $existing_backupJob['data']; $row['path'] = makeCorrectDir(str_replace($userinfo['documentroot'], "/", $row['destdir'])); $row['backup_web'] = ($row['backup_web'] == '1') ? $lng['panel']['yes'] : $lng['panel']['no']; $row['backup_mail'] = ($row['backup_mail'] == '1') ? $lng['panel']['yes'] : $lng['panel']['no']; diff --git a/lib/classes/api/commands/class.CustomerBackups.php b/lib/classes/api/commands/class.CustomerBackups.php new file mode 100644 index 00000000..5744a954 --- /dev/null +++ b/lib/classes/api/commands/class.CustomerBackups.php @@ -0,0 +1,139 @@ + (2010-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package API + * @since 0.10.0 + * + */ +class CustomerBackups extends ApiCommand implements ResourceEntity +{ + + private function validateAccess() + { + if (Settings::Get('system.backupenabled') != 1) { + throw new Exception("You cannot access this resource", 405); + } + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras')) { + throw new Exception("You cannot access this resource", 405); + } + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras.backup')) { + throw new Exception("You cannot access this resource", 405); + } + } + + public function add() + { + $this->validateAccess(); + + // required parameter + $path = $this->getParam('path'); + + // parameter + $backup_dbs = $this->getParam('backup_dbs', true, 0); + $backup_mail = $this->getParam('backup_mail', true, 0); + $backup_web = $this->getParam('backup_web', true, 0); + + // get customer data + $customer = $this->getCustomerData(); + + // validation + $path = makeCorrectDir(validate($path, 'path', '', '', array(), true)); + $userpath = $path; + $path = makeCorrectDir($customer['documentroot'] . '/' . $path); + + if ($backup_dbs != '1') { + $backup_dbs = '0'; + } + + if ($backup_mail != '1') { + $backup_mail = '0'; + } + + if ($backup_web != '1') { + $backup_web = '0'; + } + + $task_data = array( + 'customerid' => $customer['customerid'], + 'uid' => $customer['guid'], + 'gid' => $customer['guid'], + 'loginname' => $customer['loginname'], + 'destdir' => $path, + 'backup_dbs' => $backup_dbs, + 'backup_mail' => $backup_mail, + 'backup_web' => $backup_web + ); + // schedule backup job + inserttask('20', $task_data); + + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_NOTICE, "[API] added customer-backup job for '" . $customer['loginname'] . "'. Target directory: " . $userpath); + return $this->response(200, "successfull", $task_data); + } + + public function get() + { + throw new Exception('You cannot get a planned backup. Try CustomerBackups.listing()', 303); + } + + public function update() + { + throw new Exception('You cannot update a planned backup. You need to delete it and re-add it.', 303); + } + + public function listing() + { + $this->validateAccess(); + + $customer_ids = $this->getAllowedCustomerIds('extras.backup'); + + // check whether there is a backup-job for this customer + $sel_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` = '20'"); + Database::pexecute($sel_stmt); + $result = array(); + while ($entry = $sel_stmt->fetch(PDO::FETCH_ASSOC)) { + $entry['data'] = unserialize($entry['data']); + if (in_array($entry['data']['customerid'], $customer_ids)) { + $result[] = $entry; + } + } + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_NOTICE, "[API] list customer-backups"); + return $this->response(200, "successfull", array( + 'count' => count($result), + 'list' => $result + )); + } + + public function delete() + { + // get planned backups + $result = $this->apiCall('CustomerBackups.listing', $this->getParamList()); + + $entry = $this->getParam('backup_job_entry'); + $customer_ids = $this->getAllowedCustomerIds('extras.backup'); + + if ($result['count'] > 0 && $entry > 0) { + // prepare statement + $del_stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_TASKS . "` WHERE `id` = :tid"); + // check for the correct job + foreach ($result['list'] as $backupjob) { + if ($backupjob['id'] == $entry) { + Database::pexecute($del_stmt, array( + 'tid' => $entry + )); + return $this->response(200, "successfull", true); + } + } + } + throw new Exception('Backup job with id #' . $entry . ' could not be found', 404); + } +} diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index 364198a7..76f620c7 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -626,8 +626,7 @@ class Domains extends ApiCommand implements ResourceEntity // get requested domain $result = $this->apiCall('Domains.get', array( 'id' => $id, - 'domainname' => $domainname, - 'no_std_subdomain' => true + 'domainname' => $domainname )); $id = $result['id']; diff --git a/lib/classes/api/commands/class.Mysqls.php b/lib/classes/api/commands/class.Mysqls.php index a68f57eb..080b3e1a 100644 --- a/lib/classes/api/commands/class.Mysqls.php +++ b/lib/classes/api/commands/class.Mysqls.php @@ -305,17 +305,7 @@ class Mysqls extends ApiCommand implements ResourceEntity // validation $password = validate($password, 'password', '', '', array(), true); $databasedescription = validate(trim($databasedescription), 'description', '', '', array(), true); - - // validate whether the dbserver exists - $dbserver = validate($dbserver, html_entity_decode($this->lng['mysql']['mysql_server']), '', '', 0, true); - Database::needRoot(true, $dbserver); - Database::needSqlData(); - $sql_root = Database::getSqlData(); - Database::needRoot(false); - if (! isset($sql_root) || ! is_array($sql_root)) { - throw new ErrorException("Database server with index #" . $dbserver . " is unknown", 404); - } - + // get needed customer info to reduce the mysql-usage-counter by one $customer = $this->getCustomerData(); From bf589cdec8e58063824eb139945fe497a797ff47 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 19 Mar 2018 10:52:38 +0100 Subject: [PATCH 158/746] forgot to check for customer-id in CustomerBackups.delete() Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.CustomerBackups.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/classes/api/commands/class.CustomerBackups.php b/lib/classes/api/commands/class.CustomerBackups.php index 5744a954..92cfd09a 100644 --- a/lib/classes/api/commands/class.CustomerBackups.php +++ b/lib/classes/api/commands/class.CustomerBackups.php @@ -126,7 +126,7 @@ class CustomerBackups extends ApiCommand implements ResourceEntity $del_stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_TASKS . "` WHERE `id` = :tid"); // check for the correct job foreach ($result['list'] as $backupjob) { - if ($backupjob['id'] == $entry) { + if ($backupjob['id'] == $entry && in_array($backupjob['data']['customerid'], $customer_ids)) { Database::pexecute($del_stmt, array( 'tid' => $entry )); From 975d46044d67a53e7523334cf5eca445c079d839 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 19 Mar 2018 20:38:59 +0100 Subject: [PATCH 159/746] added unit-tests for DirOptions Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.DirOptions.php | 6 +- tests/Extras/DirOptionsTest.php | 184 ++++++++++++++++++ ...{ExtrasTest.php => DirProtectionsTest.php} | 2 +- 3 files changed, 188 insertions(+), 4 deletions(-) create mode 100644 tests/Extras/DirOptionsTest.php rename tests/Extras/{ExtrasTest.php => DirProtectionsTest.php} (99%) diff --git a/lib/classes/api/commands/class.DirOptions.php b/lib/classes/api/commands/class.DirOptions.php index 2bb75e83..957d1bdd 100644 --- a/lib/classes/api/commands/class.DirOptions.php +++ b/lib/classes/api/commands/class.DirOptions.php @@ -74,7 +74,7 @@ class DirOptions extends ApiCommand implements ResourceEntity SELECT `id`, `path` FROM `" . TABLE_PANEL_HTACCESS . "` WHERE `path`= :path AND `customerid`= :customerid "); - $path_dupe_check = Database::pexecute($path_dupe_check_stmt, array( + $path_dupe_check = Database::pexecute_first($path_dupe_check_stmt, array( "path" => $path, "customerid" => $customer['customerid'] ), true, true); @@ -222,7 +222,7 @@ class DirOptions extends ApiCommand implements ResourceEntity $error500path = correctErrorDocument($error500path, true); } - if (($option_indexes != $result['options_indexes']) || ($error404path != $result['error404path']) || ($error403path != $result['error403path']) || ($error500path != $result['error500path']) || ($options_cgi != $result['options_cgi'])) { + if (($options_indexes != $result['options_indexes']) || ($error404path != $result['error404path']) || ($error403path != $result['error403path']) || ($error500path != $result['error500path']) || ($options_cgi != $result['options_cgi'])) { inserttask('1'); $stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_HTACCESS . "` @@ -236,7 +236,7 @@ class DirOptions extends ApiCommand implements ResourceEntity "); $params = array( "customerid" => $customer['customerid'], - "options_indexes" => $option_indexes, + "options_indexes" => $options_indexes, "error403path" => $error403path, "error404path" => $error404path, "error500path" => $error500path, diff --git a/tests/Extras/DirOptionsTest.php b/tests/Extras/DirOptionsTest.php new file mode 100644 index 00000000..fee67464 --- /dev/null +++ b/tests/Extras/DirOptionsTest.php @@ -0,0 +1,184 @@ + 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'path' => '/test', + 'options_indexes' => 1, + 'options_cgi' => 1, + 'error404path' => '/404.html', + 'error403path' => '/403.html', + 'error500path' => '/500.html' + ]; + $json_result = DirOptions::getLocal($customer_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals($customer_userdata['documentroot'] . 'test/', $result['path']); + $this->assertEquals('1', $result['options_cgi']); + $this->assertEquals('/403.html', $result['error403path']); + } + + public function testCustomerDirOptionsAddDuplicatePath() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'path' => '/test', + 'options_indexes' => 0, + 'options_cgi' => 0, + 'error404path' => '/404a.html', + 'error403path' => '/403a.html', + 'error500path' => '/500a.html' + ]; + $this->expectExceptionMessage("Option for path /test/ already exists"); + DirOptions::getLocal($customer_userdata, $data)->add(); + } + + public function testAdminDirOptionsGet() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'id' => 1, + 'loginname' => 'test1' + ]; + $json_result = DirOptions::getLocal($admin_userdata, $data)->get(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals($customer_userdata['documentroot'] . 'test/', $result['path']); + } + + public function testResellerDirOptionsGet() + { + global $admin_userdata; + // get reseller + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller' + ))->get(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'id' => 1, + 'loginname' => 'test1' + ]; + $json_result = DirOptions::getLocal($reseller_userdata, $data)->get(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals($customer_userdata['documentroot'] . 'test/', $result['path']); + } + + public function testCustomerDirOptionsGetNotFound() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'id' => 1337 + ]; + $this->expectExceptionMessage("Directory option with id #1337 could not be found"); + DirOptions::getLocal($admin_userdata, $data)->get(); + } + + public function testCustomerDirOptionsUpdate() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'id' => 1, + 'options_indexes' => 0, + 'options_cgi' => 0, + 'error403path' => '/403-test.html' + ]; + $json_result = DirOptions::getLocal($customer_userdata, $data)->update(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals($customer_userdata['documentroot'] . 'test/', $result['path']); + $this->assertEquals('0', $result['options_cgi']); + $this->assertEquals('/403-test.html', $result['error403path']); + } + + public function testAdminDirOptionsList() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $json_result = DirOptions::getLocal($admin_userdata)->listing(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(1, $result['count']); + $this->assertEquals($customer_userdata['documentroot'] . 'test/', $result['list'][0]['path']); + } + + /** + * @depends testAdminDirOptionsList + */ + public function testCustomerDirOptionsDelete() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'id' => 1 + ]; + $json_result = DirOptions::getLocal($customer_userdata, $data)->delete(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals($customer_userdata['documentroot'] . 'test/', $result['path']); + + $data = [ + 'id' => 1 + ]; + $this->expectExceptionMessage("Directory option with id #1 could not be found"); + DirOptions::getLocal($admin_userdata, $data)->get(); + } +} diff --git a/tests/Extras/ExtrasTest.php b/tests/Extras/DirProtectionsTest.php similarity index 99% rename from tests/Extras/ExtrasTest.php rename to tests/Extras/DirProtectionsTest.php index 5e4f36cf..f4046143 100644 --- a/tests/Extras/ExtrasTest.php +++ b/tests/Extras/DirProtectionsTest.php @@ -6,7 +6,7 @@ use PHPUnit\Framework\TestCase; * @covers ApiParameter * @covers DirProtections */ -class ExtrasTest extends TestCase +class DirProtectionsTest extends TestCase { public function testCustomerDirProtectionsAdd() From 190c95bacae679b68265214166c5256045ac6c7e Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 19 Mar 2018 21:25:23 +0100 Subject: [PATCH 160/746] created DomainZones ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- dns_editor.php | 256 ++----------- .../api/commands/class.DomainZones.php | 342 ++++++++++++++++++ tests/Extras/DirProtectionsTest.php | 2 +- 3 files changed, 372 insertions(+), 228 deletions(-) create mode 100644 lib/classes/api/commands/class.DomainZones.php diff --git a/dns_editor.php b/dns_editor.php index cd720fdc..7099d0cc 100644 --- a/dns_editor.php +++ b/dns_editor.php @@ -45,234 +45,31 @@ $success_message = ""; // action for adding a new entry if ($action == 'add_record' && ! empty($_POST)) { - - // validation - if (empty($record)) { - $record = "@"; - } - - $record = strtolower($record); - - if ($record != '@' && $record != '*') { - // validate record - if (strpos($record, '--') !== false) { - $errors[] = $lng['error']['domain_nopunycode']; - } else { - // check for wildcard-record - $add_wildcard_again = false; - if (substr($record, 0, 2) == '*.') { - $record = substr($record, 2); - $add_wildcard_again = true; - } - // convert entry - $record = $idna_convert->encode($record); - - if ($add_wildcard_again) { - $record = '*.'.$record; - } - - /* - * see https://redmine.froxlor.org/issues/1697 - * - if ($type != 'SRV' && $type != 'TXT') { - $check_dom = $record . '.example.com'; - if (! validateDomain($check_dom)) { - $errors[] = sprintf($lng['error']['subdomainiswrong'], $idna_convert->decode($record)); - } - } - */ - if (strlen($record) > 63) { - $errors[] = $lng['error']['dns_record_toolong']; - } - } - } - - // TODO regex validate content for invalid characters - - if ($ttl <= 0) { - $ttl = 18000; - } - - if (empty($content)) { - $errors[] = $lng['error']['dns_content_empty']; - } - - // types - if ($type == 'A' && filter_var($content, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false) { - $errors[] = $lng['error']['dns_arec_noipv4']; - } elseif ($type == 'AAAA' && filter_var($content, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) { - $errors[] = $lng['error']['dns_aaaarec_noipv6']; - } elseif ($type == 'MX') { - if ($prio === null || $prio < 0) { - $errors[] = $lng['error']['dns_mx_prioempty']; - } - // check for trailing dot - if (substr($content, - 1) == '.') { - // remove it for checks - $content = substr($content, 0, - 1); - } - if (! validateDomain($content)) { - $errors[] = $lng['error']['dns_mx_needdom']; - } else { - // check whether there is a CNAME-record for the same resource - foreach ($dom_entries as $existing_entries) { - $fqdn = $existing_entries['record'] . '.' . $domain; - if ($existing_entries['type'] == 'CNAME' && $fqdn == $content) { - $errors[] = $lng['error']['dns_mx_noalias']; - break; - } - } - } - // append trailing dot (again) - $content .= '.'; - } elseif ($type == 'CNAME') { - // check for trailing dot - if (substr($content, - 1) == '.') { - // remove it for checks - $content = substr($content, 0, - 1); - } else { - // add domain name - $content .= '.' . $domain; - } - if (! validateDomain($content)) { - $errors[] = $lng['error']['dns_cname_invaliddom']; - } else { - // check whether there are RR-records for the same resource - foreach ($dom_entries as $existing_entries) { - if (($existing_entries['type'] == 'A' || $existing_entries['type'] == 'AAAA' || $existing_entries['type'] == 'MX' || $existing_entries['type'] == 'NS') && $existing_entries['record'] == $record) { - $errors[] = $lng['error']['dns_cname_nomorerr']; - break; - } - } - } - // append trailing dot (again) - $content .= '.'; - } elseif ($type == 'NS') { - // check for trailing dot - if (substr($content, - 1) == '.') { - // remove it for checks - $content = substr($content, 0, - 1); - } - if (! validateDomain($content)) { - $errors[] = $lng['error']['dns_ns_invaliddom']; - } - // append trailing dot (again) - $content .= '.'; - } elseif ($type == 'TXT' && ! empty($content)) { - // check that TXT content is enclosed in " " - $content = encloseTXTContent($content); - } elseif ($type == 'SRV') { - if ($prio === null || $prio < 0) { - $errors[] = $lng['error']['dns_srv_prioempty']; - } - // check only last part of content, as it can look like: - // _service._proto.name. TTL class SRV priority weight port target. - $_split_content = explode(" ", $content); - // SRV content must be [weight] [port] [target] - if (count($_split_content) != 3) { - $errors[] = $lng['error']['dns_srv_invalidcontent']; - } - $target = trim($_split_content[count($_split_content) - 1]); - if ($target != '.') { - // check for trailing dot - if (substr($target, - 1) == '.') { - // remove it for checks - $target = substr($target, 0, - 1); - } - } - if ($target != '.' && ! validateDomain($target)) { - $errors[] = $lng['error']['dns_srv_needdom']; - } else { - // check whether there is a CNAME-record for the same resource - foreach ($dom_entries as $existing_entries) { - $fqdn = $existing_entries['record'] . '.' . $domain; - if ($existing_entries['type'] == 'CNAME' && $fqdn == $target) { - $errors[] = $lng['error']['dns_srv_noalias']; - break; - } - } - } - // append trailing dot if there's none - if (substr($content, - 1) != '.') { - $content .= '.'; - } - } - - $new_entry = array( - 'record' => $record, - 'type' => $type, - 'prio' => $prio, - 'content' => $content, - 'ttl' => $ttl, - 'domain_id' => $domain_id - ); - ksort($new_entry); - - // check for duplicate - foreach ($dom_entries as $existing_entry) { - // compare serialized string of array - $check_entry = $existing_entry; - // new entry has no ID yet - unset($check_entry['id']); - // sort by key - ksort($check_entry); - // format integer fields to real integer (as they are read as string from the DB) - $check_entry['prio'] = (int) $check_entry['prio']; - $check_entry['ttl'] = (int) $check_entry['ttl']; - $check_entry['domain_id'] = (int) $check_entry['domain_id']; - // serialize both - $check_entry = serialize($check_entry); - $new = serialize($new_entry); - // compare - if ($check_entry === $new) { - $errors[] = $lng['error']['dns_duplicate_entry']; - unset($check_entry); - break; - } - } - - if (empty($errors)) { - $ins_stmt = Database::prepare(" - INSERT INTO `" . TABLE_DOMAIN_DNS . "` SET - `record` = :record, - `type` = :type, - `prio` = :prio, - `content` = :content, - `ttl` = :ttl, - `domain_id` = :domain_id - "); - - Database::pexecute($ins_stmt, $new_entry); - - $new_entry_id = Database::lastInsertId(); - - // add temporary to the entries-array (no reread of DB necessary) - $new_entry['id'] = $new_entry_id; - $dom_entries[] = $new_entry; - - // success message (inline) + try { + DomainZones::getLocal($userinfo, array( + 'id' => $domain_id, + 'record' => $record, + 'type' => $type, + 'prio' => $prio, + 'content' => $content, + 'ttl' => $ttl + ))->add(); $success_message = $lng['success']['dns_record_added']; - - $record = ""; - $type = 'A'; - $prio = ""; - $content = ""; - $ttl = ""; - - // re-generate bind configs - inserttask('4'); - } else { - // show $errors - $errors = implode("
    ", $errors); + } catch (Exception $e) { + dynamic_error($e->getMessage()); } } elseif ($action == 'delete') { // remove entry $entry_id = isset($_GET['id']) ? (int) $_GET['id'] : 0; if ($entry_id > 0) { - $del_stmt = Database::prepare("DELETE FROM `" . TABLE_DOMAIN_DNS . "` WHERE `id` = :id"); - Database::pexecute($del_stmt, array( - 'id' => $entry_id - )); + try { + DomainZones::getLocal($userinfo, array( + 'entry_id' => $entry_id, + 'id' => $domain_id + ))->delete(); + } catch (Exception $e) { + dynamic_error($e->getMessage()); + } // remove deleted entry from internal data array (no reread of DB necessary) $_t = $dom_entries; @@ -285,9 +82,6 @@ if ($action == 'add_record' && ! empty($_POST)) { unset($_t); // success message (inline) $success_message = $lng['success']['dns_record_deleted']; - - // re-generate bind configs - inserttask('4'); } } @@ -322,6 +116,14 @@ foreach ($type_select_values as $_type) { eval("\$record_list=\"" . getTemplate("dns_editor/list", true) . "\";"); -$zone = createDomainZone($domain_id); -$zonefile = (string) $zone; +try { + $json_result = DomainZones::getLocal($userinfo, array( + 'id' => $domain_id + ))->get(); +} catch (Exception $e) { + dynamic_error($e->getMessage()); +} +$result = json_decode($json_result, true)['data']; +$zonefile = implode("\n", $result); + eval("echo \"" . getTemplate("dns_editor/index", true) . "\";"); diff --git a/lib/classes/api/commands/class.DomainZones.php b/lib/classes/api/commands/class.DomainZones.php new file mode 100644 index 00000000..dbdacad6 --- /dev/null +++ b/lib/classes/api/commands/class.DomainZones.php @@ -0,0 +1,342 @@ + (2010-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package API + * @since 0.10.0 + * + */ +class DomainZones extends ApiCommand implements ResourceEntity +{ + + public function add() + { + if (Settings::Get('system.dnsenabled') != '1') { + throw new Exception("DNS server not enabled on this system", 405); + } + + $id = $this->getParam('id', true, 0); + $dn_optional = ($id <= 0 ? false : true); + $domainname = $this->getParam('domainname', $dn_optional, ''); + + // get requested domain + $result = $this->apiCall('SubDomains.get', array( + 'id' => $id, + 'domainname' => $domainname + )); + $id = $result['id']; + + // parameters + $record = $this->getParam('record', true, null); + $type = $this->getParam('type', true, 'A'); + $prio = $this->getParam('prio', true, null); + $content = $this->getParam('content', true, null); + $ttl = $this->getParam('ttl', true, 18000); + + // validation + if ($result['isbinddomain'] != '1') { + standard_error('dns_domain_nodns', '', true); + } + + $idna_convert = new idna_convert_wrapper(); + $domain = $idna_convert->encode($result['domain']); + + // select all entries + $sel_stmt = Database::prepare("SELECT * FROM `" . TABLE_DOMAIN_DNS . "` WHERE domain_id = :did"); + Database::pexecute($sel_stmt, array( + 'did' => $id + ), true, true); + $dom_entries = $sel_stmt->fetchAll(PDO::FETCH_ASSOC); + + // validation + if (empty($record)) { + $record = "@"; + } + + $record = trim(strtolower($record)); + + if ($record != '@' && $record != '*') { + // validate record + if (strpos($record, '--') !== false) { + $errors[] = $this->lng['error']['domain_nopunycode']; + } else { + // check for wildcard-record + $add_wildcard_again = false; + if (substr($record, 0, 2) == '*.') { + $record = substr($record, 2); + $add_wildcard_again = true; + } + // convert entry + $record = $idna_convert->encode($record); + + if ($add_wildcard_again) { + $record = '*.' . $record; + } + + if (strlen($record) > 63) { + $errors[] = $this->lng['error']['dns_record_toolong']; + } + } + } + + // TODO regex validate content for invalid characters + + if ($ttl <= 0) { + $ttl = 18000; + } + + $content = trim($content); + if (empty($content)) { + $errors[] = $this->lng['error']['dns_content_empty']; + } + + // types + if ($type == 'A' && filter_var($content, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false) { + $errors[] = $this->lng['error']['dns_arec_noipv4']; + } elseif ($type == 'AAAA' && filter_var($content, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) { + $errors[] = $this->lng['error']['dns_aaaarec_noipv6']; + } elseif ($type == 'MX') { + if ($prio === null || $prio < 0) { + $errors[] = $this->lng['error']['dns_mx_prioempty']; + } + // check for trailing dot + if (substr($content, - 1) == '.') { + // remove it for checks + $content = substr($content, 0, - 1); + } + if (! validateDomain($content)) { + $errors[] = $this->lng['error']['dns_mx_needdom']; + } else { + // check whether there is a CNAME-record for the same resource + foreach ($dom_entries as $existing_entries) { + $fqdn = $existing_entries['record'] . '.' . $domain; + if ($existing_entries['type'] == 'CNAME' && $fqdn == $content) { + $errors[] = $this->lng['error']['dns_mx_noalias']; + break; + } + } + } + // append trailing dot (again) + $content .= '.'; + } elseif ($type == 'CNAME') { + // check for trailing dot + if (substr($content, - 1) == '.') { + // remove it for checks + $content = substr($content, 0, - 1); + } else { + // add domain name + $content .= '.' . $domain; + } + if (! validateDomain($content)) { + $errors[] = $this->lng['error']['dns_cname_invaliddom']; + } else { + // check whether there are RR-records for the same resource + foreach ($dom_entries as $existing_entries) { + if (($existing_entries['type'] == 'A' || $existing_entries['type'] == 'AAAA' || $existing_entries['type'] == 'MX' || $existing_entries['type'] == 'NS') && $existing_entries['record'] == $record) { + $errors[] = $this->lng['error']['dns_cname_nomorerr']; + break; + } + } + } + // append trailing dot (again) + $content .= '.'; + } elseif ($type == 'NS') { + // check for trailing dot + if (substr($content, - 1) == '.') { + // remove it for checks + $content = substr($content, 0, - 1); + } + if (! validateDomain($content)) { + $errors[] = $this->lng['error']['dns_ns_invaliddom']; + } + // append trailing dot (again) + $content .= '.'; + } elseif ($type == 'TXT' && ! empty($content)) { + // check that TXT content is enclosed in " " + $content = encloseTXTContent($content); + } elseif ($type == 'SRV') { + if ($prio === null || $prio < 0) { + $errors[] = $this->lng['error']['dns_srv_prioempty']; + } + // check only last part of content, as it can look like: + // _service._proto.name. TTL class SRV priority weight port target. + $_split_content = explode(" ", $content); + // SRV content must be [weight] [port] [target] + if (count($_split_content) != 3) { + $errors[] = $this->lng['error']['dns_srv_invalidcontent']; + } + $target = trim($_split_content[count($_split_content) - 1]); + if ($target != '.') { + // check for trailing dot + if (substr($target, - 1) == '.') { + // remove it for checks + $target = substr($target, 0, - 1); + } + } + if ($target != '.' && ! validateDomain($target)) { + $errors[] = $this->lng['error']['dns_srv_needdom']; + } else { + // check whether there is a CNAME-record for the same resource + foreach ($dom_entries as $existing_entries) { + $fqdn = $existing_entries['record'] . '.' . $domain; + if ($existing_entries['type'] == 'CNAME' && $fqdn == $target) { + $errors[] = $this->lng['error']['dns_srv_noalias']; + break; + } + } + } + // append trailing dot if there's none + if (substr($content, - 1) != '.') { + $content .= '.'; + } + } + + $new_entry = array( + 'record' => $record, + 'type' => $type, + 'prio' => $prio, + 'content' => $content, + 'ttl' => $ttl, + 'domain_id' => $id + ); + ksort($new_entry); + + // check for duplicate + foreach ($dom_entries as $existing_entry) { + // compare serialized string of array + $check_entry = $existing_entry; + // new entry has no ID yet + unset($check_entry['id']); + // sort by key + ksort($check_entry); + // format integer fields to real integer (as they are read as string from the DB) + $check_entry['prio'] = (int) $check_entry['prio']; + $check_entry['ttl'] = (int) $check_entry['ttl']; + $check_entry['domain_id'] = (int) $check_entry['domain_id']; + // serialize both + $check_entry = serialize($check_entry); + $new = serialize($new_entry); + // compare + if ($check_entry === $new) { + $errors[] = $this->lng['error']['dns_duplicate_entry']; + unset($check_entry); + break; + } + } + + if (empty($errors)) { + $ins_stmt = Database::prepare(" + INSERT INTO `" . TABLE_DOMAIN_DNS . "` SET + `record` = :record, + `type` = :type, + `prio` = :prio, + `content` = :content, + `ttl` = :ttl, + `domain_id` = :domain_id + "); + Database::pexecute($ins_stmt, $new_entry, true, true); + $new_entry_id = Database::lastInsertId(); + + // add temporary to the entries-array (no reread of DB necessary) + $new_entry['id'] = $new_entry_id; + $dom_entries[] = $new_entry; + + // re-generate bind configs + inserttask('4'); + + $result = $this->apiCall('DomainZones.get', array( + 'id' => $id + )); + return $this->response(200, "successfull", $result); + } + // return $errors + throw new Exception(implode("\n", $errors)); + } + + /** + * return a domain-dns entry by either id or domainname + * + * @param int $id + * optional, the domain-id + * @param string $domainname + * optional, the domainname + * + * @access admin + * @throws Exception + * @return array + */ + public function get() + { + if (Settings::Get('system.dnsenabled') != '1') { + throw new Exception("DNS server not enabled on this system", 405); + } + + $id = $this->getParam('id', true, 0); + $dn_optional = ($id <= 0 ? false : true); + $domainname = $this->getParam('domainname', $dn_optional, ''); + + // get requested domain + $result = $this->apiCall('SubDomains.get', array( + 'id' => $id, + 'domainname' => $domainname + )); + $id = $result['id']; + + if ($result['isbinddomain'] != '1') { + standard_error('dns_domain_nodns', '', true); + } + + $zone = createDomainZone($id); + $zonefile = (string) $zone; + + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_NOTICE, "[API] get dns-zone for '" . $result['domain'] . "'"); + return $this->response(200, "successfull", explode("\n", $zonefile)); + } + + public function update() + { + throw new Exception('You cannot update a dns zone entry. You need to delete it and re-add it.', 303); + } + + public function listing() + { + throw new Exception('You cannot list dns zones. To get all domains use Domains.listing() or SubDomains.listing()', 303); + } + + public function delete() + { + if (Settings::Get('system.dnsenabled') != '1') { + throw new Exception("DNS server not enabled on this system", 405); + } + + $entry_id = $this->getParam('entry_id'); + $id = $this->getParam('id', true, 0); + $dn_optional = ($id <= 0 ? false : true); + $domainname = $this->getParam('domainname', $dn_optional, ''); + + // get requested domain + $result = $this->apiCall('SubDomains.get', array( + 'id' => $id, + 'domainname' => $domainname + )); + $id = $result['id']; + + $del_stmt = Database::prepare("DELETE FROM `" . TABLE_DOMAIN_DNS . "` WHERE `id` = :id AND `domain_id` = :did"); + Database::pexecute($del_stmt, array( + 'id' => $entry_id, + 'did' => $id + ), true, true); + // re-generate bind configs + inserttask('4'); + return $this->response(200, "successfull", true); + } +} diff --git a/tests/Extras/DirProtectionsTest.php b/tests/Extras/DirProtectionsTest.php index f4046143..23c440b6 100644 --- a/tests/Extras/DirProtectionsTest.php +++ b/tests/Extras/DirProtectionsTest.php @@ -98,7 +98,7 @@ class DirProtectionsTest extends TestCase public function testResellerDirProtectionsGet() { global $admin_userdata; - // get customer + // get reseller $json_result = Admins::getLocal($admin_userdata, array( 'loginname' => 'reseller' ))->get(); From 5123b5fccdcfa0a07ef0f2ae98f036d00cebf5f3 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 20 Mar 2018 08:55:14 +0100 Subject: [PATCH 161/746] fix error-display in dns_editor.php Signed-off-by: Michael Kaufmann (d00p) --- dns_editor.php | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/dns_editor.php b/dns_editor.php index 7099d0cc..3f731dcd 100644 --- a/dns_editor.php +++ b/dns_editor.php @@ -40,7 +40,7 @@ Database::pexecute($sel_stmt, array( )); $dom_entries = $sel_stmt->fetchAll(PDO::FETCH_ASSOC); -$errors = array(); +$errors = ""; $success_message = ""; // action for adding a new entry @@ -68,20 +68,22 @@ if ($action == 'add_record' && ! empty($_POST)) { 'id' => $domain_id ))->delete(); } catch (Exception $e) { - dynamic_error($e->getMessage()); + $errors = str_replace("\n", "
    ", $e->getMessage()); } - // remove deleted entry from internal data array (no reread of DB necessary) - $_t = $dom_entries; - foreach ($_t as $idx => $entry) { - if ($entry['id'] == $entry_id) { - unset($dom_entries[$idx]); - break; + if (empty($errors)) { + // remove deleted entry from internal data array (no reread of DB necessary) + $_t = $dom_entries; + foreach ($_t as $idx => $entry) { + if ($entry['id'] == $entry_id) { + unset($dom_entries[$idx]); + break; + } } + unset($_t); + // success message (inline) + $success_message = $lng['success']['dns_record_deleted']; } - unset($_t); - // success message (inline) - $success_message = $lng['success']['dns_record_deleted']; } } From bd7f2c26546a252a49c88fe69350aa2546474e63 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Wed, 21 Mar 2018 20:22:43 +0100 Subject: [PATCH 162/746] add unit-tests for CustomerBackup-ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- .../api/commands/class.CustomerBackups.php | 5 +- phpunit.xml | 1 + tests/Backup/CustomerBackupsTest.php | 112 ++++++++++++++++++ 3 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 tests/Backup/CustomerBackupsTest.php diff --git a/lib/classes/api/commands/class.CustomerBackups.php b/lib/classes/api/commands/class.CustomerBackups.php index 92cfd09a..46dd10e3 100644 --- a/lib/classes/api/commands/class.CustomerBackups.php +++ b/lib/classes/api/commands/class.CustomerBackups.php @@ -117,10 +117,10 @@ class CustomerBackups extends ApiCommand implements ResourceEntity { // get planned backups $result = $this->apiCall('CustomerBackups.listing', $this->getParamList()); - + $entry = $this->getParam('backup_job_entry'); $customer_ids = $this->getAllowedCustomerIds('extras.backup'); - + if ($result['count'] > 0 && $entry > 0) { // prepare statement $del_stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_TASKS . "` WHERE `id` = :tid"); @@ -130,6 +130,7 @@ class CustomerBackups extends ApiCommand implements ResourceEntity Database::pexecute($del_stmt, array( 'tid' => $entry )); + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_NOTICE, "[API] deleted planned customer-backup #" . $entry); return $this->response(200, "successfull", true); } } diff --git a/phpunit.xml b/phpunit.xml index 3f800929..33b73837 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -18,6 +18,7 @@ tests/Ftps tests/Emails tests/Extras + tests/Backup diff --git a/tests/Backup/CustomerBackupsTest.php b/tests/Backup/CustomerBackupsTest.php new file mode 100644 index 00000000..210b36eb --- /dev/null +++ b/tests/Backup/CustomerBackupsTest.php @@ -0,0 +1,112 @@ + 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'path' => '/my-backup', + 'backup_dbs' => 1, + 'backup_mail' => 2, + 'backup_web' => 1 + ]; + $json_result = CustomerBackups::getLocal($customer_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals($customer_userdata['documentroot'] . 'my-backup/', $result['destdir']); + $this->assertEquals('1', $result['backup_dbs']); + $this->assertEquals('0', $result['backup_mail']); + $this->assertEquals('1', $result['backup_web']); + } + + public function testAdminCustomerBackupsGet() + { + global $admin_userdata; + $this->expectExceptionCode(303); + CustomerBackups::getLocal($admin_userdata)->get(); + } + + public function testAdminCustomerBackupsUpdate() + { + global $admin_userdata; + $this->expectExceptionCode(303); + CustomerBackups::getLocal($admin_userdata)->update(); + } + + /** + * + * @depends testCustomerCustomerBackupsAdd + */ + public function testAdminCustomerBackupsListing() + { + global $admin_userdata; + + $json_result = CustomerBackups::getLocal($admin_userdata)->listing(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(1, $result['count']); + $this->assertEquals('1', $result['list'][0]['data']['backup_dbs']); + $this->assertEquals('0', $result['list'][0]['data']['backup_mail']); + $this->assertEquals('1', $result['list'][0]['data']['backup_web']); + } + + /** + * + * @depends testCustomerCustomerBackupsAdd + */ + public function testCustomerCustomerBackupsDelete() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'backup_job_entry' => 1 + ]; + $json_result = CustomerBackups::getLocal($customer_userdata, $data)->delete(); + $result = json_decode($json_result, true)['data']; + $this->assertTrue($result); + } + + /** + * + * @depends testAdminCustomerBackupsListing + */ + public function testCustomerCustomerBackupsDeleteNotFound() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'backup_job_entry' => 1337 + ]; + $this->expectExceptionCode(404); + $this->expectExceptionMessage('Backup job with id #1337 could not be found'); + CustomerBackups::getLocal($customer_userdata, $data)->delete(); + } +} From 48d71107790e7426278a5d499732ecaa925e582e Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 22 Mar 2018 14:56:18 +0100 Subject: [PATCH 163/746] add first unit tests for DomainZones ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- .../api/commands/class.DomainZones.php | 22 ++++-- lib/classes/api/commands/class.SubDomains.php | 6 +- phpunit.xml | 1 + tests/DomainZones/DomainZonesTest.php | 68 +++++++++++++++++++ tests/Domains/DomainsTest.php | 3 +- 5 files changed, 90 insertions(+), 10 deletions(-) create mode 100644 tests/DomainZones/DomainZonesTest.php diff --git a/lib/classes/api/commands/class.DomainZones.php b/lib/classes/api/commands/class.DomainZones.php index dbdacad6..0a7dcdf3 100644 --- a/lib/classes/api/commands/class.DomainZones.php +++ b/lib/classes/api/commands/class.DomainZones.php @@ -42,8 +42,11 @@ class DomainZones extends ApiCommand implements ResourceEntity $content = $this->getParam('content', true, null); $ttl = $this->getParam('ttl', true, 18000); - // validation - if ($result['isbinddomain'] != '1') { + if ($result['parentdomainid'] != '0') { + throw new Exception("DNS zones can only be generated for the main domain, not for subdomains", 406); + } + + if ($result['subisbinddomain'] != '1') { standard_error('dns_domain_nodns', '', true); } @@ -291,7 +294,11 @@ class DomainZones extends ApiCommand implements ResourceEntity )); $id = $result['id']; - if ($result['isbinddomain'] != '1') { + if ($result['parentdomainid'] != '0') { + throw new Exception("DNS zones can only be generated for the main domain, not for subdomains", 406); + } + + if ($result['subisbinddomain'] != '1') { standard_error('dns_domain_nodns', '', true); } @@ -335,8 +342,11 @@ class DomainZones extends ApiCommand implements ResourceEntity 'id' => $entry_id, 'did' => $id ), true, true); - // re-generate bind configs - inserttask('4'); - return $this->response(200, "successfull", true); + if ($del_stmt->rowCount() > 0) { + // re-generate bind configs + inserttask('4'); + return $this->response(200, "successfull", true); + } + return $this->response(304, "successfull", true); } } diff --git a/lib/classes/api/commands/class.SubDomains.php b/lib/classes/api/commands/class.SubDomains.php index 9159587f..b7be8393 100644 --- a/lib/classes/api/commands/class.SubDomains.php +++ b/lib/classes/api/commands/class.SubDomains.php @@ -347,7 +347,7 @@ class SubDomains extends ApiCommand implements ResourceEntity } if (count($customer_ids) > 0) { $result_stmt = Database::prepare(" - SELECT d.*, pd.`subcanemaildomain` + SELECT d.*, pd.`subcanemaildomain`, pd.`isbinddomain` as subisbinddomain FROM `" . TABLE_PANEL_DOMAINS . "` d, `" . TABLE_PANEL_DOMAINS . "` pd WHERE " . ($id > 0 ? "d.`id` = :iddn" : "d.`domain` = :iddn") . " AND d.`customerid` IN (:customerids) AND ((d.`parentdomainid`!='0' AND pd.`id` = d.`parentdomainid`) OR (d.`parentdomainid`='0' AND pd.`id` = d.`id`)) @@ -361,7 +361,7 @@ class SubDomains extends ApiCommand implements ResourceEntity } } else { $result_stmt = Database::prepare(" - SELECT d.*, pd.`subcanemaildomain` + SELECT d.*, pd.`subcanemaildomain`, pd.`isbinddomain` as subisbinddomain FROM `" . TABLE_PANEL_DOMAINS . "` d, `" . TABLE_PANEL_DOMAINS . "` pd WHERE " . ($id > 0 ? "d.`id` = :iddn" : "d.`domain` = :iddn") . " AND ((d.`parentdomainid`!='0' AND pd.`id` = d.`parentdomainid`) OR (d.`parentdomainid`='0' AND pd.`id` = d.`id`)) @@ -375,7 +375,7 @@ class SubDomains extends ApiCommand implements ResourceEntity throw new Exception("You cannot access this resource", 405); } $result_stmt = Database::prepare(" - SELECT d.*, pd.`subcanemaildomain` + SELECT d.*, pd.`subcanemaildomain`, pd.`isbinddomain` as subisbinddomain FROM `" . TABLE_PANEL_DOMAINS . "` d, `" . TABLE_PANEL_DOMAINS . "` pd WHERE d.`customerid`= :customerid AND " . ($id > 0 ? "d.`id` = :iddn" : "d.`domain` = :iddn") . " AND ((d.`parentdomainid`!='0' AND pd.`id` = d.`parentdomainid`) OR (d.`parentdomainid`='0' AND pd.`id` = d.`id`)) diff --git a/phpunit.xml b/phpunit.xml index 33b73837..ceeb519c 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -19,6 +19,7 @@ tests/Emails tests/Extras tests/Backup + tests/DomainZones diff --git a/tests/DomainZones/DomainZonesTest.php b/tests/DomainZones/DomainZonesTest.php new file mode 100644 index 00000000..3833084f --- /dev/null +++ b/tests/DomainZones/DomainZonesTest.php @@ -0,0 +1,68 @@ + 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'domainname' => 'test2.local' + ]; + $json_result = DomainZones::getLocal($customer_userdata, $data)->get(); + $result = json_decode($json_result, true)['data']; + $this->assertTrue(count($result) > 1); + $this->assertEquals('$ORIGIN test2.local.', $result[1]); + } + + public function testCustomerDomainZonesGetNoSubdomains() + { + global $admin_userdata; + + Settings::Set('system.dnsenabled', 1, true); + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'domainname' => 'mysub2.test2.local' + ]; + $this->expectExceptionCode(406); + $this->expectExceptionMessage("DNS zones can only be generated for the main domain, not for subdomains"); + DomainZones::getLocal($customer_userdata, $data)->get(); + } + + public function testAdminDomainZonesListing() + { + global $admin_userdata; + $this->expectExceptionCode(303); + DomainZones::getLocal($admin_userdata)->listing(); + } + + public function testAdminDomainZonesUpdate() + { + global $admin_userdata; + $this->expectExceptionCode(303); + DomainZones::getLocal($admin_userdata)->update(); + } +} diff --git a/tests/Domains/DomainsTest.php b/tests/Domains/DomainsTest.php index efc41901..4e899eb2 100644 --- a/tests/Domains/DomainsTest.php +++ b/tests/Domains/DomainsTest.php @@ -68,7 +68,8 @@ class DomainsTest extends TestCase $reseller_userdata['caneditphpsettings'] = 1; $data = [ 'domain' => 'test2.local', - 'customerid' => 1 + 'customerid' => 1, + 'isbinddomain' => 1 ]; // the reseller is not allowed to use the default ip/port $this->expectExceptionMessage("The ip/port combination you have chosen doesn't exist."); From c149cbacf7f13274ecae9ee4ee3743eac3cd2f26 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Fri, 23 Mar 2018 13:35:50 +0100 Subject: [PATCH 164/746] more DomainZones unit-tests Signed-off-by: Michael Kaufmann (d00p) --- .../api/commands/class.DomainZones.php | 10 +- tests/DomainZones/DomainZonesTest.php | 410 +++++++++++++++++- 2 files changed, 413 insertions(+), 7 deletions(-) diff --git a/lib/classes/api/commands/class.DomainZones.php b/lib/classes/api/commands/class.DomainZones.php index 0a7dcdf3..db01e93a 100644 --- a/lib/classes/api/commands/class.DomainZones.php +++ b/lib/classes/api/commands/class.DomainZones.php @@ -45,7 +45,7 @@ class DomainZones extends ApiCommand implements ResourceEntity if ($result['parentdomainid'] != '0') { throw new Exception("DNS zones can only be generated for the main domain, not for subdomains", 406); } - + if ($result['subisbinddomain'] != '1') { standard_error('dns_domain_nodns', '', true); } @@ -206,10 +206,10 @@ class DomainZones extends ApiCommand implements ResourceEntity $new_entry = array( 'record' => $record, 'type' => $type, - 'prio' => $prio, + 'prio' => (int) $prio, 'content' => $content, - 'ttl' => $ttl, - 'domain_id' => $id + 'ttl' => (int) $ttl, + 'domain_id' => (int) $id ); ksort($new_entry); @@ -297,7 +297,7 @@ class DomainZones extends ApiCommand implements ResourceEntity if ($result['parentdomainid'] != '0') { throw new Exception("DNS zones can only be generated for the main domain, not for subdomains", 406); } - + if ($result['subisbinddomain'] != '1') { standard_error('dns_domain_nodns', '', true); } diff --git a/tests/DomainZones/DomainZonesTest.php b/tests/DomainZones/DomainZonesTest.php index 3833084f..6e93881a 100644 --- a/tests/DomainZones/DomainZonesTest.php +++ b/tests/DomainZones/DomainZonesTest.php @@ -32,12 +32,13 @@ class DomainZonesTest extends TestCase $this->assertEquals('$ORIGIN test2.local.', $result[1]); } + /** + * @depends testCustomerDomainZonesGet + */ public function testCustomerDomainZonesGetNoSubdomains() { global $admin_userdata; - Settings::Set('system.dnsenabled', 1, true); - // get customer $json_result = Customers::getLocal($admin_userdata, array( 'loginname' => 'test1' @@ -65,4 +66,409 @@ class DomainZonesTest extends TestCase $this->expectExceptionCode(303); DomainZones::getLocal($admin_userdata)->update(); } + + /** + * @depends testCustomerDomainZonesGet + */ + public function testCustomerDomainZonesAddA() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'domainname' => 'test2.local', + 'record' => 'www2', + 'type' => 'A', + 'content' => '127.0.0.1' + ]; + $json_result = DomainZones::getLocal($customer_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertTrue(count($result) > 1); + $found = false; + foreach ($result as $entry) { + if (substr($entry, 0, 4) == 'www2') { + $found = true; + break; + } + } + $this->assertTrue($found); + $this->assertEquals('www2 18000 IN A 127.0.0.1', $entry); + } + + /** + * @depends testCustomerDomainZonesAddA + */ + public function testCustomerDomainZonesAddAInvalid() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'domainname' => 'test2.local', + 'record' => 'www3', + 'type' => 'A', + 'content' => 'a.b.c.d', + 'ttl' => -1 + ]; + $this->expectExceptionMessage("No valid IP address for A-record given"); + DomainZones::getLocal($customer_userdata, $data)->add(); + } + + /** + * @depends testCustomerDomainZonesAddA + */ + public function testCustomerDomainZonesAddADuplicate() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'domainname' => 'test2.local', + 'record' => 'www2', + 'type' => 'A', + 'content' => '127.0.0.1', + 'ttl' => -1 + ]; + $this->expectExceptionMessage("Record already exists"); + DomainZones::getLocal($customer_userdata, $data)->add(); + } + + /** + * @depends testCustomerDomainZonesGet + */ + public function testCustomerDomainZonesAddAAAA() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'domainname' => 'test2.local', + 'record' => 'www3', + 'type' => 'AAAA', + 'content' => '::1' + ]; + $json_result = DomainZones::getLocal($customer_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertTrue(count($result) > 1); + $found = false; + foreach ($result as $entry) { + if (substr($entry, 0, 4) == 'www3') { + $found = true; + break; + } + } + $this->assertTrue($found); + $this->assertEquals('www3 18000 IN AAAA ::1', $entry); + } + + /** + * @depends testCustomerDomainZonesAddA + */ + public function testCustomerDomainZonesAddAAAAInvalid() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'domainname' => 'test2.local', + 'record' => 'www4', + 'type' => 'AAAA', + 'content' => 'z:z123.123', + 'ttl' => -1 + ]; + $this->expectExceptionMessage("No valid IP address for AAAA-record given"); + DomainZones::getLocal($customer_userdata, $data)->add(); + } + + public function testAdminDomainZonesAddMX() + { + global $admin_userdata; + + $data = [ + 'domainname' => 'test2.local', + 'record' => '', + 'type' => 'MX', + 'prio' => 10, + 'content' => 'mail.example.com.' + ]; + $json_result = DomainZones::getLocal($admin_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertTrue(count($result) > 1); + $found = false; + foreach ($result as $entry) { + if (substr($entry, strlen('mail.example.com.') * -1) == 'mail.example.com.') { + $found = true; + break; + } + } + $this->assertTrue($found); + $this->assertEquals('@ 18000 IN MX 10 mail.example.com.', $entry); + } + + /** + * @depends testAdminDomainZonesAddMX + */ + public function testAdminDomainZonesAddMXNoPrio() + { + global $admin_userdata; + + $data = [ + 'domainname' => 'test2.local', + 'record' => '', + 'type' => 'MX', + 'content' => 'mail.example.com.' + ]; + $this->expectExceptionMessage("Invalid MX priority given"); + DomainZones::getLocal($admin_userdata, $data)->add(); + } + + /** + * @depends testAdminDomainZonesAddMX + */ + public function testAdminDomainZonesAddMXInvalid() + { + global $admin_userdata; + + $data = [ + 'domainname' => 'test2.local', + 'record' => '', + 'type' => 'MX', + 'prio' => 20, + 'content' => 'localhost' + ]; + $this->expectExceptionMessage("The MX content value must be a valid domain-name"); + DomainZones::getLocal($admin_userdata, $data)->add(); + } + + public function testAdminDomainZonesAddCname() + { + global $admin_userdata; + + $data = [ + 'domainname' => 'test2.local', + 'record' => 'db', + 'type' => 'CNAME', + 'content' => 'db.example.com.' + ]; + $json_result = DomainZones::getLocal($admin_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertTrue(count($result) > 1); + $found = false; + foreach ($result as $entry) { + if (substr($entry, strlen('db.example.com.') * -1) == 'db.example.com.') { + $found = true; + break; + } + } + $this->assertTrue($found); + $this->assertEquals('db 18000 IN CNAME db.example.com.', $entry); + } + + public function testAdminDomainZonesAddCnameLocal() + { + global $admin_userdata; + + $data = [ + 'domainname' => 'test2.local', + 'record' => 'db', + 'type' => 'CNAME', + 'content' => 'db2' + ]; + $json_result = DomainZones::getLocal($admin_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertTrue(count($result) > 1); + $found = false; + foreach ($result as $entry) { + if (substr($entry, strlen('db2.test2.local.') * -1) == 'db2.test2.local.') { + $found = true; + break; + } + } + $this->assertTrue($found); + $this->assertEquals('db 18000 IN CNAME db2.test2.local.', $entry); + } + + /** + * @depends testAdminDomainZonesAddCname + */ + public function testAdminDomainZonesAddCnameInvalid() + { + global $admin_userdata; + + $data = [ + 'domainname' => 'test2.local', + 'record' => '', + 'type' => 'CNAME', + 'content' => 'localhost.' + ]; + $this->expectExceptionMessage("Invalid domain-name for CNAME record"); + DomainZones::getLocal($admin_userdata, $data)->add(); + } + + public function testAdminDomainZonesAddNS() + { + global $admin_userdata; + + $data = [ + 'domainname' => 'test2.local', + 'record' => '', + 'type' => 'NS', + 'content' => 'ns.example.com.' + ]; + $json_result = DomainZones::getLocal($admin_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertTrue(count($result) > 1); + $found = false; + foreach ($result as $entry) { + if (substr($entry, strlen('ns.example.com.') * -1) == 'ns.example.com.') { + $found = true; + break; + } + } + $this->assertTrue($found); + $this->assertEquals('@ 18000 IN NS ns.example.com.', $entry); + } + + public function testAdminDomainZonesAddNsInvalid() + { + global $admin_userdata; + + $data = [ + 'domainname' => 'test2.local', + 'record' => '', + 'type' => 'NS', + 'content' => 'localhost.' + ]; + $this->expectExceptionMessage("Invalid domain-name for NS record"); + DomainZones::getLocal($admin_userdata, $data)->add(); + } + + public function testAdminDomainZonesAddTXT() + { + global $admin_userdata; + + $data = [ + 'domainname' => 'test2.local', + 'record' => '_test1', + 'type' => 'TXT', + 'content' => 'aw yeah' + ]; + $json_result = DomainZones::getLocal($admin_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertTrue(count($result) > 1); + $found = false; + foreach ($result as $entry) { + if (substr($entry, 0, 6) == '_test1') { + $found = true; + break; + } + } + $this->assertTrue($found); + $this->assertEquals('_test1 18000 IN TXT aw yeah', $entry); + } + + public function testAdminDomainZonesAddSRV() + { + global $admin_userdata; + + $data = [ + 'domainname' => 'test2.local', + 'record' => '_test2', + 'type' => 'SRV', + 'prio' => 50, + 'content' => '2 1 srv.example.com.' + ]; + $json_result = DomainZones::getLocal($admin_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertTrue(count($result) > 1); + $found = false; + foreach ($result as $entry) { + if (substr($entry, 0, 6) == '_test2') { + $found = true; + break; + } + } + $this->assertTrue($found); + $this->assertEquals('_test2 18000 IN SRV 50 2 1 srv.example.com.', $entry); + } + + public function testAdminDomainZonesAddSrvInvalid() + { + global $admin_userdata; + + $data = [ + 'domainname' => 'test2.local', + 'record' => '_test2', + 'type' => 'SRV', + 'prio' => 50, + 'content' => 'srv.example.com.' + ]; + $this->expectExceptionMessage("Invalid SRV content, must contain of fields weight, port and target, e.g.: 5 5060 sipserver.example.com."); + DomainZones::getLocal($admin_userdata, $data)->add(); + } + + public function testCustomerDomainZonesDelete() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'domainname' => 'test2.local', + 'entry_id' => 1 + ]; + $json_result = DomainZones::getLocal($customer_userdata, $data)->delete(); + $result = json_decode($json_result, true); + $this->assertTrue($result['data']); + $this->assertEquals(200, $result['status']); + } + + public function testCustomerDomainZonesDeleteUnmodified() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'domainname' => 'test2.local', + 'entry_id' => 1337 + ]; + $json_result = DomainZones::getLocal($customer_userdata, $data)->delete(); + $result = json_decode($json_result, true); + $this->assertTrue($result['data']); + $this->assertEquals(304, $result['status']); + } } From 6006b16c9556428568f29eae1c2c63ea87e416a1 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sun, 25 Mar 2018 12:38:57 +0200 Subject: [PATCH 165/746] added first test for Mysqls-ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Mysqls.php | 12 ++++----- phpunit.xml | 1 + tests/Customers/CustomersTest.php | 6 ++--- tests/Mysqls/MysqlsTest.php | 33 +++++++++++++++++++++++ tests/bootstrap.php | 4 +++ 5 files changed, 47 insertions(+), 9 deletions(-) create mode 100644 tests/Mysqls/MysqlsTest.php diff --git a/lib/classes/api/commands/class.Mysqls.php b/lib/classes/api/commands/class.Mysqls.php index 080b3e1a..733b977a 100644 --- a/lib/classes/api/commands/class.Mysqls.php +++ b/lib/classes/api/commands/class.Mysqls.php @@ -139,11 +139,11 @@ class Mysqls extends ApiCommand implements ResourceEntity $_mailerror = false; try { - $this->mail->Subject = $mail_subject; - $this->mail->AltBody = $mail_body; - $this->mail->MsgHTML(str_replace("\n", "
    ", $mail_body)); - $this->mail->AddAddress($userinfo['email'], getCorrectUserSalutation($userinfo)); - $this->mail->Send(); + $this->mailer()->Subject = $mail_subject; + $this->mailer()->AltBody = $mail_body; + $this->mailer()->MsgHTML(str_replace("\n", "
    ", $mail_body)); + $this->mailer()->AddAddress($userinfo['email'], getCorrectUserSalutation($userinfo)); + $this->mailer()->Send(); } catch (phpmailerException $e) { $mailerr_msg = $e->errorMessage(); $_mailerror = true; @@ -157,7 +157,7 @@ class Mysqls extends ApiCommand implements ResourceEntity standard_error('errorsendingmail', $userinfo['email'], true); } - $this->mail->ClearAddresses(); + $this->mailer()->ClearAddresses(); } $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_WARNING, "[API] added mysql-database '" . $username . "'"); diff --git a/phpunit.xml b/phpunit.xml index ceeb519c..1ffdbece 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -20,6 +20,7 @@ tests/Extras tests/Backup tests/DomainZones + tests/Mysqls diff --git a/tests/Customers/CustomersTest.php b/tests/Customers/CustomersTest.php index 7ca85690..d9ea0450 100644 --- a/tests/Customers/CustomersTest.php +++ b/tests/Customers/CustomersTest.php @@ -15,7 +15,7 @@ class CustomersTest extends TestCase $data = [ 'new_loginname' => 'test1', - 'email' => 'test@froxlor.org', + 'email' => 'team@froxlor.org', 'firstname' => 'Test', 'name' => 'Testman', 'customernumber' => 1337, @@ -46,7 +46,7 @@ class CustomersTest extends TestCase $json_result = Customers::getLocal($admin_userdata, $data)->add(); $result = json_decode($json_result, true)['data']; $this->assertEquals(1, $result['customerid']); - $this->assertEquals('test@froxlor.org', $result['email']); + $this->assertEquals('team@froxlor.org', $result['email']); $this->assertEquals(1337, $result['customernumber']); $this->assertEquals(15, $result['subdomains']); $this->assertEquals('secret', $result['custom_notes']); @@ -147,7 +147,7 @@ class CustomersTest extends TestCase $result = json_decode($json_result, true)['data']; $this->assertEquals(1, $result['customerid']); - $this->assertEquals('test@froxlor.org', $result['email']); + $this->assertEquals('team@froxlor.org', $result['email']); $this->assertEquals(1337, $result['customernumber']); $this->assertEquals(15, $result['subdomains']); $this->assertEquals('Sparkle', $result['theme']); diff --git a/tests/Mysqls/MysqlsTest.php b/tests/Mysqls/MysqlsTest.php new file mode 100644 index 00000000..b4f78134 --- /dev/null +++ b/tests/Mysqls/MysqlsTest.php @@ -0,0 +1,33 @@ + 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'mysql_password' => generatePassword(), + 'description' => 'testdb', + 'sendinfomail' => true + ]; + $json_result = Mysqls::getLocal($customer_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('testdb', $result['description']); + $this->assertEquals(0, $result['dbserver']); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index fcf14947..1016c4f5 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -25,6 +25,10 @@ file_put_contents($userdata, $userdata_content); // include autoloader / api / etc require dirname(__DIR__) . '/lib/classes/api/api_includes.inc.php'; +Database::needRoot(true); +Database::query("DROP DATABASE IF EXISTS `test1sql1`;"); +Database::needRoot(false); + // clear all tables Database::query("TRUNCATE TABLE `" . TABLE_PANEL_CUSTOMERS . "`;"); Database::query("TRUNCATE TABLE `" . TABLE_PANEL_DOMAINS . "`;"); From c98be3c04fbc7f36fc2d4680d7e98a4843da74cb Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 26 Mar 2018 09:53:09 +0200 Subject: [PATCH 166/746] finished unit-tests for Mysqls-ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Mysqls.php | 5 +- tests/Mysqls/MysqlsTest.php | 117 ++++++++++++++++++++++ tests/bootstrap.php | 1 + 3 files changed, 122 insertions(+), 1 deletion(-) diff --git a/lib/classes/api/commands/class.Mysqls.php b/lib/classes/api/commands/class.Mysqls.php index 733b977a..37091ccc 100644 --- a/lib/classes/api/commands/class.Mysqls.php +++ b/lib/classes/api/commands/class.Mysqls.php @@ -348,7 +348,10 @@ class Mysqls extends ApiCommand implements ResourceEntity Database::pexecute($stmt, $params, true, true); $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_WARNING, "[API] updated mysql-database '" . $result['databasename'] . "'"); - return $this->response(200, "successfull", $params); + $result = $this->apiCall('Mysqls.get', array( + 'dbname' => $result['databasename'] + )); + return $this->response(200, "successfull", $result); } /** diff --git a/tests/Mysqls/MysqlsTest.php b/tests/Mysqls/MysqlsTest.php index b4f78134..bd2a15f9 100644 --- a/tests/Mysqls/MysqlsTest.php +++ b/tests/Mysqls/MysqlsTest.php @@ -30,4 +30,121 @@ class MysqlsTest extends TestCase $this->assertEquals('testdb', $result['description']); $this->assertEquals(0, $result['dbserver']); } + + /** + * @depends testCustomerMysqlsAdd + */ + public function testAdminMysqlsGet() + { + global $admin_userdata; + + $json_result = Mysqls::getLocal($admin_userdata, array( + 'dbname' => 'test1sql1' + ))->get(); + $result = json_decode($json_result, true)['data']; + + $this->assertEquals('test1sql1', $result['databasename']); + $this->assertEquals('testdb', $result['description']); + } + + /** + * @depends testCustomerMysqlsAdd + */ + public function testResellerMysqlsGet() + { + global $admin_userdata; + // get reseller + $json_result = Admins::getLocal($admin_userdata, array( + 'loginname' => 'reseller' + ))->get(); + $reseller_userdata = json_decode($json_result, true)['data']; + $reseller_userdata['adminsession'] = 1; + $json_result = Mysqls::getLocal($reseller_userdata, array( + 'dbname' => 'test1sql1' + ))->get(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('test1sql1', $result['databasename']); + $this->assertEquals('testdb', $result['description']); + } + + public function testCustomerMysqlsGetUnknown() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'dbname' => 'test1sql5' + ]; + $this->expectExceptionCode(404); + $this->expectExceptionMessage("MySQL database with dbname 'test1sql5' could not be found"); + Mysqls::getLocal($customer_userdata, $data)->get(); + } + + /** + * @depends testCustomerMysqlsAdd + */ + public function testAdminMysqlsUpdate() + { + global $admin_userdata; + + $json_result = Mysqls::getLocal($admin_userdata, array( + 'dbname' => 'test1sql1' + ))->get(); + $old_db = json_decode($json_result, true)['data']; + + $data = [ + 'dbname' => 'test1sql1', + 'mysql_password' => generatePassword(), + 'description' => 'testdb-upd', + 'loginname' => 'test1' + ]; + $json_result = Mysqls::getLocal($admin_userdata, $data)->update(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('testdb-upd', $result['description']); + } + + /** + * @depends testCustomerMysqlsAdd + */ + public function testCustomerMysqlsList() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $json_result = Mysqls::getLocal($customer_userdata)->listing(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(1, $result['count']); + $this->assertEquals('test1sql1', $result['list'][0]['databasename']); + } + + /** + * @depends testCustomerMysqlsList + */ + public function testCustomerMysqlsDelete() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + + $data = [ + 'dbname' => 'test1sql1' + ]; + $json_result = Mysqls::getLocal($customer_userdata, $data)->delete(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('test1sql1', $result['databasename']); + } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 1016c4f5..3d94f45e 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -54,6 +54,7 @@ Database::query("TRUNCATE TABLE `" . TABLE_PANEL_DOMAINREDIRECTS . "`;"); Database::query("TRUNCATE TABLE `" . TABLE_PANEL_ADMINS . "`;"); Database::query("TRUNCATE TABLE `" . TABLE_PANEL_IPSANDPORTS . "`;"); Database::query("TRUNCATE TABLE `" . TABLE_API_KEYS . "`;"); +Database::query("TRUNCATE TABLE `" . TABLE_PANEL_DATABASES . "`;"); // add superadmin Database::query("INSERT INTO `" . TABLE_PANEL_ADMINS . "` SET From efb416ae7c506efa15fcc550a9f28655d84f29f3 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 26 Mar 2018 10:13:51 +0200 Subject: [PATCH 167/746] phpdoc for Admins-ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/class.FroxlorRPC.php | 1 - lib/classes/api/commands/class.Admins.php | 165 +++++++++++++++++++-- lib/classes/api/commands/class.Froxlor.php | 2 +- 3 files changed, 157 insertions(+), 11 deletions(-) diff --git a/lib/classes/api/class.FroxlorRPC.php b/lib/classes/api/class.FroxlorRPC.php index 07d466fa..41b7aeef 100644 --- a/lib/classes/api/class.FroxlorRPC.php +++ b/lib/classes/api/class.FroxlorRPC.php @@ -105,7 +105,6 @@ class FroxlorRPC // it will recognize non-api classes+methods as valid commands $apiclass = FROXLOR_INSTALL_DIR . '/lib/classes/api/commands/class.' . $command[0] . '.php'; if (! file_exists($apiclass) || ! @method_exists($command[0], $command[1])) { - // there will be an exception from the autoloader for class_exists hence the try-catch-block throw new Exception("Unknown command", 400); } return array( diff --git a/lib/classes/api/commands/class.Admins.php b/lib/classes/api/commands/class.Admins.php index 9a24c02e..18382d7b 100644 --- a/lib/classes/api/commands/class.Admins.php +++ b/lib/classes/api/commands/class.Admins.php @@ -86,6 +86,77 @@ class Admins extends ApiCommand implements ResourceEntity /** * create a new admin user * + * @param string $name + * @param string $email + * @param string $admin_password + * optional, default auto-generated + * @param string $def_language + * optional, default is system-default language + * @param string $custom_notes + * optional, default empty + * @param bool $custom_notes_show + * optional, default false + * @param int $diskspace + * optional, default 0 + * @param bool $diskspace_ul + * optional, default false + * @param int $traffic + * optional, default 0 + * @param bool $traffic_ul + * optional, default false + * @param int $customers + * optional, default 0 + * @param bool $customers_ul + * optional, default false + * @param int $domains + * optional, default 0 + * @param bool $domains_ul + * optional, default false + * @param int $subdomains + * optional, default 0 + * @param bool $subdomains_ul + * optional, default false + * @param int $emails + * optional, default 0 + * @param bool $emails_ul + * optional, default false + * @param int $email_accounts + * optional, default 0 + * @param bool $email_accounts_ul + * optional, default false + * @param int $email_forwarders + * optional, default 0 + * @param bool $email_forwarders_ul + * optional, default false + * @param int $email_quota + * optional, default 0 + * @param bool $email_quota_ul + * optional, default false + * @param int $ftps + * optional, default 0 + * @param bool $ftps_ul + * optional, default false + * @param int $tickets + * optional, default 0 + * @param bool $tickets_ul + * optional, default false + * @param int $mysqls + * optional, default 0 + * @param bool $mysqls_ul + * optional, default false + * @param bool $customers_see_all + * optional, default false + * @param bool $domains_see_all + * optional, default false + * @param bool $tickets_see_all + * optional, default false + * @param bool $caneditphpsettings + * optional, default false + * @param bool $change_serversettings + * optional, default false + * @param array $ipaddress + * optional, list of ip-address id's; default -1 (all IP's) + * * @access admin * @throws Exception * @return array @@ -301,6 +372,82 @@ class Admins extends ApiCommand implements ResourceEntity * optional, the admin-id * @param string $loginname * optional, the loginname + * @param string $name + * optional + * @param string $email + * optional + * @param string $admin_password + * optional, default auto-generated + * @param string $def_language + * optional, default is system-default language + * @param string $custom_notes + * optional, default empty + * @param string $theme + * optional + * @param bool $deactivated + * optional, default false + * @param bool $custom_notes_show + * optional, default false + * @param int $diskspace + * optional, default 0 + * @param bool $diskspace_ul + * optional, default false + * @param int $traffic + * optional, default 0 + * @param bool $traffic_ul + * optional, default false + * @param int $customers + * optional, default 0 + * @param bool $customers_ul + * optional, default false + * @param int $domains + * optional, default 0 + * @param bool $domains_ul + * optional, default false + * @param int $subdomains + * optional, default 0 + * @param bool $subdomains_ul + * optional, default false + * @param int $emails + * optional, default 0 + * @param bool $emails_ul + * optional, default false + * @param int $email_accounts + * optional, default 0 + * @param bool $email_accounts_ul + * optional, default false + * @param int $email_forwarders + * optional, default 0 + * @param bool $email_forwarders_ul + * optional, default false + * @param int $email_quota + * optional, default 0 + * @param bool $email_quota_ul + * optional, default false + * @param int $ftps + * optional, default 0 + * @param bool $ftps_ul + * optional, default false + * @param int $tickets + * optional, default 0 + * @param bool $tickets_ul + * optional, default false + * @param int $mysqls + * optional, default 0 + * @param bool $mysqls_ul + * optional, default false + * @param bool $customers_see_all + * optional, default false + * @param bool $domains_see_all + * optional, default false + * @param bool $tickets_see_all + * optional, default false + * @param bool $caneditphpsettings + * optional, default false + * @param bool $change_serversettings + * optional, default false + * @param array $ipaddress + * optional, list of ip-address id's; default -1 (all IP's) * * @access admin * @throws Exception @@ -313,7 +460,7 @@ class Admins extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); $loginname = $this->getParam('loginname', $ln_optional, ''); - + $result = $this->apiCall('Admins.get', array( 'id' => $id, 'loginname' => $loginname @@ -579,7 +726,7 @@ class Admins extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $ln_optional = ($id <= 0 ? false : true); $loginname = $this->getParam('loginname', $ln_optional, ''); - + $result = $this->apiCall('Admins.get', array( 'id' => $id, 'loginname' => $loginname @@ -598,7 +745,7 @@ class Admins extends ApiCommand implements ResourceEntity Database::pexecute($del_stmt, array( 'adminid' => $id ), true, true); - + // delete the traffic-usage $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_PANEL_TRAFFIC_ADMINS . "` WHERE `adminid` = :adminid @@ -606,7 +753,7 @@ class Admins extends ApiCommand implements ResourceEntity Database::pexecute($del_stmt, array( 'adminid' => $id ), true, true); - + // delete the diskspace usage $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_PANEL_DISKSPACE_ADMINS . "` WHERE `adminid` = :adminid @@ -614,7 +761,7 @@ class Admins extends ApiCommand implements ResourceEntity Database::pexecute($del_stmt, array( 'adminid' => $id ), true, true); - + // set admin-id of the old admin's customer to current admins $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET @@ -624,7 +771,7 @@ class Admins extends ApiCommand implements ResourceEntity 'userid' => $this->getUserDetail('adminid'), 'adminid' => $id ), true, true); - + // set admin-id of the old admin's domains to current admins $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_DOMAINS . "` SET @@ -634,7 +781,7 @@ class Admins extends ApiCommand implements ResourceEntity 'userid' => $this->getUserDetail('adminid'), 'adminid' => $id ), true, true); - + // delete old admin's api keys if exists (no customer keys) $upd_stmt = Database::prepare(" DELETE FROM `" . TABLE_API_KEYS . "` WHERE @@ -643,7 +790,7 @@ class Admins extends ApiCommand implements ResourceEntity Database::pexecute($upd_stmt, array( 'adminid' => $id ), true, true); - + // set admin-id of the old admin's api-keys to current admins $upd_stmt = Database::prepare(" UPDATE `" . TABLE_API_KEYS . "` SET @@ -653,7 +800,7 @@ class Admins extends ApiCommand implements ResourceEntity 'userid' => $this->getUserDetail('adminid'), 'adminid' => $id ), true, true); - + $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] deleted admin '" . $result['loginname'] . "'"); updateCounters(); return $this->response(200, "successfull", $result); diff --git a/lib/classes/api/commands/class.Froxlor.php b/lib/classes/api/commands/class.Froxlor.php index 9c68a666..bcf37499 100644 --- a/lib/classes/api/commands/class.Froxlor.php +++ b/lib/classes/api/commands/class.Froxlor.php @@ -234,7 +234,7 @@ class Froxlor extends ApiCommand $reflection = new \ReflectionClass($mod); $_functions = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC); foreach ($_functions as $func) { - if ($func->class == $mod && $func->isPublic()) { + if ($func->class == $mod && $func->isPublic() && !$func->isStatic()) { array_push($functions, array_merge(array( 'module' => $matches[1], 'function' => $func->name From cb3d5f348885801c4819b0e2423273be1ae94dd4 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 26 Mar 2018 14:26:05 +0200 Subject: [PATCH 168/746] unit-test FpmDaemons-ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.FpmDaemons.php | 31 +++++-- tests/PhpAndFpm/FpmDaemonsTest.php | 92 +++++++++++++++++++ tests/bootstrap.php | 1 + 3 files changed, 114 insertions(+), 10 deletions(-) create mode 100644 tests/PhpAndFpm/FpmDaemonsTest.php diff --git a/lib/classes/api/commands/class.FpmDaemons.php b/lib/classes/api/commands/class.FpmDaemons.php index e740f4cf..c1aedbbb 100644 --- a/lib/classes/api/commands/class.FpmDaemons.php +++ b/lib/classes/api/commands/class.FpmDaemons.php @@ -72,8 +72,9 @@ class FpmDaemons extends ApiCommand implements ResourceEntity /** * return a fpm-daemon entry by id * - * @param int $id fpm-daemon-id - * + * @param int $id + * fpm-daemon-id + * * @access admin * @throws Exception * @return array @@ -121,7 +122,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity $max_spare_servers = $this->getParam('max_spare_servers', true, 0); $max_requests = $this->getParam('max_requests', true, 0); $idle_timeout = $this->getParam('idle_timeout', true, 0); - $limit_extensions = $this->getParam('limit_extensions', true, ''); + $limit_extensions = $this->getParam('limit_extensions', true, '.php'); // validation $description = validate($description, 'description', '', '', array(), true); @@ -134,6 +135,9 @@ class FpmDaemons extends ApiCommand implements ResourceEntity ))) { throw new ErrorException("Unknown process manager", 406); } + if (empty($limit_extensions)) { + $limit_extensions = '.php'; + } $limit_extensions = validate($limit_extensions, 'limit_extensions', '/^(\.[a-z]([a-z0-9]+)\ ?)+$/', '', array(), true); if (strlen($description) == 0 || strlen($description) > 50) { @@ -168,11 +172,14 @@ class FpmDaemons extends ApiCommand implements ResourceEntity 'limit_extensions' => $limit_extensions ); Database::pexecute($ins_stmt, $ins_data); - $ins_data['id'] = Database::lastInsertId(); + $id = Database::lastInsertId(); inserttask('1'); $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] fpm-daemon with description '" . $description . "' has been created by '" . $this->getUserDetail('loginname') . "'"); - return $this->response(200, "successfull", $ins_data); + $result = $this->apiCall('FpmDaemons.get', array( + 'id' => $id + )); + return $this->response(200, "successfull", $result); } throw new Exception("Not allowed to execute given command.", 403); } @@ -192,8 +199,8 @@ class FpmDaemons extends ApiCommand implements ResourceEntity // required parameter $id = $this->getParam('id'); - - $result = $this->apiCall('PhpSettings.get', array( + + $result = $this->apiCall('FpmDaemons.get', array( 'id' => $id )); @@ -221,6 +228,9 @@ class FpmDaemons extends ApiCommand implements ResourceEntity ))) { throw new ErrorException("Unknown process manager", 406); } + if (empty($limit_extensions)) { + $limit_extensions = '.php'; + } $limit_extensions = validate($limit_extensions, 'limit_extensions', '/^(\.[a-z]([a-z0-9]+)\ ?)+$/', '', array(), true); if (strlen($description) == 0 || strlen($description) > 50) { @@ -268,8 +278,9 @@ class FpmDaemons extends ApiCommand implements ResourceEntity /** * delete a fpm-daemon entry by id * - * @param int $id fpm-daemon-id - * + * @param int $id + * fpm-daemon-id + * * @access admin * @throws Exception * @return array @@ -282,7 +293,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity if ($id == 1) { standard_error('cannotdeletedefaultphpconfig', '', true); } - + $result = $this->apiCall('FpmDaemons.get', array( 'id' => $id )); diff --git a/tests/PhpAndFpm/FpmDaemonsTest.php b/tests/PhpAndFpm/FpmDaemonsTest.php new file mode 100644 index 00000000..b0491056 --- /dev/null +++ b/tests/PhpAndFpm/FpmDaemonsTest.php @@ -0,0 +1,92 @@ + 'test2 fpm', + 'reload_cmd' => 'service php7.1-fpm reload', + 'config_dir' => '/etc/php/7.1/fpm/pool.d' + ]; + $json_result = FpmDaemons::getLocal($admin_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('/etc/php/7.1/fpm/pool.d/', $result['config_dir']); + $this->assertEquals(0, $result['max_children']); + $this->assertEquals('.php', $result['limit_extensions']); + self::$id = $result['id']; + } + + /** + * @depends testAdminFpmDaemonsAdd + */ + public function testAdminFpmDaemonsUpdate() + { + global $admin_userdata; + $data = [ + 'id' => self::$id, + 'description' => 'test2 fpm edit', + 'pm' => 'dynamic', + 'max_children' => '10', + 'start_servers' => '4', + 'limit_extensions' => '.php .php.xml', + ]; + $json_result = FpmDaemons::getLocal($admin_userdata, $data)->update(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('/etc/php/7.1/fpm/pool.d/', $result['config_dir']); + $this->assertEquals(10, $result['max_children']); + $this->assertEquals('.php .php.xml', $result['limit_extensions']); + } + + /** + * @depends testAdminFpmDaemonsUpdate + */ + public function testAdminFpmDaemonsList() + { + global $admin_userdata; + $json_result = FpmDaemons::getLocal($admin_userdata)->listing(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals(2, $result['count']); + $this->assertEquals('test fpm', $result['list'][0]['description']); + $this->assertEquals('test2 fpm edit', $result['list'][1]['description']); + } + + /** + * @depends testAdminFpmDaemonsList + */ + public function testAdminFpmDaemonsDelete() + { + global $admin_userdata; + $data = [ + 'id' => self::$id + ]; + $json_result = FpmDaemons::getLocal($admin_userdata, $data)->delete(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('/etc/php/7.1/fpm/pool.d/', $result['config_dir']); + $this->assertEquals(10, $result['max_children']); + $this->assertEquals('.php .php.xml', $result['limit_extensions']); + } + + /** + * @depends testAdminFpmDaemonsDelete + */ + public function testAdminFpmDaemonsDeleteDefaultConfig() + { + global $admin_userdata; + $data = [ + 'id' => 1 + ]; + $this->expectExceptionMessage("This PHP-configuration is set as default and cannot be deleted."); + FpmDaemons::getLocal($admin_userdata, $data)->delete(); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 3d94f45e..b5dbbab7 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -55,6 +55,7 @@ Database::query("TRUNCATE TABLE `" . TABLE_PANEL_ADMINS . "`;"); Database::query("TRUNCATE TABLE `" . TABLE_PANEL_IPSANDPORTS . "`;"); Database::query("TRUNCATE TABLE `" . TABLE_API_KEYS . "`;"); Database::query("TRUNCATE TABLE `" . TABLE_PANEL_DATABASES . "`;"); +Database::query("ALTER TABLE `" . TABLE_PANEL_FPMDAEMONS . "` AUTO_INCREMENT=2;"); // add superadmin Database::query("INSERT INTO `" . TABLE_PANEL_ADMINS . "` SET From bf3ae3009f80930a1b294e07966715453b9ffd72 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 26 Mar 2018 14:28:32 +0200 Subject: [PATCH 169/746] add tests/PhpAndFpm to phpunit-testsuite Signed-off-by: Michael Kaufmann (d00p) --- phpunit.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/phpunit.xml b/phpunit.xml index 1ffdbece..7c551348 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -21,6 +21,7 @@ tests/Backup tests/DomainZones tests/Mysqls + tests/PhpAndFpm From 45d7307a8feaafa519e896ab1668c876e0cd7c55 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 26 Mar 2018 14:33:43 +0200 Subject: [PATCH 170/746] fix phpunit test for FpmDaemonTest Signed-off-by: Michael Kaufmann (d00p) --- tests/PhpAndFpm/FpmDaemonsTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PhpAndFpm/FpmDaemonsTest.php b/tests/PhpAndFpm/FpmDaemonsTest.php index b0491056..f85ec30e 100644 --- a/tests/PhpAndFpm/FpmDaemonsTest.php +++ b/tests/PhpAndFpm/FpmDaemonsTest.php @@ -57,7 +57,7 @@ class FpmDaemonsTest extends TestCase $json_result = FpmDaemons::getLocal($admin_userdata)->listing(); $result = json_decode($json_result, true)['data']; $this->assertEquals(2, $result['count']); - $this->assertEquals('test fpm', $result['list'][0]['description']); + $this->assertEquals('System default', $result['list'][0]['description']); $this->assertEquals('test2 fpm edit', $result['list'][1]['description']); } From d15e4a827038b0640a974892733a3c83e86199b9 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 27 Mar 2018 14:43:24 +0200 Subject: [PATCH 171/746] more unit-tests Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Admins.php | 34 +----- tests/Admins/AdminsTest.php | 75 +++++++++++- tests/Backup/CustomerBackupsTest.php | 73 +++++++++-- tests/Customers/CustomersTest.php | 141 ++++++++++++++++++++++ tests/Emails/EmailsTest.php | 65 ++++++++++ tests/Ftps/FtpsTest.php | 2 + tests/Mysqls/MysqlsTest.php | 2 + tests/SubDomains/SubDomainsTest.php | 2 + 8 files changed, 356 insertions(+), 38 deletions(-) diff --git a/lib/classes/api/commands/class.Admins.php b/lib/classes/api/commands/class.Admins.php index 18382d7b..4aeba76b 100644 --- a/lib/classes/api/commands/class.Admins.php +++ b/lib/classes/api/commands/class.Admins.php @@ -88,6 +88,7 @@ class Admins extends ApiCommand implements ResourceEntity * * @param string $name * @param string $email + * @param string $new_loginname * @param string $admin_password * optional, default auto-generated * @param string $def_language @@ -168,13 +169,13 @@ class Admins extends ApiCommand implements ResourceEntity // required parameters $name = $this->getParam('name'); $email = $this->getParam('email'); - + $loginname = $this->getParam('new_loginname'); + // parameters $def_language = $this->getParam('def_language', true, Settings::Get('panel.standardlanguage')); $custom_notes = $this->getParam('custom_notes', true, ''); $custom_notes_show = $this->getParam('custom_notes_show', true, 0); $password = $this->getParam('admin_password', true, ''); - $loginname = $this->getParam('new_loginname', true, ''); $diskspace = $this->getUlParam('diskspace', 'diskspace_ul', true, 0); $traffic = $this->getUlParam('traffic', 'traffic_ul', true, 0); @@ -239,28 +240,13 @@ class Admins extends ApiCommand implements ResourceEntity 'login' => $loginname ), true, true); - if ($loginname == '') { - standard_error(array( - 'stringisempty', - 'myloginname' - ), '', true); - } elseif (strtolower($loginname_check['loginname']) == strtolower($loginname) || strtolower($loginname_check_admin['loginname']) == strtolower($loginname)) { + if (strtolower($loginname_check['loginname']) == strtolower($loginname) || strtolower($loginname_check_admin['loginname']) == strtolower($loginname)) { standard_error('loginnameexists', $loginname, true); } // Accounts which match systemaccounts are not allowed, filtering them elseif (preg_match('/^' . preg_quote(Settings::Get('customer.accountprefix'), '/') . '([0-9]+)/', $loginname)) { standard_error('loginnameissystemaccount', Settings::Get('customer.accountprefix'), true); } elseif (! validateUsername($loginname)) { standard_error('loginnameiswrong', $loginname, true); - } elseif ($name == '') { - standard_error(array( - 'stringisempty', - 'myname' - ), '', true); - } elseif ($email == '') { - standard_error(array( - 'stringisempty', - 'emailadd' - ), '', true); } elseif (! validateEmail($email)) { standard_error('emailiswrong', $email, true); } else { @@ -548,17 +534,7 @@ class Admins extends ApiCommand implements ResourceEntity $theme = Settings::Get('panel.default_theme'); } - if ($name == '') { - standard_error(array( - 'stringisempty', - 'myname' - ), '', true); - } elseif ($email == '') { - standard_error(array( - 'stringisempty', - 'emailadd' - ), '', true); - } elseif (! validateEmail($email)) { + if (! validateEmail($email)) { standard_error('emailiswrong', $email, true); } else { diff --git a/tests/Admins/AdminsTest.php b/tests/Admins/AdminsTest.php index 79b110a2..da096256 100644 --- a/tests/Admins/AdminsTest.php +++ b/tests/Admins/AdminsTest.php @@ -51,6 +51,78 @@ class AdminsTest extends TestCase $this->assertEquals(0, $result['customers_see_all']); } + /** + * + * @depends testAdminAdminsAdd + */ + public function testAdminAdminsAddLoginnameExists() + { + global $admin_userdata; + + $data = [ + 'new_loginname' => 'reseller', + 'email' => 'testreseller@froxlor.org', + 'name' => 'Testreseller' + ]; + + $this->expectExceptionMessage('Loginname reseller already exists'); + Admins::getLocal($admin_userdata, $data)->add(); + } + + /** + * + * @depends testAdminAdminsAddLoginnameExists + */ + public function testAdminAdminsAddLoginnameIsSystemaccount() + { + global $admin_userdata; + + $data = [ + 'new_loginname' => 'web2', + 'email' => 'testreseller@froxlor.org', + 'name' => 'Testreseller' + ]; + + $this->expectExceptionMessage('You cannot create accounts which are similar to system accounts (as for example begin with "web"). Please enter another account name.'); + Admins::getLocal($admin_userdata, $data)->add(); + } + + /** + * + * @depends testAdminAdminsAddLoginnameIsSystemaccount + */ + public function testAdminAdminsAddLoginnameInvalid() + { + global $admin_userdata; + + $data = [ + 'new_loginname' => 'reslr-', + 'email' => 'testreseller@froxlor.org', + 'name' => 'Testreseller' + ]; + + $this->expectExceptionMessage('Loginname "reslr-" contains illegal characters.'); + Admins::getLocal($admin_userdata, $data)->add(); + } + + /** + * + * @depends testAdminAdminsAddLoginnameIsSystemaccount + */ + public function testAdminAdminsAddLoginnameInvalidEmail() + { + global $admin_userdata; + + $data = [ + 'new_loginname' => 'reslr', + 'email' => 'testreseller.froxlor.org', + 'name' => 'Testreseller' + ]; + + $this->expectExceptionMessage('Email-address testreseller.froxlor.org contains invalid characters or is incomplete'); + Admins::getLocal($admin_userdata, $data)->add(); + } + public function testAdminAdminsAddNotAllowed() { global $admin_userdata; @@ -160,8 +232,7 @@ class AdminsTest extends TestCase $data = [ 'new_loginname' => 'resellertest', 'email' => 'testreseller@froxlor.org', - 'name' => 'Testreseller', - 'admin_password' => 'h0lYmo1y' + 'name' => 'Testreseller' ]; $json_result = Admins::getLocal($admin_userdata, $data)->add(); diff --git a/tests/Backup/CustomerBackupsTest.php b/tests/Backup/CustomerBackupsTest.php index 210b36eb..6a865780 100644 --- a/tests/Backup/CustomerBackupsTest.php +++ b/tests/Backup/CustomerBackupsTest.php @@ -10,10 +10,69 @@ use PHPUnit\Framework\TestCase; class CustomerBackupsTest extends TestCase { + public function testAdminCustomerBackupsNotEnabled() + { + global $admin_userdata; + + Settings::Set('system.backupenabled', 0, true); + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $this->expectExceptionCode(405); + $this->expectExceptionMessage("You cannot access this resource"); + CustomerBackups::getLocal($customer_userdata)->add(); + } + + /** + * @depends testAdminCustomerBackupsNotEnabled + */ + public function testAdminCustomerBackupsExtrasHidden() + { + global $admin_userdata; + + Settings::Set('system.backupenabled', 1, true); + Settings::Set('panel.customer_hide_options', 'extras', true); + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $this->expectExceptionCode(405); + $this->expectExceptionMessage("You cannot access this resource"); + CustomerBackups::getLocal($customer_userdata)->add(); + } + + /** + * @depends testAdminCustomerBackupsExtrasHidden + */ + public function testAdminCustomerBackupsExtrasBackupHidden() + { + global $admin_userdata; + + Settings::Set('panel.customer_hide_options', 'extras.backup', true); + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $this->expectExceptionCode(405); + $this->expectExceptionMessage("You cannot access this resource"); + CustomerBackups::getLocal($customer_userdata)->add(); + } + + /** + * @depends testAdminCustomerBackupsExtrasBackupHidden + */ public function testCustomerCustomerBackupsAdd() { global $admin_userdata; + Settings::Set('panel.customer_hide_options', '', true); Database::query("TRUNCATE TABLE `panel_tasks`;"); // get customer @@ -24,16 +83,16 @@ class CustomerBackupsTest extends TestCase $data = [ 'path' => '/my-backup', - 'backup_dbs' => 1, - 'backup_mail' => 2, - 'backup_web' => 1 + 'backup_dbs' => 2, + 'backup_mail' => 3, + 'backup_web' => 4 ]; $json_result = CustomerBackups::getLocal($customer_userdata, $data)->add(); $result = json_decode($json_result, true)['data']; $this->assertEquals($customer_userdata['documentroot'] . 'my-backup/', $result['destdir']); - $this->assertEquals('1', $result['backup_dbs']); + $this->assertEquals('0', $result['backup_dbs']); $this->assertEquals('0', $result['backup_mail']); - $this->assertEquals('1', $result['backup_web']); + $this->assertEquals('0', $result['backup_web']); } public function testAdminCustomerBackupsGet() @@ -61,9 +120,9 @@ class CustomerBackupsTest extends TestCase $json_result = CustomerBackups::getLocal($admin_userdata)->listing(); $result = json_decode($json_result, true)['data']; $this->assertEquals(1, $result['count']); - $this->assertEquals('1', $result['list'][0]['data']['backup_dbs']); + $this->assertEquals('0', $result['list'][0]['data']['backup_dbs']); $this->assertEquals('0', $result['list'][0]['data']['backup_mail']); - $this->assertEquals('1', $result['list'][0]['data']['backup_web']); + $this->assertEquals('0', $result['list'][0]['data']['backup_web']); } /** diff --git a/tests/Customers/CustomersTest.php b/tests/Customers/CustomersTest.php index d9ea0450..20b3eafb 100644 --- a/tests/Customers/CustomersTest.php +++ b/tests/Customers/CustomersTest.php @@ -2,9 +2,11 @@ use PHPUnit\Framework\TestCase; /** + * * @covers ApiCommand * @covers ApiParameter * @covers Customers + * @covers Admins */ class CustomersTest extends TestCase { @@ -83,6 +85,7 @@ class CustomersTest extends TestCase } /** + * * @depends testAdminCustomersAdd */ public function testAdminCustomersList() @@ -95,6 +98,7 @@ class CustomersTest extends TestCase } /** + * * @depends testAdminCustomersAdd */ public function testResellerCustomersList() @@ -112,6 +116,7 @@ class CustomersTest extends TestCase } /** + * * @depends testAdminCustomersAdd */ public function testCustomerCustomersList() @@ -130,6 +135,7 @@ class CustomersTest extends TestCase } /** + * * @depends testAdminCustomersAdd */ public function testCustomerCustomersGet() @@ -165,6 +171,7 @@ class CustomersTest extends TestCase } /** + * * @depends testAdminCustomersAdd */ public function testCustomerCustomersGetForeign() @@ -185,6 +192,7 @@ class CustomersTest extends TestCase } /** + * * @depends testAdminCustomersAdd */ public function testAdminCustomerUpdateDeactivate() @@ -209,6 +217,7 @@ class CustomersTest extends TestCase } /** + * * @depends testAdminCustomersAdd */ public function testCustomerCustomersGetWhenDeactivated() @@ -230,6 +239,7 @@ class CustomersTest extends TestCase } /** + * * @depends testAdminCustomersAdd */ public function testCustomerCustomersUpdate() @@ -266,6 +276,7 @@ class CustomersTest extends TestCase } /** + * * @depends testAdminCustomersAdd */ public function testResellerCustomersAddAllocateMore() @@ -403,4 +414,134 @@ class CustomersTest extends TestCase $this->assertEquals(2, $result['adminid']); } + + /** + * + * @depends testAdminCustomersMove + */ + public function testAdminCustomersAddLoginnameIsSystemaccount() + { + global $admin_userdata; + + $data = [ + 'new_loginname' => 'web1', + 'email' => 'team@froxlor.org', + 'firstname' => 'Test', + 'name' => 'Testman', + 'customernumber' => 1338, + 'diskspace' => - 1, + 'traffic' => - 1, + 'subdomains' => 15, + 'emails' => - 1, + 'email_accounts' => 15, + 'email_forwarders' => 15, + 'email_imap' => 1, + 'email_pop3' => 0, + 'ftps' => 15, + 'tickets' => 15, + 'mysqls' => 15, + 'createstdsubdomain' => 1, + 'new_customer_password' => 'h0lYmo1y', + 'sendpassword' => 1, + 'phpenabled' => 1, + 'store_defaultindex' => 1, + 'custom_notes' => 'secret', + 'custom_notes_show' => 0, + 'gender' => 5, + 'allowed_phpconfigs' => array( + 1 + ) + ]; + + $this->expectExceptionMessage('You cannot create accounts which are similar to system accounts (as for example begin with "web"). Please enter another account name.'); + Customers::getLocal($admin_userdata, $data)->add(); + } + + /** + * + * @depends testAdminCustomersAddLoginnameIsSystemaccount + */ + public function testAdminCustomersAddAutoLoginname() + { + global $admin_userdata; + + Settings::Set('system.lastaccountnumber', 0, true); + Settings::Set('ticket.enabled', 0, true); + + $data = [ + 'new_loginname' => '', + 'email' => 'team@froxlor.org', + 'firstname' => 'Test2', + 'name' => 'Testman2', + 'customernumber' => 1338, + 'sendpassword' => 0, + 'perlenabled' => 2, + 'dnsenabled' => 4 + ]; + + $json_result = Customers::getLocal($admin_userdata, $data)->add(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('web1', $result['loginname']); + $this->assertEquals(1338, $result['customernumber']); + } + + /** + * + * @depends testAdminCustomersAddAutoLoginname + */ + public function testAdminCustomersAddLoginnameExists() + { + global $admin_userdata; + + $data = [ + 'new_loginname' => 'test1', + 'email' => 'team@froxlor.org', + 'firstname' => 'Test2', + 'name' => 'Testman2', + 'customernumber' => 1339 + ]; + + $this->expectExceptionMessage('Loginname test1 already exists'); + Customers::getLocal($admin_userdata, $data)->add(); + } + + /** + * + * @depends testAdminCustomersAddLoginnameExists + */ + public function testAdminCustomersAddLoginnameInvalid() + { + global $admin_userdata; + + $data = [ + 'new_loginname' => 'user-', + 'email' => 'team@froxlor.org', + 'firstname' => 'Test2', + 'name' => 'Testman2', + 'customernumber' => 1339 + ]; + + $this->expectExceptionMessage('Loginname "user-" contains illegal characters.'); + Customers::getLocal($admin_userdata, $data)->add(); + } + + /** + * + * @depends testAdminCustomersAddLoginnameExists + */ + public function testAdminCustomersAddLoginnameInvalid2() + { + global $admin_userdata; + + $data = [ + 'new_loginname' => 'useruseruseruseruseruserX', + 'email' => 'team@froxlor.org', + 'firstname' => 'Test2', + 'name' => 'Testman2', + 'customernumber' => 1339 + ]; + + $this->expectExceptionMessage('Loginname contains too many characters. Only ' . (14 - strlen(Settings::Get('customer.mysqlprefix'))) . ' characters are allowed.'); + Customers::getLocal($admin_userdata, $data)->add(); + } } diff --git a/tests/Emails/EmailsTest.php b/tests/Emails/EmailsTest.php index 5ef1f0d8..9cfd1065 100644 --- a/tests/Emails/EmailsTest.php +++ b/tests/Emails/EmailsTest.php @@ -7,6 +7,8 @@ use PHPUnit\Framework\TestCase; * @covers Emails * @covers EmailForwarders * @covers EmailAccounts + * @covers Customers + * @covers Admins */ class MailsTest extends TestCase { @@ -96,11 +98,72 @@ class MailsTest extends TestCase $result = json_decode($json_result, true)['data']; $this->assertEquals('other@domain.tld', $result['destination']); } + + /** + * @depends testCustomerEmailForwardersAdd + */ + public function testCustomerEmailForwardersAddNoMoreResources() + { + global $admin_userdata; + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $customer_userdata['email_forwarders_used'] = $customer_userdata['email_forwarders']; + $this->expectExceptionCode(406); + $this->expectExceptionMessage("No more resources available"); + EmailForwarders::getLocal($customer_userdata)->add(); + } + + /** + * @depends testCustomerEmailForwardersAddNoMoreResources + */ + public function testCustomerEmailForwardersAddEmailHidden() + { + global $admin_userdata; + + Settings::Set('panel.customer_hide_options', 'email', true); + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $this->expectExceptionCode(405); + $this->expectExceptionMessage("You cannot access this resource"); + EmailForwarders::getLocal($customer_userdata)->add(); + } + /** + * @depends testCustomerEmailForwardersAddEmailHidden + */ + public function testCustomerEmailForwardersDeleteEmailHidden() + { + global $admin_userdata; + + Settings::Set('panel.customer_hide_options', 'email', true); + + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $this->expectExceptionCode(405); + $this->expectExceptionMessage("You cannot access this resource"); + EmailForwarders::getLocal($customer_userdata)->delete(); + } + + /** + * @depends testCustomerEmailForwardersDeleteEmailHidden + */ public function testCustomerEmailForwardersAddAnother() { global $admin_userdata; + Settings::Set('panel.customer_hide_options', '', true); + // get customer $json_result = Customers::getLocal($admin_userdata, array( 'loginname' => 'test1' @@ -245,6 +308,8 @@ class MailsTest extends TestCase { global $admin_userdata; + Settings::Set('panel.customer_hide_options', '', true); + // get customer $json_result = Customers::getLocal($admin_userdata, array( 'loginname' => 'test1' diff --git a/tests/Ftps/FtpsTest.php b/tests/Ftps/FtpsTest.php index 38389a44..09a3bfcd 100644 --- a/tests/Ftps/FtpsTest.php +++ b/tests/Ftps/FtpsTest.php @@ -5,6 +5,8 @@ use PHPUnit\Framework\TestCase; * @covers ApiCommand * @covers ApiParameter * @covers Ftps + * @covers Customers + * @covers Admins */ class FtpsTest extends TestCase { diff --git a/tests/Mysqls/MysqlsTest.php b/tests/Mysqls/MysqlsTest.php index bd2a15f9..fda7b559 100644 --- a/tests/Mysqls/MysqlsTest.php +++ b/tests/Mysqls/MysqlsTest.php @@ -6,6 +6,8 @@ use PHPUnit\Framework\TestCase; * @covers ApiCommand * @covers ApiParameter * @covers Mysqls + * @covers Customers + * @covers Admins */ class MysqlsTest extends TestCase { diff --git a/tests/SubDomains/SubDomainsTest.php b/tests/SubDomains/SubDomainsTest.php index d1aa4af0..54b9219a 100644 --- a/tests/SubDomains/SubDomainsTest.php +++ b/tests/SubDomains/SubDomainsTest.php @@ -6,6 +6,8 @@ use PHPUnit\Framework\TestCase; * @covers ApiParameter * @covers SubDomains * @covers Domains + * @covers Customers + * @covers Admins */ class SubDomainsTest extends TestCase { From e58192edc259b4560a05882f627431a7cf9a9953 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 29 Mar 2018 11:11:39 +0200 Subject: [PATCH 172/746] show whether curl extension is installed when trying to check for a new version in admin-dashboard Signed-off-by: Michael Kaufmann (d00p) --- admin_index.php | 53 ++++++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/admin_index.php b/admin_index.php index 4650a24e..0f88a023 100644 --- a/admin_index.php +++ b/admin_index.php @@ -85,35 +85,42 @@ if ($page == 'overview') { if ((isset($_GET['lookfornewversion']) && $_GET['lookfornewversion'] == 'yes') || (isset($lookfornewversion) && $lookfornewversion == 'yes') ) { - $update_check_uri = 'http://version.froxlor.org/Froxlor/legacy/' . $version; - $latestversion = HttpClient::urlGet($update_check_uri); - $latestversion = explode('|', $latestversion); + if (function_exists('curl_version')) { + $update_check_uri = 'http://version.froxlor.org/Froxlor/legacy/' . $version; + $latestversion = HttpClient::urlGet($update_check_uri); + $latestversion = explode('|', $latestversion); - if (is_array($latestversion) - && count($latestversion) >= 1 - ) { - $_version = $latestversion[0]; - $_message = isset($latestversion[1]) ? $latestversion[1] : ''; - $_link = isset($latestversion[2]) ? $latestversion[2] : htmlspecialchars($filename . '?s=' . urlencode($s) . '&page=' . urlencode($page) . '&lookfornewversion=yes'); + if (is_array($latestversion) + && count($latestversion) >= 1 + ) { + $_version = $latestversion[0]; + $_message = isset($latestversion[1]) ? $latestversion[1] : ''; + $_link = isset($latestversion[2]) ? $latestversion[2] : htmlspecialchars($filename . '?s=' . urlencode($s) . '&page=' . urlencode($page) . '&lookfornewversion=yes'); - // add the branding so debian guys are not gettings confused - // about their version-number - $lookfornewversion_lable = $_version.$branding; - $lookfornewversion_link = $_link; - $lookfornewversion_addinfo = $_message; + // add the branding so debian guys are not gettings confused + // about their version-number + $lookfornewversion_lable = $_version.$branding; + $lookfornewversion_link = $_link; + $lookfornewversion_addinfo = $_message; - // not numeric -> error-message - if (!preg_match('/^((\d+\\.)(\d+\\.)(\d+\\.)?(\d+)?(\-(svn|dev|rc)(\d+))?)$/', $_version)) { - // check for customized version to not output - // "There is a newer version of froxlor" besides the error-message - $isnewerversion = 2; - } elseif (version_compare2($version, $_version) == -1) { - $isnewerversion = 1; + // not numeric -> error-message + if (!preg_match('/^((\d+\\.)(\d+\\.)(\d+\\.)?(\d+)?(\-(svn|dev|rc)(\d+))?)$/', $_version)) { + // check for customized version to not output + // "There is a newer version of froxlor" besides the error-message + $isnewerversion = 2; + } elseif (version_compare2($version, $_version) == -1) { + $isnewerversion = 1; + } else { + $isnewerversion = 0; + } } else { - $isnewerversion = 0; + redirectTo($update_check_uri.'/pretty', NULL, false); } } else { - redirectTo($update_check_uri.'/pretty', NULL, false); + $lookfornewversion_lable = "Version-check not available due to missing php-curl extension"; + $lookfornewversion_link = htmlspecialchars($filename . '?s=' . urlencode($s) . '&page=' . urlencode($page) . '&lookfornewversion=yes'); + $lookfornewversion_addinfo = ''; + $isnewerversion = 0; } } else { $lookfornewversion_lable = $lng['admin']['lookfornewversion']['clickhere']; From 30d39d622d2daef59c9fc97822bd4902dd14d1a3 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Thu, 29 Mar 2018 13:10:33 +0200 Subject: [PATCH 173/746] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 33cb17cb..171035cf 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ https://files.froxlor.org/releases/froxlor-latest.tar.gz [MD5](https://files.fro [HowTo](https://github.com/Froxlor/Froxlor/wiki/Install-froxlor-on-debian) /etc/apt/sources.list.d/froxlor.list -> deb http://debian.froxlor.org {wheezy|jessie} main +> deb http://debian.froxlor.org {wheezy|jessie|stretch} main ### Gentoo repository From 85407abfb4ec5632292bf43534c3d9750403169b Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sun, 1 Apr 2018 09:59:25 +0200 Subject: [PATCH 174/746] optimize stats-folder-decision in Customers-ApiCommand Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/commands/class.Customers.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/classes/api/commands/class.Customers.php b/lib/classes/api/commands/class.Customers.php index ae02b9c5..6f8da50e 100644 --- a/lib/classes/api/commands/class.Customers.php +++ b/lib/classes/api/commands/class.Customers.php @@ -431,7 +431,7 @@ class Customers extends ApiCommand implements ResourceEntity // Using filesystem - quota, insert a task which cleans the filesystem - quota inserttask('10'); - // Add htpasswd for the webalizer stats + // Add htpasswd for the stats-pages if (CRYPT_STD_DES == 1) { $saltfordescrypt = substr(md5(uniqid(microtime(), 1)), 4, 2); $htpasswdPassword = crypt($password, $saltfordescrypt); @@ -452,13 +452,12 @@ class Customers extends ApiCommand implements ResourceEntity 'passwd' => $htpasswdPassword ); + $stats_folder = 'webalizer'; if (Settings::Get('system.awstats_enabled') == '1') { - $ins_data['path'] = makeCorrectDir($documentroot . '/awstats/'); - $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] automatically added awstats htpasswd for user '" . $loginname . "'"); - } else { - $ins_data['path'] = makeCorrectDir($documentroot . '/webalizer/'); - $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] automatically added webalizer htpasswd for user '" . $loginname . "'"); + $stats_folder = 'awstats'; } + $ins_data['path'] = makeCorrectDir($documentroot . '/' . $stats_folder . '/'); + $this->logger()->logAction(ADM_ACTION, LOG_NOTICE, "[API] automatically added ".$stats_folder." htpasswd for user '" . $loginname . "'"); Database::pexecute($ins_stmt, $ins_data, true, true); inserttask('1'); From 3f69c97874076356b9556ee522c499071d5dbc03 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sun, 1 Apr 2018 10:31:38 +0200 Subject: [PATCH 175/746] opzimize ApiParameter::getModFunctionString(); corrected FpmDaemons::update(); added a few more unit-tests Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/api/abstract.ApiParameter.php | 38 +++-- lib/classes/api/commands/class.FpmDaemons.php | 5 +- tests/Customers/CustomersTest.php | 3 +- tests/PhpAndFpm/FpmDaemonsTest.php | 150 +++++++++++++++++- 4 files changed, 175 insertions(+), 21 deletions(-) diff --git a/lib/classes/api/abstract.ApiParameter.php b/lib/classes/api/abstract.ApiParameter.php index c51f4088..4bc957d3 100644 --- a/lib/classes/api/abstract.ApiParameter.php +++ b/lib/classes/api/abstract.ApiParameter.php @@ -117,29 +117,33 @@ abstract class ApiParameter * returns "module::function()" for better error-messages (missing parameter etc.) * makes debugging a whole lot more comfortable * + * @param int $level + * depth of backtrace, default 2 + * * @return string */ - private function getModFunctionString() + private function getModFunctionString($level = 1, $max_level = 5, $trace = null) { + // which class called us $_class = get_called_class(); - $level = 2; - if (version_compare(PHP_VERSION, "5.4.0", "<")) { - $trace = debug_backtrace(); - } else { - $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); - } - while (true) { - $class = $trace[$level]['class']; - $func = $trace[$level]['function']; - if ($class != $_class) { - $level ++; - if ($level > 5) { - break; - } - continue; + if (empty($trace)) { + // check php version for backtrace flags + $_traceopts = false; + if (version_compare(PHP_VERSION, "5.3.6", ">")) { + $_traceopts = DEBUG_BACKTRACE_IGNORE_ARGS; } - return $class . ':' . $func; + // get backtrace + $trace = debug_backtrace($_traceopts); } + // check class and function + $class = $trace[$level]['class']; + $func = $trace[$level]['function']; + // is it the one we are looking for? + if ($class != $_class && $level <= $max_level) { + // check one level deeper + return $this->getModFunctionString(++ $level, $max_level, $trace); + } + return $class . ':' . $func; } /** diff --git a/lib/classes/api/commands/class.FpmDaemons.php b/lib/classes/api/commands/class.FpmDaemons.php index c1aedbbb..9e071235 100644 --- a/lib/classes/api/commands/class.FpmDaemons.php +++ b/lib/classes/api/commands/class.FpmDaemons.php @@ -270,7 +270,10 @@ class FpmDaemons extends ApiCommand implements ResourceEntity inserttask('1'); $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] fpm-daemon with description '" . $description . "' has been updated by '" . $this->getUserDetail('loginname') . "'"); - return $this->response(200, "successfull", $upd_data); + $result = $this->apiCall('FpmDaemons.get', array( + 'id' => $id + )); + return $this->response(200, "successfull", $result); } throw new Exception("Not allowed to execute given command.", 403); } diff --git a/tests/Customers/CustomersTest.php b/tests/Customers/CustomersTest.php index 20b3eafb..1b99b6fc 100644 --- a/tests/Customers/CustomersTest.php +++ b/tests/Customers/CustomersTest.php @@ -21,7 +21,8 @@ class CustomersTest extends TestCase 'firstname' => 'Test', 'name' => 'Testman', 'customernumber' => 1337, - 'diskspace' => - 1, + 'diskspace' => 0, + 'diskspace_ul' => 1, 'traffic' => - 1, 'subdomains' => 15, 'emails' => - 1, diff --git a/tests/PhpAndFpm/FpmDaemonsTest.php b/tests/PhpAndFpm/FpmDaemonsTest.php index f85ec30e..86ae54db 100644 --- a/tests/PhpAndFpm/FpmDaemonsTest.php +++ b/tests/PhpAndFpm/FpmDaemonsTest.php @@ -17,7 +17,8 @@ class FpmDaemonsTest extends TestCase $data = [ 'description' => 'test2 fpm', 'reload_cmd' => 'service php7.1-fpm reload', - 'config_dir' => '/etc/php/7.1/fpm/pool.d' + 'config_dir' => '/etc/php/7.1/fpm/pool.d', + 'limit_extensions' => '' ]; $json_result = FpmDaemons::getLocal($admin_userdata, $data)->add(); $result = json_decode($json_result, true)['data']; @@ -27,6 +28,33 @@ class FpmDaemonsTest extends TestCase self::$id = $result['id']; } + public function testAdminFpmDaemonsAddUnknownPM() + { + global $admin_userdata; + $data = [ + 'description' => 'test2 fpm', + 'reload_cmd' => 'service php7.3-fpm reload', + 'config_dir' => '/etc/php/7.3/fpm/pool.d', + 'pm' => 'supermegapm' + ]; + $this->expectExceptionCode(406); + $this->expectExceptionMessage("Unknown process manager"); + FpmDaemons::getLocal($admin_userdata, $data)->add(); + } + + public function testAdminFpmDaemonsAddInvalidDesc() + { + // max 50. characters + global $admin_userdata; + $data = [ + 'description' => str_repeat('test', 30), + 'reload_cmd' => 'service php7.3-fpm reload', + 'config_dir' => '/etc/php/7.3/fpm/pool.d' + ]; + $this->expectExceptionMessage("The description is too short, too long or contains illegal characters."); + FpmDaemons::getLocal($admin_userdata, $data)->add(); + } + /** * @depends testAdminFpmDaemonsAdd */ @@ -48,6 +76,51 @@ class FpmDaemonsTest extends TestCase $this->assertEquals('.php .php.xml', $result['limit_extensions']); } + /** + * @depends testAdminFpmDaemonsUpdate + */ + public function testAdminFpmDaemonsUpdate2() + { + global $admin_userdata; + $data = [ + 'id' => self::$id, + 'limit_extensions' => '', + ]; + $json_result = FpmDaemons::getLocal($admin_userdata, $data)->update(); + $result = json_decode($json_result, true)['data']; + $this->assertEquals('.php', $result['limit_extensions']); + } + + /** + * @depends testAdminFpmDaemonsAdd + */ + public function testAdminFpmDaemonsUpdateUnknownPM() + { + global $admin_userdata; + $data = [ + 'id' => self::$id, + 'pm' => 'supermegapm' + ]; + $this->expectExceptionCode(406); + $this->expectExceptionMessage("Unknown process manager"); + FpmDaemons::getLocal($admin_userdata, $data)->update(); + } + + /** + * @depends testAdminFpmDaemonsAdd + */ + public function testAdminFpmDaemonsUpdateInvalidDesc() + { + // max 50. characters + global $admin_userdata; + $data = [ + 'id' => self::$id, + 'description' => str_repeat('test', 30) + ]; + $this->expectExceptionMessage("The description is too short, too long or contains illegal characters."); + FpmDaemons::getLocal($admin_userdata, $data)->update(); + } + /** * @depends testAdminFpmDaemonsUpdate */ @@ -61,6 +134,79 @@ class FpmDaemonsTest extends TestCase $this->assertEquals('test2 fpm edit', $result['list'][1]['description']); } + public function testAdminFpmDaemonsGetNotFound() + { + global $admin_userdata; + $this->expectExceptionCode(404); + $this->expectExceptionMessage("fpm-daemon with id #-1 could not be found"); + FpmDaemons::getLocal($admin_userdata, array('id' => -1))->get(); + } + + public function testCustomerFpmDaemonsAdd() + { + global $admin_userdata; + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $this->expectExceptionCode(403); + $this->expectExceptionMessage("Not allowed to execute given command."); + FpmDaemons::getLocal($customer_userdata)->add(); + } + + public function testCustomerFpmDaemonsGet() + { + global $admin_userdata; + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $this->expectExceptionCode(403); + $this->expectExceptionMessage("Not allowed to execute given command."); + FpmDaemons::getLocal($customer_userdata)->get(); + } + + public function testCustomerFpmDaemonsList() + { + global $admin_userdata; + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $this->expectExceptionCode(403); + $this->expectExceptionMessage("Not allowed to execute given command."); + FpmDaemons::getLocal($customer_userdata)->listing(); + } + + public function testCustomerFpmDaemonsUpdate() + { + global $admin_userdata; + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $this->expectExceptionCode(403); + $this->expectExceptionMessage("Not allowed to execute given command."); + FpmDaemons::getLocal($customer_userdata)->update(); + } + + public function testCustomerFpmDaemonsDelete() + { + global $admin_userdata; + // get customer + $json_result = Customers::getLocal($admin_userdata, array( + 'loginname' => 'test1' + ))->get(); + $customer_userdata = json_decode($json_result, true)['data']; + $this->expectExceptionCode(403); + $this->expectExceptionMessage("Not allowed to execute given command."); + FpmDaemons::getLocal($customer_userdata)->delete(); + } + /** * @depends testAdminFpmDaemonsList */ @@ -74,7 +220,7 @@ class FpmDaemonsTest extends TestCase $result = json_decode($json_result, true)['data']; $this->assertEquals('/etc/php/7.1/fpm/pool.d/', $result['config_dir']); $this->assertEquals(10, $result['max_children']); - $this->assertEquals('.php .php.xml', $result['limit_extensions']); + $this->assertEquals('.php', $result['limit_extensions']); } /** From 060115c5e995932146fa932df3970f5643b649a6 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sun, 1 Apr 2018 10:35:55 +0200 Subject: [PATCH 176/746] added new ApiParameterTest Signed-off-by: Michael Kaufmann (d00p) --- tests/Global/ApiParameterTest.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/Global/ApiParameterTest.php diff --git a/tests/Global/ApiParameterTest.php b/tests/Global/ApiParameterTest.php new file mode 100644 index 00000000..8611baca --- /dev/null +++ b/tests/Global/ApiParameterTest.php @@ -0,0 +1,18 @@ +expectExceptionCode(404); + $this->expectExceptionMessage('Requested parameter "key" could not be found for "Froxlor:getSetting"'); + Froxlor::getLocal($admin_userdata)->getSetting(); + } + +} From a21f3c5a3f92eafd9d96f1cc7ac9705d128b12b4 Mon Sep 17 00:00:00 2001 From: Erik <32094350+z3dm4n@users.noreply.github.com> Date: Tue, 3 Apr 2018 12:59:14 +0200 Subject: [PATCH 177/746] added xenial.xml (#533) * added xenial.xml * fixed touch commands as in commit 3c802038f21dabcc558eb756a59b1bd6e280ede4 * changed a2dismod php to a2dismod php7.0 * fixed overlooked a2dismod php to a2dismod php7.0 --- lib/configfiles/xenial.xml | 4680 ++++++++++++++++++++++++++++++++++++ 1 file changed, 4680 insertions(+) create mode 100644 lib/configfiles/xenial.xml diff --git a/lib/configfiles/xenial.xml b/lib/configfiles/xenial.xml new file mode 100644 index 00000000..34877c1f --- /dev/null +++ b/lib/configfiles/xenial.xml @@ -0,0 +1,4680 @@ + + + + + + + + + + + {{settings.system.apacheconf_vhost}} + + + + + {{settings.system.apacheconf_vhost}} + + + + + + + {{settings.system.apacheconf_diroptions}} + + + + + {{settings.system.apacheconf_diroptions}} + + + + + + + + + + + {{settings.system.deactivateddocroot}} + + + + + + + + + //service[@type='http']/general/commands + + + + {{settings.system.use_ssl}} + + + + + {{settings.phpfpm.enabled}} + + + + + {{settings.phpfpm.enabled}} + + + FastCgiIpcDir + + + Require all granted + Require env REDIRECT_STATUS + + +]]> + + + + {{settings.system.leenabled}} + + + Require all granted + +]]> + + + + + + + + + "{{settings.system.letsencryptchallengepath}}/.well-known/acme-challenge/") + +# default listening port for IPv6 falls back to the IPv4 port +include_shell "/usr/share/lighttpd/use-ipv6.pl " + server.port +include_shell "/usr/share/lighttpd/create-mime.assign.pl" +include_shell "/usr/share/lighttpd/include-conf-enabled.pl" +]]> + + + //service[@type='http']/general/commands + + {{settings.system.apacheconf_vhost}} + + > /etc/lighttpd/lighttpd.conf]]> + + + {{settings.system.apacheconf_vhost}} + + > /etc/lighttpd/lighttpd.conf]]> + + + {{settings.system.apacheconf_diroptions}} + + > /etc/lighttpd/lighttpd.conf]]> + + + {{settings.system.apacheconf_diroptions}} + + > /etc/lighttpd/lighttpd.conf]]> + + + + + + + + + + {{settings.phpfpm.enabled}} + + {{settings.system.mod_fcgid}} + + + + + + + + + + + + + {{settings.system.leenabled}} + + + + + + {{settings.phpfpm.enabled}} + + {{settings.system.mod_fcgid}} + + + + + //service[@type='http']/general/commands + + {{settings.phpfpm.enabled}} + + {{settings.system.mod_fcgid}} + + + + + + + + + + + + > /etc/bind/named.conf.local]]> + + + + + + + + + +# add these entries to the list if any speficied: + +################################# +# allow-dnsupdate-from A global setting to allow DNS updates from these IP ranges. +# +# allow-dnsupdate-from=127.0.0.0/8,::1 + +################################# +# allow-recursion List of subnets that are allowed to recurse +# +allow-recursion=127.0.0.1 + +################################# +# also-notify When notifying a domain, also notify these nameservers +# +# also-notify= + +################################# +# any-to-tcp Answer ANY queries with tc=1, shunting to TCP +# +# any-to-tcp=no + +################################# +# cache-ttl Seconds to store packets in the PacketCache +# +# cache-ttl=20 + +################################# +# carbon-interval Number of seconds between carbon (graphite) updates +# +# carbon-interval=30 + +################################# +# carbon-ourname If set, overrides our reported hostname for carbon stats +# +# carbon-ourname= + +################################# +# carbon-server If set, send metrics in carbon (graphite) format to this server +# +# carbon-server= + +################################# +# chroot If set, chroot to this directory for more security +# +# chroot= + +################################# +# config-dir Location of configuration directory (pdns.conf) +# +config-dir=/etc/powerdns + +################################# +# config-name Name of this virtual configuration - will rename the binary image +# +# config-name= + +################################# +# control-console Debugging switch - don't use +# +# control-console=no + +################################# +# daemon Operate as a daemon +# +daemon=yes + +################################# +# default-ksk-algorithms Default KSK algorithms +# +# default-ksk-algorithms=rsasha256 + +################################# +# default-ksk-size Default KSK size (0 means default) +# +# default-ksk-size=0 + +################################# +# default-soa-mail mail address to insert in the SOA record if none set in the backend +# +# default-soa-mail= + +################################# +# default-soa-name name to insert in the SOA record if none set in the backend +# +# default-soa-name=a.misconfigured.powerdns.server + +################################# +# default-ttl Seconds a result is valid if not set otherwise +# +# default-ttl=3600 + +################################# +# default-zsk-algorithms Default ZSK algorithms +# +# default-zsk-algorithms=rsasha256 + +################################# +# default-zsk-size Default ZSK size (0 means default) +# +# default-zsk-size=0 + +################################# +# direct-dnskey Fetch DNSKEY RRs from backend during DNSKEY synthesis +# +# direct-dnskey=no + +################################# +# disable-axfr Disable zonetransfers but do allow TCP queries +# +# disable-axfr=no + +################################# +# disable-axfr-rectify Disable the rectify step during an outgoing AXFR. Only required for regression testing. +# +# disable-axfr-rectify=no + +################################# +# disable-tcp Do not listen to TCP queries +# +# disable-tcp=no + +################################# +# distributor-threads Default number of Distributor (backend) threads to start +# +# distributor-threads=3 + +################################# +# do-ipv6-additional-processing Do AAAA additional processing +# +# do-ipv6-additional-processing=yes + +################################# +# edns-subnet-processing If we should act on EDNS Subnet options +# +# edns-subnet-processing=no + +################################# +# entropy-source If set, read entropy from this file +# +# entropy-source=/dev/urandom + +################################# +# experimental-api-key REST API Static authentication key (required for API use) +# +# experimental-api-key= + +################################# +# experimental-api-readonly If the JSON API should disallow data modification +# +# experimental-api-readonly=no + +################################# +# experimental-dname-processing If we should support DNAME records +# +# experimental-dname-processing=no + +################################# +# experimental-dnsupdate Enable/Disable DNS update (RFC2136) support. Default is no. +# +# experimental-dnsupdate=no + +################################# +# experimental-json-interface If the webserver should serve JSON data +# +# experimental-json-interface=no + +################################# +# experimental-logfile Filename of the log file for JSON parser +# +# experimental-logfile=/var/log/pdns.log + +################################# +# forward-dnsupdate A global setting to allow DNS update packages that are for a Slave domain, to be forwarded to the master. +# +# forward-dnsupdate=yes + +################################# +# guardian Run within a guardian process +# +guardian=yes + +################################# +# include-dir Include *.conf files from this directory +# +# include-dir= + +################################# +# launch Which backends to launch and order to query them in +# +# launch= + +################################# +# load-modules Load this module - supply absolute or relative path +# +# load-modules= + +################################# +# local-address Local IP addresses to which we bind +# +local-address=,127.0.0.1 + +################################# +# local-address-nonexist-fail Fail to start if one or more of the local-address's do not exist on this server +# +# local-address-nonexist-fail=yes + +################################# +# local-ipv6 Local IP address to which we bind +# +# local-ipv6= + +################################# +# local-ipv6-nonexist-fail Fail to start if one or more of the local-ipv6 addresses do not exist on this server +# +# local-ipv6-nonexist-fail=yes + +################################# +# local-port The port on which we listen +# +# local-port=53 + +################################# +# log-dns-details If PDNS should log DNS non-erroneous details +# +# log-dns-details=no + +################################# +# log-dns-queries If PDNS should log all incoming DNS queries +# +# log-dns-queries=no + +################################# +# logging-facility Log under a specific facility +# +# logging-facility= + +################################# +# loglevel Amount of logging. Higher is more. Do not set below 3 +# +# loglevel=4 + +################################# +# lua-prequery-script Lua script with prequery handler +# +# lua-prequery-script= + +################################# +# master Act as a master +# +master=yes + +################################# +# max-cache-entries Maximum number of cache entries +# +# max-cache-entries=1000000 + +################################# +# max-ent-entries Maximum number of empty non-terminals in a zone +# +# max-ent-entries=100000 + +################################# +# max-nsec3-iterations Limit the number of NSEC3 hash iterations +# +# max-nsec3-iterations=500 + +################################# +# max-queue-length Maximum queuelength before considering situation lost +# +# max-queue-length=5000 + +################################# +# max-signature-cache-entries Maximum number of signatures cache entries +# +# max-signature-cache-entries= + +################################# +# max-tcp-connections Maximum number of TCP connections +# +# max-tcp-connections=10 + +################################# +# module-dir Default directory for modules +# +# module-dir=/usr/lib/TRIPLET/pdns + +################################# +# negquery-cache-ttl Seconds to store negative query results in the QueryCache +# +# negquery-cache-ttl=60 + +################################# +# no-shuffle Set this to prevent random shuffling of answers - for regression testing +# +# no-shuffle=off + +################################# +# only-notify Only send AXFR NOTIFY to these IP addresses or netmasks +# +# only-notify=0.0.0.0/0,::/0 + +################################# +# out-of-zone-additional-processing Do out of zone additional processing +# +# out-of-zone-additional-processing=yes + +################################# +# overload-queue-length Maximum queuelength moving to packetcache only +# +# overload-queue-length=0 + +################################# +# pipebackend-abi-version Version of the pipe backend ABI +# +# pipebackend-abi-version=1 + +################################# +# prevent-self-notification Don't send notifications to what we think is ourself +# +# prevent-self-notification=yes + +################################# +# query-cache-ttl Seconds to store query results in the QueryCache +# +# query-cache-ttl=20 + +################################# +# query-local-address Source IP address for sending queries +# +# query-local-address=0.0.0.0 + +################################# +# query-local-address6 Source IPv6 address for sending queries +# +# query-local-address6=:: + +################################# +# query-logging Hint backends that queries should be logged +# +# query-logging=no + +################################# +# queue-limit Maximum number of milliseconds to queue a query +# +# queue-limit=1500 + +################################# +# receiver-threads Default number of receiver threads to start +# +# receiver-threads=1 + +################################# +# recursive-cache-ttl Seconds to store packets for recursive queries in the PacketCache +# +# recursive-cache-ttl=10 + +################################# +# recursor If recursion is desired, IP address of a recursing nameserver +# +# recursor=no + +################################# +# retrieval-threads Number of AXFR-retrieval threads for slave operation +# +# retrieval-threads=2 + +################################# +# reuseport Enable higher performance on compliant kernels by using SO_REUSEPORT allowing each receiver thread to open its own socket +# +# reuseport=no + +################################# +# security-poll-suffix Domain name from which to query security update notifications +# +# security-poll-suffix=secpoll.powerdns.com. + +################################# +# send-root-referral Send out old-fashioned root-referral instead of ServFail in case of no authority +# +# send-root-referral=no + +################################# +# server-id Returned when queried for 'server.id' TXT or NSID, defaults to hostname - disabled or custom +# +# server-id= + +################################# +# setgid If set, change group id to this gid for more security +# +setgid=pdns + +################################# +# setuid If set, change user id to this uid for more security +# +setuid=pdns + +################################# +# signing-threads Default number of signer threads to start +# +# signing-threads=3 + +################################# +# slave Act as a slave +# +# slave=no + +################################# +# slave-cycle-interval Reschedule failed SOA serial checks once every .. seconds +# +# slave-cycle-interval=60 + +################################# +# slave-renotify If we should send out notifications for slaved updates +# +# slave-renotify=no + +################################# +# soa-expire-default Default SOA expire +# +# soa-expire-default=604800 + +################################# +# soa-minimum-ttl Default SOA minimum ttl +# +# soa-minimum-ttl=3600 + +################################# +# soa-refresh-default Default SOA refresh +# +# soa-refresh-default=10800 + +################################# +# soa-retry-default Default SOA retry +# +# soa-retry-default=3600 + +################################# +# socket-dir Where the controlsocket will live +# +# socket-dir=/var/run + +################################# +# tcp-control-address If set, PowerDNS can be controlled over TCP on this address +# +# tcp-control-address= + +################################# +# tcp-control-port If set, PowerDNS can be controlled over TCP on this address +# +# tcp-control-port=53000 + +################################# +# tcp-control-range If set, remote control of PowerDNS is possible over these networks only +# +# tcp-control-range=127.0.0.0/8, 10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, ::1/128, fe80::/10 + +################################# +# tcp-control-secret If set, PowerDNS can be controlled over TCP after passing this secret +# +# tcp-control-secret= + +################################# +# traceback-handler Enable the traceback handler (Linux only) +# +# traceback-handler=yes + +################################# +# trusted-notification-proxy IP address of incoming notification proxy +# +# trusted-notification-proxy= + +################################# +# udp-truncation-threshold Maximum UDP response size before we truncate +# +# udp-truncation-threshold=1680 + +################################# +# version-string PowerDNS version in packets - full, anonymous, powerdns or custom +# + +version-string=powerdns +################################# +# webserver Start a webserver for monitoring +# +# webserver=no + +################################# +# webserver-address IP Address of webserver to listen on +# +# webserver-address=127.0.0.1 + +################################# +# webserver-allow-from Webserver access is only allowed from these subnets +# +# webserver-allow-from=0.0.0.0/0,::/0 + +################################# +# webserver-password Password required for accessing the webserver +# +# webserver-password= + +################################# +# webserver-port Port of webserver to listen on +# +# webserver-port=8081 + +################################# +# webserver-print-arguments If the webserver should print arguments +# +# webserver-print-arguments=no + +# include froxlor-bind-specific config +include-dir=/etc/powerdns/froxlor/ +]]> + + + + + + + + + + + + + +# add these entries to the list if any speficied: + +################################# +# allow-dnsupdate-from A global setting to allow DNS updates from these IP ranges. +# +# allow-dnsupdate-from=127.0.0.0/8,::1 + +################################# +# allow-recursion List of subnets that are allowed to recurse +# +allow-recursion=127.0.0.1 + +################################# +# also-notify When notifying a domain, also notify these nameservers +# +# also-notify= + +################################# +# any-to-tcp Answer ANY queries with tc=1, shunting to TCP +# +# any-to-tcp=no + +################################# +# cache-ttl Seconds to store packets in the PacketCache +# +# cache-ttl=20 + +################################# +# carbon-interval Number of seconds between carbon (graphite) updates +# +# carbon-interval=30 + +################################# +# carbon-ourname If set, overrides our reported hostname for carbon stats +# +# carbon-ourname= + +################################# +# carbon-server If set, send metrics in carbon (graphite) format to this server +# +# carbon-server= + +################################# +# chroot If set, chroot to this directory for more security +# +# chroot= + +################################# +# config-dir Location of configuration directory (pdns.conf) +# +config-dir=/etc/powerdns + +################################# +# config-name Name of this virtual configuration - will rename the binary image +# +# config-name= + +################################# +# control-console Debugging switch - don't use +# +# control-console=no + +################################# +# daemon Operate as a daemon +# +daemon=yes + +################################# +# default-ksk-algorithms Default KSK algorithms +# +# default-ksk-algorithms=rsasha256 + +################################# +# default-ksk-size Default KSK size (0 means default) +# +# default-ksk-size=0 + +################################# +# default-soa-mail mail address to insert in the SOA record if none set in the backend +# +# default-soa-mail= + +################################# +# default-soa-name name to insert in the SOA record if none set in the backend +# +# default-soa-name=a.misconfigured.powerdns.server + +################################# +# default-ttl Seconds a result is valid if not set otherwise +# +# default-ttl=3600 + +################################# +# default-zsk-algorithms Default ZSK algorithms +# +# default-zsk-algorithms=rsasha256 + +################################# +# default-zsk-size Default ZSK size (0 means default) +# +# default-zsk-size=0 + +################################# +# direct-dnskey Fetch DNSKEY RRs from backend during DNSKEY synthesis +# +# direct-dnskey=no + +################################# +# disable-axfr Disable zonetransfers but do allow TCP queries +# +# disable-axfr=no + +################################# +# disable-axfr-rectify Disable the rectify step during an outgoing AXFR. Only required for regression testing. +# +# disable-axfr-rectify=no + +################################# +# disable-tcp Do not listen to TCP queries +# +# disable-tcp=no + +################################# +# distributor-threads Default number of Distributor (backend) threads to start +# +# distributor-threads=3 + +################################# +# do-ipv6-additional-processing Do AAAA additional processing +# +# do-ipv6-additional-processing=yes + +################################# +# edns-subnet-processing If we should act on EDNS Subnet options +# +# edns-subnet-processing=no + +################################# +# entropy-source If set, read entropy from this file +# +# entropy-source=/dev/urandom + +################################# +# experimental-api-key REST API Static authentication key (required for API use) +# +# experimental-api-key= + +################################# +# experimental-api-readonly If the JSON API should disallow data modification +# +# experimental-api-readonly=no + +################################# +# experimental-dname-processing If we should support DNAME records +# +# experimental-dname-processing=no + +################################# +# experimental-dnsupdate Enable/Disable DNS update (RFC2136) support. Default is no. +# +# experimental-dnsupdate=no + +################################# +# experimental-json-interface If the webserver should serve JSON data +# +# experimental-json-interface=no + +################################# +# experimental-logfile Filename of the log file for JSON parser +# +# experimental-logfile=/var/log/pdns.log + +################################# +# forward-dnsupdate A global setting to allow DNS update packages that are for a Slave domain, to be forwarded to the master. +# +# forward-dnsupdate=yes + +################################# +# guardian Run within a guardian process +# +guardian=yes + +################################# +# include-dir Include *.conf files from this directory +# +# include-dir= + +################################# +# launch Which backends to launch and order to query them in +# +# launch= +launch=bind + +################################# +# load-modules Load this module - supply absolute or relative path +# +# load-modules= + +################################# +# local-address Local IP addresses to which we bind +# +local-address=,127.0.0.1 + +################################# +# local-address-nonexist-fail Fail to start if one or more of the local-address's do not exist on this server +# +# local-address-nonexist-fail=yes + +################################# +# local-ipv6 Local IP address to which we bind +# +# local-ipv6= + +################################# +# local-ipv6-nonexist-fail Fail to start if one or more of the local-ipv6 addresses do not exist on this server +# +# local-ipv6-nonexist-fail=yes + +################################# +# local-port The port on which we listen +# +# local-port=53 + +################################# +# log-dns-details If PDNS should log DNS non-erroneous details +# +# log-dns-details=no + +################################# +# log-dns-queries If PDNS should log all incoming DNS queries +# +# log-dns-queries=no + +################################# +# logging-facility Log under a specific facility +# +# logging-facility= + +################################# +# loglevel Amount of logging. Higher is more. Do not set below 3 +# +# loglevel=4 + +################################# +# lua-prequery-script Lua script with prequery handler +# +# lua-prequery-script= + +################################# +# master Act as a master +# +master=yes + +################################# +# max-cache-entries Maximum number of cache entries +# +# max-cache-entries=1000000 + +################################# +# max-ent-entries Maximum number of empty non-terminals in a zone +# +# max-ent-entries=100000 + +################################# +# max-nsec3-iterations Limit the number of NSEC3 hash iterations +# +# max-nsec3-iterations=500 + +################################# +# max-queue-length Maximum queuelength before considering situation lost +# +# max-queue-length=5000 + +################################# +# max-signature-cache-entries Maximum number of signatures cache entries +# +# max-signature-cache-entries= + +################################# +# max-tcp-connections Maximum number of TCP connections +# +# max-tcp-connections=10 + +################################# +# module-dir Default directory for modules +# +# module-dir=/usr/lib/TRIPLET/pdns + +################################# +# negquery-cache-ttl Seconds to store negative query results in the QueryCache +# +# negquery-cache-ttl=60 + +################################# +# no-shuffle Set this to prevent random shuffling of answers - for regression testing +# +# no-shuffle=off + +################################# +# only-notify Only send AXFR NOTIFY to these IP addresses or netmasks +# +# only-notify=0.0.0.0/0,::/0 + +################################# +# out-of-zone-additional-processing Do out of zone additional processing +# +# out-of-zone-additional-processing=yes + +################################# +# overload-queue-length Maximum queuelength moving to packetcache only +# +# overload-queue-length=0 + +################################# +# pipebackend-abi-version Version of the pipe backend ABI +# +# pipebackend-abi-version=1 + +################################# +# prevent-self-notification Don't send notifications to what we think is ourself +# +# prevent-self-notification=yes + +################################# +# query-cache-ttl Seconds to store query results in the QueryCache +# +# query-cache-ttl=20 + +################################# +# query-local-address Source IP address for sending queries +# +# query-local-address=0.0.0.0 + +################################# +# query-local-address6 Source IPv6 address for sending queries +# +# query-local-address6=:: + +################################# +# query-logging Hint backends that queries should be logged +# +# query-logging=no + +################################# +# queue-limit Maximum number of milliseconds to queue a query +# +# queue-limit=1500 + +################################# +# receiver-threads Default number of receiver threads to start +# +# receiver-threads=1 + +################################# +# recursive-cache-ttl Seconds to store packets for recursive queries in the PacketCache +# +# recursive-cache-ttl=10 + +################################# +# recursor If recursion is desired, IP address of a recursing nameserver +# +# recursor=no + +################################# +# retrieval-threads Number of AXFR-retrieval threads for slave operation +# +# retrieval-threads=2 + +################################# +# reuseport Enable higher performance on compliant kernels by using SO_REUSEPORT allowing each receiver thread to open its own socket +# +# reuseport=no + +################################# +# security-poll-suffix Domain name from which to query security update notifications +# +# security-poll-suffix=secpoll.powerdns.com. + +################################# +# send-root-referral Send out old-fashioned root-referral instead of ServFail in case of no authority +# +# send-root-referral=no + +################################# +# server-id Returned when queried for 'server.id' TXT or NSID, defaults to hostname - disabled or custom +# +# server-id= + +################################# +# setgid If set, change group id to this gid for more security +# +setgid=pdns + +################################# +# setuid If set, change user id to this uid for more security +# +setuid=pdns + +################################# +# signing-threads Default number of signer threads to start +# +# signing-threads=3 + +################################# +# slave Act as a slave +# +# slave=no + +################################# +# slave-cycle-interval Reschedule failed SOA serial checks once every .. seconds +# +# slave-cycle-interval=60 + +################################# +# slave-renotify If we should send out notifications for slaved updates +# +# slave-renotify=no + +################################# +# soa-expire-default Default SOA expire +# +# soa-expire-default=604800 + +################################# +# soa-minimum-ttl Default SOA minimum ttl +# +# soa-minimum-ttl=3600 + +################################# +# soa-refresh-default Default SOA refresh +# +# soa-refresh-default=10800 + +################################# +# soa-retry-default Default SOA retry +# +# soa-retry-default=3600 + +################################# +# socket-dir Where the controlsocket will live +# +# socket-dir=/var/run + +################################# +# tcp-control-address If set, PowerDNS can be controlled over TCP on this address +# +# tcp-control-address= + +################################# +# tcp-control-port If set, PowerDNS can be controlled over TCP on this address +# +# tcp-control-port=53000 + +################################# +# tcp-control-range If set, remote control of PowerDNS is possible over these networks only +# +# tcp-control-range=127.0.0.0/8, 10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, ::1/128, fe80::/10 + +################################# +# tcp-control-secret If set, PowerDNS can be controlled over TCP after passing this secret +# +# tcp-control-secret= + +################################# +# traceback-handler Enable the traceback handler (Linux only) +# +# traceback-handler=yes + +################################# +# trusted-notification-proxy IP address of incoming notification proxy +# +# trusted-notification-proxy= + +################################# +# udp-truncation-threshold Maximum UDP response size before we truncate +# +# udp-truncation-threshold=1680 + +################################# +# version-string PowerDNS version in packets - full, anonymous, powerdns or custom +# + +version-string=powerdns +################################# +# webserver Start a webserver for monitoring +# +# webserver=no + +################################# +# webserver-address IP Address of webserver to listen on +# +# webserver-address=127.0.0.1 + +################################# +# webserver-allow-from Webserver access is only allowed from these subnets +# +# webserver-allow-from=0.0.0.0/0,::/0 + +################################# +# webserver-password Password required for accessing the webserver +# +# webserver-password= + +################################# +# webserver-port Port of webserver to listen on +# +# webserver-port=8081 + +################################# +# webserver-print-arguments If the webserver should print arguments +# +# webserver-print-arguments=no + +# include froxlor-bind-specific config +include-dir=/etc/powerdns/froxlor/ +]]> + + + + + named.conf + +# How often to check for zone changes. See 'Operation' section. +bind-check-interval=180 + +# Uncomment to enable Huffman compression on zone data. +# Currently saves around 20% of memory actually used, but slows down operation. +# bind-enable-huffman +]]> + + + + + + + + + + + + {{settings.system.vmail_gid}} + + + + + {{settings.system.vmail_uid}} + + + + + + + + + + + + + + + + + +password = +dbname = +hosts = +query = SELECT destination FROM mail_virtual WHERE email = '%s' AND trim(destination) <> '' +]]> + + + + +password = +dbname = +hosts = +query = SELECT domain FROM panel_domains WHERE domain = '%s' AND isemaildomain = '1' +]]> + + + + +password = +dbname = +expansion_limit = 1 +hosts = +query = SELECT CONCAT(homedir,maildir) FROM mail_users WHERE email = '%s' +]]> + + + + +password = +dbname = +hosts = +query = SELECT DISTINCT username FROM mail_users WHERE email in ((SELECT mail_virtual.email_full FROM mail_virtual WHERE mail_virtual.email = '%s' UNION SELECT mail_virtual.destination FROM mail_virtual WHERE mail_virtual.email = '%s')); +]]> + + + + +password = +dbname = +expansion_limit = 1 +hosts = +query = SELECT uid FROM mail_users WHERE email = '%s' +]]> + + + + +password = +dbname = +expansion_limit = 1 +hosts = +query = SELECT gid FROM mail_users WHERE email = '%s' +]]> + + + + +]]> + + + + + + + + + + + //service[@type='smtp']/general/commands[@index=1] + + //service[@type='smtp']/general/installs[@index=1] + + //service[@type='smtp']/general/commands[@index=2] + + + + +# SENDING MAIL +# +# The myorigin parameter specifies the domain that locally-posted +# mail appears to come from. The default is to append $myhostname, +# which is fine for small sites. If you run a domain with multiple +# machines, you should (1) change this to $mydomain and (2) set up +# a domain-wide alias database that aliases each user to +# user@that.users.mailhost. +# +# For the sake of consistency between sender and recipient addresses, +# myorigin also specifies the default domain name that is appended +# to recipient addresses that have no @domain part. +# +# Debian GNU/Linux specific: Specifying a file name will cause the +# first line of that file to be used as the name. The Debian default +# is /etc/mailname. +# +#myorigin = /etc/mailname +#myorigin = $myhostname +#myorigin = $mydomain + +# RECEIVING MAIL + +# The inet_interfaces parameter specifies the network interface +# addresses that this mail system receives mail on. By default, +# the software claims all active interfaces on the machine. The +# parameter also controls delivery of mail to user@[ip.address]. +# +# See also the proxy_interfaces parameter, for network addresses that +# are forwarded to us via a proxy or network address translator. +# +# Note: you need to stop/start Postfix when this parameter changes. +# +inet_interfaces = all +#inet_interfaces = $myhostname +#inet_interfaces = $myhostname, localhost + +# The proxy_interfaces parameter specifies the network interface +# addresses that this mail system receives mail on by way of a +# proxy or network address translation unit. This setting extends +# the address list specified with the inet_interfaces parameter. +# +# You must specify your proxy/NAT addresses when your system is a +# backup MX host for other domains, otherwise mail delivery loops +# will happen when the primary MX host is down. +# +#proxy_interfaces = +#proxy_interfaces = 1.2.3.4 + +# The mydestination parameter specifies the list of domains that this +# machine considers itself the final destination for. +# +# These domains are routed to the delivery agent specified with the +# local_transport parameter setting. By default, that is the UNIX +# compatible delivery agent that lookups all recipients in /etc/passwd +# and /etc/aliases or their equivalent. +# +# The default is $myhostname + localhost.$mydomain. On a mail domain +# gateway, you should also include $mydomain. +# +# Do not specify the names of virtual domains - those domains are +# specified elsewhere (see VIRTUAL_README). +# +# Do not specify the names of domains that this machine is backup MX +# host for. Specify those names via the relay_domains settings for +# the SMTP server, or use permit_mx_backup if you are lazy (see +# STANDARD_CONFIGURATION_README). +# +# The local machine is always the final destination for mail addressed +# to user@[the.net.work.address] of an interface that the mail system +# receives mail on (see the inet_interfaces parameter). +# +# Specify a list of host or domain names, /file/name or type:table +# patterns, separated by commas and/or whitespace. A /file/name +# pattern is replaced by its contents; a type:table is matched when +# a name matches a lookup key (the right-hand side is ignored). +# Continue long lines by starting the next line with whitespace. +# +# See also below, section "REJECTING MAIL FOR UNKNOWN LOCAL USERS". +# +#mydestination = $myhostname, localhost.$mydomain, localhost +mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain +#mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain, +# mail.$mydomain, www.$mydomain, ftp.$mydomain + +# REJECTING MAIL FOR UNKNOWN LOCAL USERS +# +# The local_recipient_maps parameter specifies optional lookup tables +# with all names or addresses of users that are local with respect +# to $mydestination, $inet_interfaces or $proxy_interfaces. +# +# If this parameter is defined, then the SMTP server will reject +# mail for unknown local users. This parameter is defined by default. +# +# To turn off local recipient checking in the SMTP server, specify +# local_recipient_maps = (i.e. empty). +# +# The default setting assumes that you use the default Postfix local +# delivery agent for local delivery. You need to update the +# local_recipient_maps setting if: +# +# - You define $mydestination domain recipients in files other than +# /etc/passwd, /etc/aliases, or the $virtual_alias_maps files. +# For example, you define $mydestination domain recipients in +# the $virtual_mailbox_maps files. +# +# - You redefine the local delivery agent in master.cf. +# +# - You redefine the "local_transport" setting in main.cf. +# +# - You use the "luser_relay", "mailbox_transport", or "fallback_transport" +# feature of the Postfix local delivery agent (see local(8)). +# +# Details are described in the LOCAL_RECIPIENT_README file. +# +# Beware: if the Postfix SMTP server runs chrooted, you probably have +# to access the passwd file via the proxymap service, in order to +# overcome chroot restrictions. The alternative, having a copy of +# the system passwd file in the chroot jail is just not practical. +# +# The right-hand side of the lookup tables is conveniently ignored. +# In the left-hand side, specify a bare username, an @domain.tld +# wild-card, or specify a user@domain.tld address. +# +#local_recipient_maps = unix:passwd.byname $alias_maps +#local_recipient_maps = proxy:unix:passwd.byname $alias_maps +#local_recipient_maps = + +# The unknown_local_recipient_reject_code specifies the SMTP server +# response code when a recipient domain matches $mydestination or +# ${proxy,inet}_interfaces, while $local_recipient_maps is non-empty +# and the recipient address or address local-part is not found. +# +# The default setting is 550 (reject mail) but it is safer to start +# with 450 (try again later) until you are certain that your +# local_recipient_maps settings are OK. +# +unknown_local_recipient_reject_code = 550 + +# TRUST AND RELAY CONTROL + +# The mynetworks parameter specifies the list of "trusted" SMTP +# clients that have more privileges than "strangers". +# +# In particular, "trusted" SMTP clients are allowed to relay mail +# through Postfix. See the smtpd_recipient_restrictions parameter +# in postconf(5). +# +# You can specify the list of "trusted" network addresses by hand +# or you can let Postfix do it for you (which is the default). +# +# By default (mynetworks_style = subnet), Postfix "trusts" SMTP +# clients in the same IP subnetworks as the local machine. +# On Linux, this does works correctly only with interfaces specified +# with the "ifconfig" command. +# +# Specify "mynetworks_style = class" when Postfix should "trust" SMTP +# clients in the same IP class A/B/C networks as the local machine. +# Don't do this with a dialup site - it would cause Postfix to "trust" +# your entire provider's network. Instead, specify an explicit +# mynetworks list by hand, as described below. +# +# Specify "mynetworks_style = host" when Postfix should "trust" +# only the local machine. +# +#mynetworks_style = class +#mynetworks_style = subnet +#mynetworks_style = host + +# Alternatively, you can specify the mynetworks list by hand, in +# which case Postfix ignores the mynetworks_style setting. +# +# Specify an explicit list of network/netmask patterns, where the +# mask specifies the number of bits in the network part of a host +# address. +# +# You can also specify the absolute pathname of a pattern file instead +# of listing the patterns here. Specify type:table for table-based lookups +# (the value on the table right-hand side is not used). +# +#mynetworks = 168.100.189.0/28, 127.0.0.0/8 +#mynetworks = $config_directory/mynetworks +#mynetworks = hash:/etc/postfix/network_table +mynetworks = 127.0.0.0/8 + +# The relay_domains parameter restricts what destinations this system will +# relay mail to. See the smtpd_recipient_restrictions description in +# postconf(5) for detailed information. +# +# By default, Postfix relays mail +# - from "trusted" clients (IP address matches $mynetworks) to any destination, +# - from "untrusted" clients to destinations that match $relay_domains or +# subdomains thereof, except addresses with sender-specified routing. +# The default relay_domains value is $mydestination. +# +# In addition to the above, the Postfix SMTP server by default accepts mail +# that Postfix is final destination for: +# - destinations that match $inet_interfaces or $proxy_interfaces, +# - destinations that match $mydestination +# - destinations that match $virtual_alias_domains, +# - destinations that match $virtual_mailbox_domains. +# These destinations do not need to be listed in $relay_domains. +# +# Specify a list of hosts or domains, /file/name patterns or type:name +# lookup tables, separated by commas and/or whitespace. Continue +# long lines by starting the next line with whitespace. A file name +# is replaced by its contents; a type:name table is matched when a +# (parent) domain appears as lookup key. +# +# NOTE: Postfix will not automatically forward mail for domains that +# list this system as their primary or backup MX host. See the +# permit_mx_backup restriction description in postconf(5). +# +#relay_domains = $mydestination + +# INTERNET OR INTRANET + +# The relayhost parameter specifies the default host to send mail to +# when no entry is matched in the optional transport(5) table. When +# no relayhost is given, mail is routed directly to the destination. +# +# On an intranet, specify the organizational domain name. If your +# internal DNS uses no MX records, specify the name of the intranet +# gateway host instead. +# +# In the case of SMTP, specify a domain, host, host:port, [host]:port, +# [address] or [address]:port; the form [host] turns off MX lookups. +# +# If you're connected via UUCP, see also the default_transport parameter. +# +#relayhost = $mydomain +#relayhost = [gateway.my.domain] +#relayhost = [mailserver.isp.tld] +#relayhost = uucphost +#relayhost = [an.ip.add.ress] + +# REJECTING UNKNOWN RELAY USERS +# +# The relay_recipient_maps parameter specifies optional lookup tables +# with all addresses in the domains that match $relay_domains. +# +# If this parameter is defined, then the SMTP server will reject +# mail for unknown relay users. This feature is off by default. +# +# The right-hand side of the lookup tables is conveniently ignored. +# In the left-hand side, specify an @domain.tld wild-card, or specify +# a user@domain.tld address. +# +#relay_recipient_maps = hash:/etc/postfix/relay_recipients + +# INPUT RATE CONTROL +# +# The in_flow_delay configuration parameter implements mail input +# flow control. This feature is turned on by default, although it +# still needs further development (it's disabled on SCO UNIX due +# to an SCO bug). +# +# A Postfix process will pause for $in_flow_delay seconds before +# accepting a new message, when the message arrival rate exceeds the +# message delivery rate. With the default 100 SMTP server process +# limit, this limits the mail inflow to 100 messages a second more +# than the number of messages delivered per second. +# +# Specify 0 to disable the feature. Valid delays are 0..10. +# +#in_flow_delay = 1s + +# ADDRESS REWRITING +# +# The ADDRESS_REWRITING_README document gives information about +# address masquerading or other forms of address rewriting including +# username->Firstname.Lastname mapping. + +# ADDRESS REDIRECTION (VIRTUAL DOMAIN) +# +# The VIRTUAL_README document gives information about the many forms +# of domain hosting that Postfix supports. + +# "USER HAS MOVED" BOUNCE MESSAGES +# +# See the discussion in the ADDRESS_REWRITING_README document. + +# TRANSPORT MAP +# +# See the discussion in the ADDRESS_REWRITING_README document. + +# ALIAS DATABASE +# +# The alias_maps parameter specifies the list of alias databases used +# by the local delivery agent. The default list is system dependent. +# +# On systems with NIS, the default is to search the local alias +# database, then the NIS alias database. See aliases(5) for syntax +# details. +# +# If you change the alias database, run "postalias /etc/aliases" (or +# wherever your system stores the mail alias file), or simply run +# "newaliases" to build the necessary DBM or DB file. +# +# It will take a minute or so before changes become visible. Use +# "postfix reload" to eliminate the delay. +# +#alias_maps = dbm:/etc/aliases +#alias_maps = hash:/etc/aliases +#alias_maps = hash:/etc/aliases, nis:mail.aliases +#alias_maps = netinfo:/aliases + +# The alias_database parameter specifies the alias database(s) that +# are built with "newaliases" or "sendmail -bi". This is a separate +# configuration parameter, because alias_maps (see above) may specify +# tables that are not necessarily all under control by Postfix. +# +#alias_database = dbm:/etc/aliases +#alias_database = dbm:/etc/mail/aliases +#alias_database = hash:/etc/aliases +#alias_database = hash:/etc/aliases, hash:/opt/majordomo/aliases + +# ADDRESS EXTENSIONS (e.g., user+foo) +# +# The recipient_delimiter parameter specifies the separator between +# user names and address extensions (user+foo). See canonical(5), +# local(8), relocated(5) and virtual(5) for the effects this has on +# aliases, canonical, virtual, relocated and .forward file lookups. +# Basically, the software tries user+foo and .forward+foo before +# trying user and .forward. +# +#recipient_delimiter = + + +# DELIVERY TO MAILBOX +# +# The home_mailbox parameter specifies the optional pathname of a +# mailbox file relative to a user's home directory. The default +# mailbox file is /var/spool/mail/user or /var/mail/user. Specify +# "Maildir/" for qmail-style delivery (the / is required). +# +#home_mailbox = Mailbox +#home_mailbox = Maildir/ + +# The mail_spool_directory parameter specifies the directory where +# UNIX-style mailboxes are kept. The default setting depends on the +# system type. +# +#mail_spool_directory = /var/mail +#mail_spool_directory = /var/spool/mail + +# The mailbox_command parameter specifies the optional external +# command to use instead of mailbox delivery. The command is run as +# the recipient with proper HOME, SHELL and LOGNAME environment settings. +# Exception: delivery for root is done as $default_user. +# +# Other environment variables of interest: USER (recipient username), +# EXTENSION (address extension), DOMAIN (domain part of address), +# and LOCAL (the address localpart). +# +# Unlike other Postfix configuration parameters, the mailbox_command +# parameter is not subjected to $parameter substitutions. This is to +# make it easier to specify shell syntax (see example below). +# +# Avoid shell meta characters because they will force Postfix to run +# an expensive shell process. Procmail alone is expensive enough. +# +# IF YOU USE THIS TO DELIVER MAIL SYSTEM-WIDE, YOU MUST SET UP AN +# ALIAS THAT FORWARDS MAIL FOR ROOT TO A REAL USER. +# +mailbox_command = /usr/lib/dovecot/deliver +#mailbox_command = /usr/bin/procmail -a "$EXTENSION" + +# The mailbox_transport specifies the optional transport in master.cf +# to use after processing aliases and .forward files. This parameter +# has precedence over the mailbox_command, fallback_transport and +# luser_relay parameters. +# +# Specify a string of the form transport:nexthop, where transport is +# the name of a mail delivery transport defined in master.cf. The +# :nexthop part is optional. For more details see the sample transport +# configuration file. +# +# NOTE: if you use this feature for accounts not in the UNIX password +# file, then you must update the "local_recipient_maps" setting in +# the main.cf file, otherwise the SMTP server will reject mail for +# non-UNIX accounts with "User unknown in local recipient table". +# +# Cyrus IMAP over LMTP. Specify ``lmtpunix cmd="lmtpd" +# listen="/var/imap/socket/lmtp" prefork=0'' in cyrus.conf. +#mailbox_transport = lmtp:unix:/var/imap/socket/lmtp +# +# Cyrus IMAP via command line. Uncomment the "cyrus...pipe" and +# subsequent line in master.cf. +#mailbox_transport = cyrus + +# The fallback_transport specifies the optional transport in master.cf +# to use for recipients that are not found in the UNIX passwd database. +# This parameter has precedence over the luser_relay parameter. +# +# Specify a string of the form transport:nexthop, where transport is +# the name of a mail delivery transport defined in master.cf. The +# :nexthop part is optional. For more details see the sample transport +# configuration file. +# +# NOTE: if you use this feature for accounts not in the UNIX password +# file, then you must update the "local_recipient_maps" setting in +# the main.cf file, otherwise the SMTP server will reject mail for +# non-UNIX accounts with "User unknown in local recipient table". +# +#fallback_transport = lmtp:unix:/file/name +#fallback_transport = cyrus +#fallback_transport = + +# The luser_relay parameter specifies an optional destination address +# for unknown recipients. By default, mail for unknown@$mydestination, +# unknown@[$inet_interfaces] or unknown@[$proxy_interfaces] is returned +# as undeliverable. +# +# The following expansions are done on luser_relay: $user (recipient +# username), $shell (recipient shell), $home (recipient home directory), +# $recipient (full recipient address), $extension (recipient address +# extension), $domain (recipient domain), $local (entire recipient +# localpart), $recipient_delimiter. Specify ${name?value} or +# ${name:value} to expand value only when $name does (does not) exist. +# +# luser_relay works only for the default Postfix local delivery agent. +# +# NOTE: if you use this feature for accounts not in the UNIX password +# file, then you must specify "local_recipient_maps =" (i.e. empty) in +# the main.cf file, otherwise the SMTP server will reject mail for +# non-UNIX accounts with "User unknown in local recipient table". +# +#luser_relay = $user@other.host +#luser_relay = $local@other.host +#luser_relay = admin+$local + +# JUNK MAIL CONTROLS +# +# The controls listed here are only a very small subset. The file +# SMTPD_ACCESS_README provides an overview. + +# The header_checks parameter specifies an optional table with patterns +# that each logical message header is matched against, including +# headers that span multiple physical lines. +# +# By default, these patterns also apply to MIME headers and to the +# headers of attached messages. With older Postfix versions, MIME and +# attached message headers were treated as body text. +# +# For details, see "man header_checks". +# +#header_checks = regexp:/etc/postfix/header_checks + +# FAST ETRN SERVICE +# +# Postfix maintains per-destination logfiles with information about +# deferred mail, so that mail can be flushed quickly with the SMTP +# "ETRN domain.tld" command, or by executing "sendmail -qRdomain.tld". +# See the ETRN_README document for a detailed description. +# +# The fast_flush_domains parameter controls what destinations are +# eligible for this service. By default, they are all domains that +# this server is willing to relay mail to. +# +#fast_flush_domains = $relay_domains + +# SHOW SOFTWARE VERSION OR NOT +# +# The smtpd_banner parameter specifies the text that follows the 220 +# code in the SMTP server's greeting banner. Some people like to see +# the mail version advertised. By default, Postfix shows no version. +# +# You MUST specify $myhostname at the start of the text. That is an +# RFC requirement. Postfix itself does not care. +# +#smtpd_banner = $myhostname ESMTP $mail_name +#smtpd_banner = $myhostname ESMTP $mail_name ($mail_version) +smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU) + + +# PARALLEL DELIVERY TO THE SAME DESTINATION +# +# How many parallel deliveries to the same user or domain? With local +# delivery, it does not make sense to do massively parallel delivery +# to the same user, because mailbox updates must happen sequentially, +# and expensive pipelines in .forward files can cause disasters when +# too many are run at the same time. With SMTP deliveries, 10 +# simultaneous connections to the same domain could be sufficient to +# raise eyebrows. +# +# Each message delivery transport has its XXX_destination_concurrency_limit +# parameter. The default is $default_destination_concurrency_limit for +# most delivery transports. For the local delivery agent the default is 2. + +#local_destination_concurrency_limit = 2 +#default_destination_concurrency_limit = 20 + +# DEBUGGING CONTROL +# +# The debug_peer_level parameter specifies the increment in verbose +# logging level when an SMTP client or server host name or address +# matches a pattern in the debug_peer_list parameter. +# +#debug_peer_level = 2 + +# The debug_peer_list parameter specifies an optional list of domain +# or network patterns, /file/name patterns or type:name tables. When +# an SMTP client or server host name or address matches a pattern, +# increase the verbose logging level by the amount specified in the +# debug_peer_level parameter. +# +#debug_peer_list = 127.0.0.1 +#debug_peer_list = some.domain + +# The debugger_command specifies the external command that is executed +# when a Postfix daemon program is run with the -D option. +# +# Use "command .. & sleep 5" so that the debugger can attach before +# the process marches on. If you use an X-based debugger, be sure to +# set up your XAUTHORITY environment variable before starting Postfix. +# +debugger_command = + PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin + ddd $daemon_directory/$process_name $process_id & sleep 5 + +# If you can't use X, use this to capture the call stack when a +# daemon crashes. The result is in a file in the configuration +# directory, and is named after the process name and the process ID. +# +# debugger_command = +# PATH=/bin:/usr/bin:/usr/local/bin; export PATH; (echo cont; +# echo where) | gdb $daemon_directory/$process_name $process_id 2>&1 +# >$config_directory/$process_name.$process_id.log & sleep 5 +# +# Another possibility is to run gdb under a detached screen session. +# To attach to the screen sesssion, su root and run "screen -r +# " where uniquely matches one of the detached +# sessions (from "screen -list"). +# +# debugger_command = +# PATH=/bin:/usr/bin:/sbin:/usr/sbin; export PATH; screen +# -dmS $process_name gdb $daemon_directory/$process_name +# $process_id & sleep 1 + +# INSTALL-TIME CONFIGURATION INFORMATION +# +# The following parameters are used when installing a new Postfix version. +# +# sendmail_path: The full pathname of the Postfix sendmail command. +# This is the Sendmail-compatible mail posting interface. +# +sendmail_path = /usr/sbin/sendmail + +# newaliases_path: The full pathname of the Postfix newaliases command. +# This is the Sendmail-compatible command to build alias databases. +# +newaliases_path = /usr/bin/newaliases + +# mailq_path: The full pathname of the Postfix mailq command. This +# is the Sendmail-compatible mail queue listing command. +# +mailq_path = /usr/bin/mailq + +# setgid_group: The group for mail submission and queue management +# commands. This must be a group name with a numerical group ID that +# is not shared with other accounts, not even with the Postfix account. +# +setgid_group = postdrop + +# html_directory: The location of the Postfix HTML documentation. +# +html_directory = no + +# manpage_directory: The location of the Postfix on-line manual pages. +# +manpage_directory = /usr/share/man + +# sample_directory: The location of the Postfix sample configuration files. +# This parameter is obsolete as of Postfix 2.1. +# +sample_directory = /usr/share/doc/postfix + +# readme_directory: The location of the Postfix README files. +# +readme_directory = /usr/share/doc/postfix +inet_protocols = ipv4 + +append_dot_mydomain = no +biff = no +smtpd_helo_required = yes +smtpd_recipient_restrictions = permit_mynetworks, + permit_sasl_authenticated, + reject_unauth_destination, + reject_unauth_pipelining, + reject_non_fqdn_recipient +smtpd_sender_restrictions = permit_mynetworks, + reject_sender_login_mismatch, + permit_sasl_authenticated, + reject_unknown_helo_hostname, + reject_unknown_recipient_domain, + reject_unknown_sender_domain +smtpd_client_restrictions = permit_mynetworks, + permit_sasl_authenticated, + reject_unknown_client_hostname + +# Postfix 2.10 requires this option. Postfix < 2.10 ignores this. +# The option is intentionally left empty. +smtpd_relay_restrictions = + +# Maximum size of Message in bytes (50MB) +message_size_limit = 52428800 + +## SASL Auth Settings +smtpd_sasl_auth_enable = yes +smtpd_sasl_local_domain = $myhostname +broken_sasl_auth_clients = yes +## Dovecot Settings for deliver, SASL Auth and virtual transport +smtpd_sasl_type = dovecot +virtual_transport = dovecot +dovecot_destination_recipient_limit = 1 +smtpd_sasl_path = private/auth + +# Virtual delivery settings +virtual_mailbox_base = / +virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual_mailbox_maps.cf +virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual_mailbox_domains.cf +virtual_alias_maps = mysql:/etc/postfix/mysql-virtual_alias_maps.cf +smtpd_sender_login_maps = mysql:/etc/postfix/mysql-virtual_sender_permissions.cf +virtual_uid_maps = static: +virtual_gid_maps = static: + +# Local delivery settings +local_transport = local +alias_maps = $alias_database + +# Default Mailbox size, is set to 0 which means unlimited! +mailbox_size_limit = 0 +virtual_mailbox_limit = 0 + +### TLS settings +### +## TLS for outgoing mails from the server to another server +#smtp_tls_security_level = may +#smtp_tls_note_starttls_offer = yes +## TLS for incoming connections (clients or other mail servers) +#smtpd_tls_security_level = may +#smtpd_tls_cert_file = /etc/ssl/server/.pem +#smtpd_tls_key_file = $smtpd_tls_cert_file +#smtpd_tls_CAfile = /etc/ssl/certs/ca-certificates.crt +#smtpd_tls_loglevel = 1 +#smtpd_tls_received_header = yes +]]> + + + //service[@type='smtp']/general/files[@index=0] + + + + + //service[@type='smtp']/general/commands[@index=3] + + + + + + + + + + + + + to select which instance is used (an alternative +# to -c ). The instance name is also added to Dovecot processes +# in ps output. +#instance_name = dovecot + +# Greeting message for clients. +#login_greeting = Dovecot ready. + +# Space separated list of trusted network ranges. Connections from these +# IPs are allowed to override their IP addresses and ports (for logging and +# for authentication checks). disable_plaintext_auth is also ignored for +# these networks. Typically you'd specify your IMAP proxy servers here. +#login_trusted_networks = + +# Space separated list of login access check sockets (e.g. tcpwrap) +#login_access_sockets = + +# With proxy_maybe=yes if proxy destination matches any of these IPs, don't do +# proxying. This isn't necessary normally, but may be useful if the destination +# IP is e.g. a load balancer's IP. +#auth_proxy_self = + +# Show more verbose process titles (in ps). Currently shows user name and +# IP address. Useful for seeing who are actually using the IMAP processes +# (eg. shared mailboxes or if same uid is used for multiple accounts). +#verbose_proctitle = no + +# Should all processes be killed when Dovecot master process shuts down. +# Setting this to "no" means that Dovecot can be upgraded without +# forcing existing client connections to close (although that could also be +# a problem if the upgrade is e.g. because of a security fix). +#shutdown_clients = yes + +# If non-zero, run mail commands via this many connections to doveadm server, +# instead of running them directly in the same process. +#doveadm_worker_count = 0 +# UNIX socket or host:port used for connecting to doveadm server +#doveadm_socket_path = doveadm-server + +# Space separated list of environment variables that are preserved on Dovecot +# startup and passed down to all of its child processes. You can also give +# key=value pairs to always set specific settings. +#import_environment = TZ + +## +## Dictionary server settings +## + +# Dictionary can be used to store key=value lists. This is used by several +# plugins. The dictionary can be accessed either directly or though a +# dictionary server. The following dict block maps dictionary names to URIs +# when the server is used. These can then be referenced using URIs in format +# "proxy::". + +dict { + #quota = mysql:/etc/dovecot/dovecot-dict-sql.conf.ext + #expire = sqlite:/etc/dovecot/dovecot-dict-sql.conf.ext +} + +# Most of the actual configuration gets included below. The filenames are +# first sorted by their ASCII value and parsed in that order. The 00-prefixes +# in filenames are intended to make it easier to understand the ordering. +!include conf.d/*.conf + +# A config file can also tried to be included without giving an error if +# it's not found: +!include_try local.conf +]]> + + + + dbname= user= password= + +# Default password scheme. +# +# List of supported schemes is in +# http://wiki2.dovecot.org/Authentication/PasswordSchemes +# +default_pass_scheme = CRYPT + +# passdb query to retrieve the password. It can return fields: +# password - The user's password. This field must be returned. +# user - user@domain from the database. Needed with case-insensitive lookups. +# username and domain - An alternative way to represent the "user" field. +# +# The "user" field is often necessary with case-insensitive lookups to avoid +# e.g. "name" and "nAme" logins creating two different mail directories. If +# your user and domain names are in separate fields, you can return "username" +# and "domain" fields instead of "user". +# +# The query can also return other fields which have a special meaning, see +# http://wiki2.dovecot.org/PasswordDatabase/ExtraFields +# +# Commonly used available substitutions (see http://wiki2.dovecot.org/Variables +# for full list): +# %u = entire user@domain +# %n = user part of user@domain +# %d = domain part of user@domain +# +# Note that these can be used only as input to SQL query. If the query outputs +# any of these substitutions, they're not touched. Otherwise it would be +# difficult to have eg. usernames containing '%' characters. +# +# Example: +# password_query = SELECT userid AS user, pw AS password \ +# FROM users WHERE userid = '%u' AND active = 'Y' +# +#password_query = \ +# SELECT username, domain, password \ +# FROM users WHERE username = '%n' AND domain = '%d' + +# userdb query to retrieve the user information. It can return fields: +# uid - System UID (overrides mail_uid setting) +# gid - System GID (overrides mail_gid setting) +# home - Home directory +# mail - Mail location (overrides mail_location setting) +# +# None of these are strictly required. If you use a single UID and GID, and +# home or mail directory fits to a template string, you could use userdb static +# instead. For a list of all fields that can be returned, see +# http://wiki2.dovecot.org/UserDatabase/ExtraFields +# +# Examples: +# user_query = SELECT home, uid, gid FROM users WHERE userid = '%u' +# user_query = SELECT dir AS home, user AS uid, group AS gid FROM users where userid = '%u' +# user_query = SELECT home, 501 AS uid, 501 AS gid FROM users WHERE userid = '%u' +# +#user_query = \ +# SELECT home, uid, gid \ +# FROM users WHERE username = '%n' AND domain = '%d' +user_query = SELECT CONCAT(homedir, maildir) AS home, CONCAT('maildir:', homedir, maildir) AS mail, uid, gid, CONCAT('*:storage=', quota, 'M') as quota_rule FROM mail_users WHERE (username = '%u' OR email = '%u') + +# If you wish to avoid two SQL lookups (passdb + userdb), you can use +# userdb prefetch instead of userdb sql in dovecot.conf. In that case you'll +# also have to return userdb fields in password_query prefixed with "userdb_" +# string. For example: +#password_query = \ +# SELECT userid AS user, password, \ +# home AS userdb_home, uid AS userdb_uid, gid AS userdb_gid \ +# FROM users WHERE userid = '%u' +password_query = SELECT username AS user, password_enc AS password, CONCAT(homedir, maildir) AS userdb_home, uid AS userdb_uid, gid AS userdb_gid, CONCAT('maildir:', homedir, maildir) AS userdb_mail, CONCAT('*:storage=', quota, 'M') as userdb_quota_rule FROM mail_users WHERE (username = '%u' OR email = '%u') AND ((imap = 1 AND '%Ls' = 'imap') OR (pop3 = 1 AND '%Ls' = 'pop3') OR '%Ls' = 'smtp' OR '%Ls' = 'sieve') + +# Query to get a list of all usernames. +#iterate_query = SELECT username AS user FROM users +]]> + + + + to characters. For example "#@/@" means +# that '#' and '/' characters are translated to '@'. +#auth_username_translation = + +# Username formatting before it's looked up from databases. You can use +# the standard variables here, eg. %Lu would lowercase the username, %n would +# drop away the domain if it was given, or "%n-AT-%d" would change the '@' into +# "-AT-". This translation is done after auth_username_translation changes. +#auth_username_format = %Lu + +# If you want to allow master users to log in by specifying the master +# username within the normal username string (ie. not using SASL mechanism's +# support for it), you can specify the separator character here. The format +# is then . UW-IMAP uses "*" as the +# separator, so that could be a good choice. +#auth_master_user_separator = + +# Username to use for users logging in with ANONYMOUS SASL mechanism +#auth_anonymous_username = anonymous + +# Maximum number of dovecot-auth worker processes. They're used to execute +# blocking passdb and userdb queries (eg. MySQL and PAM). They're +# automatically created and destroyed as needed. +#auth_worker_max_count = 30 + +# Host name to use in GSSAPI principal names. The default is to use the +# name returned by gethostname(). Use "$ALL" (with quotes) to allow all keytab +# entries. +#auth_gssapi_hostname = + +# Kerberos keytab to use for the GSSAPI mechanism. Will use the system +# default (usually /etc/krb5.keytab) if not specified. You may need to change +# the auth service to run as root to be able to read this file. +#auth_krb5_keytab = + +# Do NTLM and GSS-SPNEGO authentication using Samba's winbind daemon and +# ntlm_auth helper. +#auth_use_winbind = no + +# Path for Samba's ntlm_auth helper binary. +#auth_winbind_helper_path = /usr/bin/ntlm_auth + +# Time to delay before replying to failed authentications. +#auth_failure_delay = 2 secs + +# Require a valid SSL client certificate or the authentication fails. +#auth_ssl_require_client_cert = no + +# Take the username from client's SSL certificate, using +# X509_NAME_get_text_by_NID() which returns the subject's DN's +# CommonName. +#auth_ssl_username_from_cert = no + +# Space separated list of wanted authentication mechanisms: +# plain login digest-md5 cram-md5 ntlm rpa apop anonymous gssapi otp skey +# gss-spnego +# NOTE: See also disable_plaintext_auth setting. +auth_mechanisms = plain login + +## +## Password and user databases +## + +# +# Password database is used to verify user's password (and nothing more). +# You can have multiple passdbs and userdbs. This is useful if you want to +# allow both system users (/etc/passwd) and virtual users to login without +# duplicating the system users into virtual database. +# +# +# +# User database specifies where mails are located and what user/group IDs +# own them. For single-UID configuration use "static" userdb. +# +# + +#!include auth-deny.conf.ext +#!include auth-master.conf.ext + +#!include auth-system.conf.ext +!include auth-sql.conf.ext +#!include auth-ldap.conf.ext +#!include auth-passwdfile.conf.ext +#!include auth-checkpassword.conf.ext +#!include auth-vpopmail.conf.ext +#!include auth-static.conf.ext +]]> + + + + +# +mail_location = mbox:~/mail:INBOX=/var/mail/%u + +# If you need to set multiple mailbox locations or want to change default +# namespace settings, you can do it by defining namespace sections. +# +# You can have private, shared and public namespaces. Private namespaces +# are for user's personal mails. Shared namespaces are for accessing other +# users' mailboxes that have been shared. Public namespaces are for shared +# mailboxes that are managed by sysadmin. If you create any shared or public +# namespaces you'll typically want to enable ACL plugin also, otherwise all +# users can access all the shared mailboxes, assuming they have permissions +# on filesystem level to do so. +namespace inbox { + # Namespace type: private, shared or public + #type = private + + # Hierarchy separator to use. You should use the same separator for all + # namespaces or some clients get confused. '/' is usually a good one. + # The default however depends on the underlying mail storage format. + #separator = + + # Prefix required to access this namespace. This needs to be different for + # all namespaces. For example "Public/". + #prefix = + + # Physical location of the mailbox. This is in same format as + # mail_location, which is also the default for it. + #location = + + # There can be only one INBOX, and this setting defines which namespace + # has it. + inbox = yes + + # If namespace is hidden, it's not advertised to clients via NAMESPACE + # extension. You'll most likely also want to set list=no. This is mostly + # useful when converting from another server with different namespaces which + # you want to deprecate but still keep working. For example you can create + # hidden namespaces with prefixes "~/mail/", "~%u/mail/" and "mail/". + #hidden = no + + # Show the mailboxes under this namespace with LIST command. This makes the + # namespace visible for clients that don't support NAMESPACE extension. + # "children" value lists child mailboxes, but hides the namespace prefix. + #list = yes + + # Namespace handles its own subscriptions. If set to "no", the parent + # namespace handles them (empty prefix should always have this as "yes") + #subscriptions = yes +} + +# Example shared namespace configuration +#namespace { + #type = shared + #separator = / + + # Mailboxes are visible under "shared/user@domain/" + # %%n, %%d and %%u are expanded to the destination user. + #prefix = shared/%%u/ + + # Mail location for other users' mailboxes. Note that %variables and ~/ + # expands to the logged in user's data. %%n, %%d, %%u and %%h expand to the + # destination user's data. + #location = maildir:%%h/Maildir:INDEX=~/Maildir/shared/%%u + + # Use the default namespace for saving subscriptions. + #subscriptions = no + + # List the shared/ namespace only if there are visible shared mailboxes. + #list = children +#} +# Should shared INBOX be visible as "shared/user" or "shared/user/INBOX"? +#mail_shared_explicit_inbox = no + +# System user and group used to access mails. If you use multiple, userdb +# can override these by returning uid or gid fields. You can use either numbers +# or names. +#mail_uid = +#mail_gid = + +# Group to enable temporarily for privileged operations. Currently this is +# used only with INBOX when either its initial creation or dotlocking fails. +# Typically this is set to "mail" to give access to /var/mail. +#mail_privileged_group = + +# Grant access to these supplementary groups for mail processes. Typically +# these are used to set up access to shared mailboxes. Note that it may be +# dangerous to set these if users can create symlinks (e.g. if "mail" group is +# set here, ln -s /var/mail ~/mail/var could allow a user to delete others' +# mailboxes, or ln -s /secret/shared/box ~/mail/mybox would allow reading it). +mail_access_groups = vmail + +# Allow full filesystem access to clients. There's no access checks other than +# what the operating system does for the active UID/GID. It works with both +# maildir and mboxes, allowing you to prefix mailboxes names with eg. /path/ +# or ~user/. +#mail_full_filesystem_access = no + +# Dictionary for key=value mailbox attributes. Currently used by URLAUTH, but +# soon intended to be used by METADATA as well. +#mail_attribute_dict = + +## +## Mail processes +## + +# Don't use mmap() at all. This is required if you store indexes to shared +# filesystems (NFS or clustered filesystem). +#mmap_disable = no + +# Rely on O_EXCL to work when creating dotlock files. NFS supports O_EXCL +# since version 3, so this should be safe to use nowadays by default. +#dotlock_use_excl = yes + +# When to use fsync() or fdatasync() calls: +# optimized (default): Whenever necessary to avoid losing important data +# always: Useful with e.g. NFS when write()s are delayed +# never: Never use it (best performance, but crashes can lose data) +#mail_fsync = optimized + +# Locking method for index files. Alternatives are fcntl, flock and dotlock. +# Dotlocking uses some tricks which may create more disk I/O than other locking +# methods. NFS users: flock doesn't work, remember to change mmap_disable. +#lock_method = fcntl + +# Directory in which LDA/LMTP temporarily stores incoming mails >128 kB. +#mail_temp_dir = /tmp + +# Valid UID range for users, defaults to 500 and above. This is mostly +# to make sure that users can't log in as daemons or other system users. +# Note that denying root logins is hardcoded to dovecot binary and can't +# be done even if first_valid_uid is set to 0. +#first_valid_uid = 500 +#last_valid_uid = 0 + +# Valid GID range for users, defaults to non-root/wheel. Users having +# non-valid GID as primary group ID aren't allowed to log in. If user +# belongs to supplementary groups with non-valid GIDs, those groups are +# not set. +#first_valid_gid = 1 +#last_valid_gid = 0 + +# Maximum allowed length for mail keyword name. It's only forced when trying +# to create new keywords. +#mail_max_keyword_length = 50 + +# ':' separated list of directories under which chrooting is allowed for mail +# processes (ie. /var/mail will allow chrooting to /var/mail/foo/bar too). +# This setting doesn't affect login_chroot, mail_chroot or auth chroot +# settings. If this setting is empty, "/./" in home dirs are ignored. +# WARNING: Never add directories here which local users can modify, that +# may lead to root exploit. Usually this should be done only if you don't +# allow shell access for users. +#valid_chroot_dirs = + +# Default chroot directory for mail processes. This can be overridden for +# specific users in user database by giving /./ in user's home directory +# (eg. /home/./user chroots into /home). Note that usually there is no real +# need to do chrooting, Dovecot doesn't allow users to access files outside +# their mail directory anyway. If your home directories are prefixed with +# the chroot directory, append "/." to mail_chroot. +#mail_chroot = + +# UNIX socket path to master authentication server to find users. +# This is used by imap (for shared users) and lda. +#auth_socket_path = /var/run/dovecot/auth-userdb + +# Directory where to look up mail plugins. +#mail_plugin_dir = /usr/lib/dovecot/modules + +# Space separated list of plugins to load for all services. Plugins specific to +# IMAP, LDA, etc. are added to this list in their own .conf files. +#mail_plugins = + +## +## Mailbox handling optimizations +## + +# Mailbox list indexes can be used to optimize IMAP STATUS commands. They are +# also required for IMAP NOTIFY extension to be enabled. +#mailbox_list_index = no + +# The minimum number of mails in a mailbox before updates are done to cache +# file. This allows optimizing Dovecot's behavior to do less disk writes at +# the cost of more disk reads. +#mail_cache_min_mail_count = 0 + +# When IDLE command is running, mailbox is checked once in a while to see if +# there are any new mails or other changes. This setting defines the minimum +# time to wait between those checks. Dovecot can also use dnotify, inotify and +# kqueue to find out immediately when changes occur. +#mailbox_idle_check_interval = 30 secs + +# Save mails with CR+LF instead of plain LF. This makes sending those mails +# take less CPU, especially with sendfile() syscall with Linux and FreeBSD. +# But it also creates a bit more disk I/O which may just make it slower. +# Also note that if other software reads the mboxes/maildirs, they may handle +# the extra CRs wrong and cause problems. +#mail_save_crlf = no + +# Max number of mails to keep open and prefetch to memory. This only works with +# some mailbox formats and/or operating systems. +#mail_prefetch_count = 0 + +# How often to scan for stale temporary files and delete them (0 = never). +# These should exist only after Dovecot dies in the middle of saving mails. +#mail_temp_scan_interval = 1w + +## +## Maildir-specific settings +## + +# By default LIST command returns all entries in maildir beginning with a dot. +# Enabling this option makes Dovecot return only entries which are directories. +# This is done by stat()ing each entry, so it causes more disk I/O. +# (For systems setting struct dirent->d_type, this check is free and it's +# done always regardless of this setting) +#maildir_stat_dirs = no + +# When copying a message, do it with hard links whenever possible. This makes +# the performance much better, and it's unlikely to have any side effects. +#maildir_copy_with_hardlinks = yes + +# Assume Dovecot is the only MUA accessing Maildir: Scan cur/ directory only +# when its mtime changes unexpectedly or when we can't find the mail otherwise. +#maildir_very_dirty_syncs = no + +# If enabled, Dovecot doesn't use the S= in the Maildir filenames for +# getting the mail's physical size, except when recalculating Maildir++ quota. +# This can be useful in systems where a lot of the Maildir filenames have a +# broken size. The performance hit for enabling this is very small. +#maildir_broken_filename_sizes = no + +# Always move mails from new/ directory to cur/, even when the \Recent flags +# aren't being reset. +#maildir_empty_new = no + +## +## mbox-specific settings +## + +# Which locking methods to use for locking mbox. There are four available: +# dotlock: Create .lock file. This is the oldest and most NFS-safe +# solution. If you want to use /var/mail/ like directory, the users +# will need write access to that directory. +# dotlock_try: Same as dotlock, but if it fails because of permissions or +# because there isn't enough disk space, just skip it. +# fcntl : Use this if possible. Works with NFS too if lockd is used. +# flock : May not exist in all systems. Doesn't work with NFS. +# lockf : May not exist in all systems. Doesn't work with NFS. +# +# You can use multiple locking methods; if you do the order they're declared +# in is important to avoid deadlocks if other MTAs/MUAs are using multiple +# locking methods as well. Some operating systems don't allow using some of +# them simultaneously. +# +# The Debian value for mbox_write_locks differs from upstream Dovecot. It is +# changed to be compliant with Debian Policy (section 11.6) for NFS safety. +# Dovecot: mbox_write_locks = dotlock fcntl +# Debian: mbox_write_locks = fcntl dotlock +# +#mbox_read_locks = fcntl +#mbox_write_locks = fcntl dotlock + +# Maximum time to wait for lock (all of them) before aborting. +#mbox_lock_timeout = 5 mins + +# If dotlock exists but the mailbox isn't modified in any way, override the +# lock file after this much time. +#mbox_dotlock_change_timeout = 2 mins + +# When mbox changes unexpectedly we have to fully read it to find out what +# changed. If the mbox is large this can take a long time. Since the change +# is usually just a newly appended mail, it'd be faster to simply read the +# new mails. If this setting is enabled, Dovecot does this but still safely +# fallbacks to re-reading the whole mbox file whenever something in mbox isn't +# how it's expected to be. The only real downside to this setting is that if +# some other MUA changes message flags, Dovecot doesn't notice it immediately. +# Note that a full sync is done with SELECT, EXAMINE, EXPUNGE and CHECK +# commands. +#mbox_dirty_syncs = yes + +# Like mbox_dirty_syncs, but don't do full syncs even with SELECT, EXAMINE, +# EXPUNGE or CHECK commands. If this is set, mbox_dirty_syncs is ignored. +#mbox_very_dirty_syncs = no + +# Delay writing mbox headers until doing a full write sync (EXPUNGE and CHECK +# commands and when closing the mailbox). This is especially useful for POP3 +# where clients often delete all mails. The downside is that our changes +# aren't immediately visible to other MUAs. +#mbox_lazy_writes = yes + +# If mbox size is smaller than this (e.g. 100k), don't write index files. +# If an index file already exists it's still read, just not updated. +#mbox_min_index_size = 0 + +# Mail header selection algorithm to use for MD5 POP3 UIDLs when +# pop3_uidl_format=%m. For backwards compatibility we use apop3d inspired +# algorithm, but it fails if the first Received: header isn't unique in all +# mails. An alternative algorithm is "all" that selects all headers. +#mbox_md5 = apop3d + +## +## mdbox-specific settings +## + +# Maximum dbox file size until it's rotated. +#mdbox_rotate_size = 2M + +# Maximum dbox file age until it's rotated. Typically in days. Day begins +# from midnight, so 1d = today, 2d = yesterday, etc. 0 = check disabled. +#mdbox_rotate_interval = 0 + +# When creating new mdbox files, immediately preallocate their size to +# mdbox_rotate_size. This setting currently works only in Linux with some +# filesystems (ext4, xfs). +#mdbox_preallocate_space = no + +## +## Mail attachments +## + +# sdbox and mdbox support saving mail attachments to external files, which +# also allows single instance storage for them. Other backends don't support +# this for now. + +# Directory root where to store mail attachments. Disabled, if empty. +#mail_attachment_dir = + +# Attachments smaller than this aren't saved externally. It's also possible to +# write a plugin to disable saving specific attachments externally. +#mail_attachment_min_size = 128k + +# Filesystem backend to use for saving attachments: +# posix : No SiS done by Dovecot (but this might help FS's own deduplication) +# sis posix : SiS with immediate byte-by-byte comparison during saving +# sis-queue posix : SiS with delayed comparison and deduplication +#mail_attachment_fs = sis posix + +# Hash format to use in attachment filenames. You can add any text and +# variables: %{md4}, %{md5}, %{sha1}, %{sha256}, %{sha512}, %{size}. +# Variables can be truncated, e.g. %{sha256:80} returns only first 80 bits +#mail_attachment_hash = %{sha1} +]]> + + + + + #service_count = 1 + + # Number of processes to always keep waiting for more connections. + #process_min_avail = 0 + + # If you set service_count=0, you probably need to grow this. + #vsz_limit = $default_vsz_limit +} + +service pop3-login { + inet_listener pop3 { + #port = 110 + } + inet_listener pop3s { + #port = 995 + #ssl = yes + } +} + +service lmtp { + unix_listener lmtp { + #mode = 0666 + } + + # Create inet listener only if you can't use the above UNIX socket + #inet_listener lmtp { + # Avoid making LMTP visible for the entire internet + #address = + #port = + #} +} + +service imap { + # Most of the memory goes to mmap()ing files. You may need to increase this + # limit if you have huge mailboxes. + #vsz_limit = $default_vsz_limit + + # Max. number of IMAP processes (connections) + #process_limit = 1024 +} + +service pop3 { + # Max. number of POP3 processes (connections) + #process_limit = 1024 +} + +service auth { + # auth_socket_path points to this userdb socket by default. It's typically + # used by dovecot-lda, doveadm, possibly imap process, etc. Users that have + # full permissions to this socket are able to get a list of all usernames and + # get the results of everyone's userdb lookups. + # + # The default 0666 mode allows anyone to connect to the socket, but the + # userdb lookups will succeed only if the userdb returns an "uid" field that + # matches the caller process's UID. Also if caller's uid or gid matches the + # socket's uid or gid the lookup succeeds. Anything else causes a failure. + # + # To give the caller full permissions to lookup all users, set the mode to + # something else than 0666 and Dovecot lets the kernel enforce the + # permissions (e.g. 0777 allows everyone full permissions). + unix_listener auth-userdb { + #mode = 0666 + #user = + #group = + } + + # Postfix smtp-auth + unix_listener /var/spool/postfix/private/auth { + mode = 0660 + user = postfix + group = postfix + } + + # Exim4 smtp-auth + unix_listener auth-client { + mode = 0660 + user = mail + # group = Debian-exim + } + + # Auth process is run as this user. + #user = $default_internal_user +} + +service auth-worker { + # Auth worker process is run as root by default, so that it can access + # /etc/shadow. If this isn't necessary, the user should be changed to + # $default_internal_user. + #user = root +} + +service dict { + # If dict proxy is used, mail processes should have access to its socket. + # For example: mode=0660, group=vmail and global mail_access_groups=vmail + unix_listener dict { + #mode = 0600 + #user = + #group = + } +} +]]> + + + + +ssl = no + +# PEM encoded X.509 SSL/TLS certificate and private key. They're opened before +# dropping root privileges, so keep the key file unreadable by anyone but +# root. Included doc/mkcert.sh can be used to easily generate self-signed +# certificate, just make sure to update the domains in dovecot-openssl.cnf +#ssl_cert = + + + + . %d expands to recipient domain. +postmaster_address = postmaster@ + +# Hostname to use in various parts of sent mails (e.g. in Message-Id) and +# in LMTP replies. Default is the system's real hostname@domain. +#hostname = + +# If user is over quota, return with temporary failure instead of +# bouncing the mail. +#quota_full_tempfail = no + +# Binary to use for sending mails. +#sendmail_path = /usr/sbin/sendmail + +# If non-empty, send mails via this SMTP host[:port] instead of sendmail. +#submission_host = + +# Subject: header to use for rejection mails. You can use the same variables +# as for rejection_reason below. +#rejection_subject = Rejected: %s + +# Human readable error message for rejection mails. You can use variables: +# %n = CRLF, %r = reason, %s = original subject, %t = recipient +#rejection_reason = Your message to <%t> was automatically rejected:%n%r + +# Delimiter character between local-part and detail in email address. +#recipient_delimiter = + + +# Header where the original recipient address (SMTP's RCPT TO: address) is taken +# from if not available elsewhere. With dovecot-lda -a parameter overrides this. +# A commonly used header for this is X-Original-To. +#lda_original_recipient_header = + +# Should saving a mail to a nonexistent mailbox automatically create it? +#lda_mailbox_autocreate = no + +# Should automatically created mailboxes be also automatically subscribed? +#lda_mailbox_autosubscribe = no + +protocol lda { + # Space separated list of plugins to load (default is global mail_plugins). + mail_plugins = $mail_plugins quota sieve +} +]]> + + + + + + + + + #service_count = 1 + + # Number of processes to always keep waiting for more connections. + #process_min_avail = 0 + + # If you set service_count=0, you probably need to grow this. + #vsz_limit = 64M +#} + +#service managesieve { + # Max. number of ManageSieve processes (connections) + #process_limit = 1024 +#} + +# Service configuration + +protocol sieve { + # Maximum ManageSieve command line length in bytes. ManageSieve usually does + # not involve overly long command lines, so this setting will not normally + # need adjustment + #managesieve_max_line_length = 65536 + + # Maximum number of ManageSieve connections allowed for a user from each IP + # address. + # NOTE: The username is compared case-sensitively. + #mail_max_userip_connections = 10 + + # Space separated list of plugins to load (none known to be useful so far). + # Do NOT try to load IMAP plugins here. + #mail_plugins = + + # MANAGESIEVE logout format string: + # %i - total number of bytes read from client + # %o - total number of bytes sent to client + #managesieve_logout_format = bytes=%i/%o + + # To fool ManageSieve clients that are focused on CMU's timesieved you can + # specify the IMPLEMENTATION capability that Dovecot reports to clients. + # For example: 'Cyrus timsieved v2.2.13' + #managesieve_implementation_string = Dovecot Pigeonhole + + # Explicitly specify the SIEVE and NOTIFY capability reported by the server + # before login. If left unassigned these will be reported dynamically + # according to what the Sieve interpreter supports by default (after login + # this may differ depending on the user). + #managesieve_sieve_capability = + #managesieve_notify_capability = + + # The maximum number of compile errors that are returned to the client upon + # script upload or script verification. + #managesieve_max_compile_errors = 5 + + # Refer to 90-sieve.conf for script quota configuration and configuration of + # Sieve execution limits. +} +]]> + + + + = 2.1.4) : %v.%u +# Dovecot v0.99.x : %v.%u +# tpop3d : %Mf +# +# Note that Outlook 2003 seems to have problems with %v.%u format which was +# Dovecot's default, so if you're building a new server it would be a good +# idea to change this. %08Xu%08Xv should be pretty fail-safe. +# +#pop3_uidl_format = %08Xu%08Xv + +# Permanently save UIDLs sent to POP3 clients, so pop3_uidl_format changes +# won't change those UIDLs. Currently this works only with Maildir. +#pop3_save_uidl = no + +# What to do about duplicate UIDLs if they exist? +# allow: Show duplicates to clients. +# rename: Append a temporary -2, -3, etc. counter after the UIDL. +#pop3_uidl_duplicates = allow + +# This option changes POP3 behavior so that it's not possible to actually +# delete mails via POP3, only hide them from future POP3 sessions. The mails +# will still be counted towards user's quota until actually deleted via IMAP. +# Use e.g. "$POP3Deleted" as the value (it will be visible as IMAP keyword). +# Make sure you can legally archive mails before enabling this setting. +#pop3_deleted_flag = + +# POP3 logout format string: +# %i - total number of bytes read from client +# %o - total number of bytes sent to client +# %t - number of TOP commands +# %p - number of bytes sent to client as a result of TOP command +# %r - number of RETR commands +# %b - number of bytes sent to client as a result of RETR command +# %d - number of deleted messages +# %m - number of messages (before deletion) +# %s - mailbox size in bytes (before deletion) +# %u - old/new UIDL hash. may help finding out if UIDLs changed unexpectedly +#pop3_logout_format = top=%t/%p, retr=%r/%b, del=%d/%m, size=%s + +# Workarounds for various client bugs: +# outlook-no-nuls: +# Outlook and Outlook Express hang if mails contain NUL characters. +# This setting replaces them with 0x80 character. +# oe-ns-eoh: +# Outlook Express and Netscape Mail breaks if end of headers-line is +# missing. This option simply sends it if it's missing. +# The list is space-separated. +#pop3_client_workarounds = + +protocol pop3 { + # Space separated list of plugins to load (default is global mail_plugins). + #mail_plugins = $mail_plugins + + # Maximum number of POP3 connections allowed for a user from each IP address. + # NOTE: The username is compared case-sensitively. + #mail_max_userip_connections = 10 +} +]]> + + + + See sieve_before fore executing scripts before the user's personal + # script. + #sieve_default = /var/lib/dovecot/sieve/default.sieve + + # Directory for :personal include scripts for the include extension. This + # is also where the ManageSieve service stores the user's scripts. + sieve_dir = ~/sieve + + # Directory for :global include scripts for the include extension. + #sieve_global_dir = + + # Path to a script file or a directory containing script files that need to be + # executed before the user's script. If the path points to a directory, all + # the Sieve scripts contained therein (with the proper .sieve extension) are + # executed. The order of execution within a directory is determined by the + # file names, using a normal 8bit per-character comparison. Multiple script + # file or directory paths can be specified by appending an increasing number. + #sieve_before = + #sieve_before2 = + #sieve_before3 = (etc...) + + # Identical to sieve_before, only the specified scripts are executed after the + # user's script (only when keep is still in effect!). Multiple script file or + # directory paths can be specified by appending an increasing number. + #sieve_after = + #sieve_after2 = + #sieve_after2 = (etc...) + + # Which Sieve language extensions are available to users. By default, all + # supported extensions are available, except for deprecated extensions or + # those that are still under development. Some system administrators may want + # to disable certain Sieve extensions or enable those that are not available + # by default. This setting can use '+' and '-' to specify differences relative + # to the default. For example `sieve_extensions = +imapflags' will enable the + # deprecated imapflags extension in addition to all extensions were already + # enabled by default. + #sieve_extensions = +notify +imapflags + + # Which Sieve language extensions are ONLY available in global scripts. This + # can be used to restrict the use of certain Sieve extensions to administrator + # control, for instance when these extensions can cause security concerns. + # This setting has higher precedence than the `sieve_extensions' setting + # (above), meaning that the extensions enabled with this setting are never + # available to the user's personal script no matter what is specified for the + # `sieve_extensions' setting. The syntax of this setting is similar to the + # `sieve_extensions' setting, with the difference that extensions are + # enabled or disabled for exclusive use in global scripts. Currently, no + # extensions are marked as such by default. + #sieve_global_extensions = + + # The Pigeonhole Sieve interpreter can have plugins of its own. Using this + # setting, the used plugins can be specified. Check the Dovecot wiki + # (wiki2.dovecot.org) or the pigeonhole website + # (http://pigeonhole.dovecot.org) for available plugins. + # The sieve_extprograms plugin is included in this release. + #sieve_plugins = + + # The separator that is expected between the :user and :detail + # address parts introduced by the subaddress extension. This may + # also be a sequence of characters (e.g. '--'). The current + # implementation looks for the separator from the left of the + # localpart and uses the first one encountered. The :user part is + # left of the separator and the :detail part is right. This setting + # is also used by Dovecot's LMTP service. + #recipient_delimiter = + + + # The maximum size of a Sieve script. The compiler will refuse to compile any + # script larger than this limit. If set to 0, no limit on the script size is + # enforced. + #sieve_max_script_size = 1M + + # The maximum number of actions that can be performed during a single script + # execution. If set to 0, no limit on the total number of actions is enforced. + #sieve_max_actions = 32 + + # The maximum number of redirect actions that can be performed during a single + # script execution. If set to 0, no redirect actions are allowed. + #sieve_max_redirects = 4 + + # The maximum number of personal Sieve scripts a single user can have. If set + # to 0, no limit on the number of scripts is enforced. + # (Currently only relevant for ManageSieve) + #sieve_quota_max_scripts = 0 + + # The maximum amount of disk storage a single user's scripts may occupy. If + # set to 0, no limit on the used amount of disk storage is enforced. + # (Currently only relevant for ManageSieve) + #sieve_quota_max_storage = 0 +} +]]> + + + + + + + + + + //service[@type='mail']/general/installs[@index=1] + + //service[@type='mail']/general/files[@index=1] + + //service[@type='mail']/general/commands[@index=1] + + + + + + + + + + " +[ -f /etc/ssl/certs/proftpd_ec.crt ] || openssl req -new -x509 -nodes -newkey ec:<(openssl ecparam -name secp521r1) -keyout /etc/ssl/private/proftpd_ec.key -out /etc/ssl/certs/proftpd_ec.crt -days 3650 -subj "/C=US/ST=Some-State/O=Internet Widgits Pty Ltd/CN=" +chmod 0600 /etc/ssl/private/proftpd.key /etc/ssl/private/proftpd_ec.key +]]> + + + + + + + + FTP Server" +ServerType standalone +DeferWelcome off + +MultilineRFC2228 on +DefaultServer on +ShowSymlinks on + +TimeoutNoTransfer 600 +TimeoutStalled 600 +TimeoutIdle 1200 + +DisplayLogin welcome.msg +DisplayChdir .message true +ListOptions "-l" + +DenyFilter \*.*/ + +# Use this to jail all users in their homes +# DefaultRoot ~ + +# Users require a valid shell listed in /etc/shells to login. +# Use this directive to release that constrain. +# RequireValidShell off + +# Port 21 is the standard FTP port. +Port 21 + +# In some cases you have to specify passive ports range to by-pass +# firewall limitations. Ephemeral ports can be used for that, but +# feel free to use a more narrow range. +# PassivePorts 49152 65534 + +# If your host was NATted, this option is useful in order to +# allow passive tranfers to work. You have to use your public +# address and opening the passive ports used on your firewall as well. +# MasqueradeAddress 1.2.3.4 + +# This is useful for masquerading address with dynamic IPs: +# refresh any configured MasqueradeAddress directives every 8 hours + +# DynMasqRefresh 28800 + + +# To prevent DoS attacks, set the maximum number of child processes +# to 30. If you need to allow more than 30 concurrent connections +# at once, simply increase this value. Note that this ONLY works +# in standalone mode, in inetd mode you should use an inetd server +# that allows you to limit maximum number of processes per service +# (such as xinetd) +MaxInstances 30 + +# Set the user and group that the server normally runs at. +User proftpd +Group nogroup + +# Umask 022 is a good standard umask to prevent new files and dirs +# (second parm) from being group and world writable. +Umask 022 022 +# Normally, we want files to be overwriteable. +AllowOverwrite on + +# Uncomment this if you are using NIS or LDAP via NSS to retrieve passwords: +# PersistentPasswd off + +# This is required to use both PAM-based authentication and local passwords +# AuthOrder mod_auth_pam.c* mod_auth_unix.c + +# Be warned: use of this directive impacts CPU average load! +# Uncomment this if you like to see progress and transfer rate with ftpwho +# in downloads. That is not needed for uploads rates. +# +# UseSendFile off + +TransferLog /var/log/proftpd/xferlog +SystemLog /var/log/proftpd/proftpd.log + +# Logging onto /var/log/lastlog is enabled but set to off by default +#UseLastlog on + +# In order to keep log file dates consistent after chroot, use timezone info +# from /etc/localtime. If this is not set, and proftpd is configured to +# chroot (e.g. DefaultRoot or ), it will use the non-daylight +# savings timezone regardless of whether DST is in effect. +#SetEnv TZ :/etc/localtime + + +QuotaEngine on + + + +Ratios off + + + +# Delay engine reduces impact of the so-called Timing Attack described in +# http://www.securityfocus.com/bid/11430/discuss +# It is on by default. + +DelayEngine on + + + +ControlsEngine off +ControlsMaxClients 2 +ControlsLog /var/log/proftpd/controls.log +ControlsInterval 5 +ControlsSocket /var/run/proftpd/proftpd.sock + + + +AdminControlsEngine off + + +# +# Alternative authentication frameworks +# +#Include /etc/proftpd/ldap.conf +Include /etc/proftpd/sql.conf + +# +# This is used for FTPS connections +# +Include /etc/proftpd/tls.conf + +# +# Useful to keep VirtualHost/VirtualRoot directives separated +# +#Include /etc/proftpd/virtuals.conf + +# A basic anonymous configuration, no upload directories. + +# +# User ftp +# Group nogroup +# # We want clients to be able to login with "anonymous" as well as "ftp" +# UserAlias anonymous ftp +# # Cosmetic changes, all files belongs to ftp user +# DirFakeUser on ftp +# DirFakeGroup on ftp +# +# RequireValidShell off +# +# # Limit the maximum number of anonymous logins +# MaxClients 10 +# +# # We want 'welcome.msg' displayed at login, and '.message' displayed +# # in each newly chdired directory. +# DisplayLogin welcome.msg +# DisplayChdir .message +# +# # Limit WRITE everywhere in the anonymous chroot +# +# +# DenyAll +# +# +# +# # Uncomment this if you're brave. +# # +# # # Umask 022 is a good standard umask to prevent new files and dirs +# # # (second parm) from being group and world writable. +# # Umask 022 022 +# # +# # DenyAll +# # +# # +# # AllowAll +# # +# # +# +# + +# Include other custom configuration files +Include /etc/proftpd/conf.d/ +]]> + + + + + + + + + +DefaultRoot ~ +RequireValidShell off +AuthOrder mod_sql.c + +# +# Choose a SQL backend among MySQL or PostgreSQL. +# Both modules are loaded in default configuration, so you have to specify the backend +# or comment out the unused module in /etc/proftpd/modules.conf. +# Use 'mysql' or 'postgres' as possible values. +# +SQLBackend mysql +# +SQLEngine on +SQLAuthenticate on +# +# Use both a crypted or plaintext password +SQLAuthTypes Crypt + +SQLAuthenticate users* groups* + +# +# Connection +SQLConnectInfo @ +# +# Describes both users/groups tables +# +SQLUserInfo ftp_users username password uid gid homedir shell +SQLGroupInfo ftp_groups groupname gid members +# +SQLUserWhereClause "login_enabled = 'y'" + +SQLLog PASS login +SQLNamedQuery login UPDATE "last_login=now(), login_count=login_count+1 WHERE username='%u'" ftp_users + +SQLLog RETR download +SQLNamedQuery download UPDATE "down_count=down_count+1, down_bytes=down_bytes+%b WHERE username='%u'" ftp_users + +SQLLog STOR upload +SQLNamedQuery upload UPDATE "up_count=up_count+1, up_bytes=up_bytes+%b WHERE username='%u'" ftp_users + +QuotaEngine on +QuotaShowQuotas on +QuotaDisplayUnits Mb +QuotaLock /var/lock/ftpd.quotatab.lock +QuotaLimitTable sql:/get-quota-limit +QuotaTallyTable sql:/get-quota-tally/update-quota-tally/insert-quota-tally +SQLNamedQuery get-quota-limit SELECT "ftp_users.username AS name, ftp_quotalimits.quota_type, ftp_quotalimits.per_session, ftp_quotalimits.limit_type, panel_customers.diskspace*1024 AS bytes_in_avail, ftp_quotalimits.bytes_out_avail, ftp_quotalimits.bytes_xfer_avail, ftp_quotalimits.files_in_avail, ftp_quotalimits.files_out_avail, ftp_quotalimits.files_xfer_avail FROM ftp_users, ftp_quotalimits, panel_customers WHERE ftp_users.username = '%{0}' AND panel_customers.loginname = SUBSTRING_INDEX('%{0}', 'ftp', 1) AND quota_type ='%{1}'" +SQLNamedQuery get-quota-tally SELECT "name, quota_type, bytes_in_used,bytes_out_used, bytes_xfer_used, files_in_used, files_out_used,files_xfer_used FROM ftp_quotatallies WHERE name = '%{0}' AND quota_type = '%{1}'" +SQLNamedQuery update-quota-tally UPDATE "bytes_in_used = bytes_in_used + %{0}, bytes_out_used = bytes_out_used + %{1}, bytes_xfer_used = bytes_xfer_used + %{2}, files_in_used = files_in_used + %{3}, files_out_used= files_out_used + %{4}, files_xfer_used = files_xfer_used + %{5} WHERE name= '%{6}' AND quota_type = '%{7}'" ftp_quotatallies +SQLNamedQuery insert-quota-tally INSERT "%{0}, %{1}, %{2}, %{3}, %{4},%{5}, %{6}, %{7}" ftp_quotatallies + + +]]> + + + + +TLSEngine on +TLSLog /var/log/proftpd/tls.log +TLSProtocol TLSv1 TLSv1.1 TLSv1.2 +TLSRSACertificateFile /etc/ssl/certs/proftpd.crt +TLSRSACertificateKeyFile /etc/ssl/private/proftpd.key +TLSECCertificateFile /etc/ssl/certs/proftpd_ec.crt +TLSECCertificateKeyFile /etc/ssl/private/proftpd_ec.key +TLSOptions NoCertRequest NoSessionReuseRequired +TLSVerifyClient off + +# Are clients required to use FTP over TLS when talking to this server? +#TLSRequired on + +# Allow SSL/TLS renegotiations when the client requests them, but +# do not force the renegotations. Some clients do not support +# SSL/TLS renegotiations; when mod_tls forces a renegotiation, these +# clients will close the data connection, or there will be a timeout +# on an idle data connection. +# +#TLSRenegotiate required off + +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# Mandatory : user password. You must have a password. + +MYSQLPassword + + +# Mandatory : database to open. + +MYSQLDatabase + + +# Mandatory : how passwords are stored +# Valid values are : "cleartext", "crypt", "sha1", "md5" and "password" +# ("password" = MySQL password() function) +# You can also use "any" to try "crypt", "sha1", "md5" *and* "password" + +MYSQLCrypt any + + +# In the following directives, parts of the strings are replaced at +# run-time before performing queries : +# +# \L is replaced by the login of the user trying to authenticate. +# \I is replaced by the IP address the user connected to. +# \P is replaced by the port number the user connected to. +# \R is replaced by the IP address the user connected from. +# \D is replaced by the remote IP address, as a long decimal number. +# +# Very complex queries can be performed using these substitution strings, +# especially for virtual hosting. + + +# Query to execute in order to fetch the password + +MYSQLGetPW SELECT password FROM ftp_users WHERE username="\L" AND login_enabled="y" + + +# Query to execute in order to fetch the system user name or uid + +MYSQLGetUID SELECT uid FROM ftp_users WHERE username="\L" AND login_enabled="y" + + +# Optional : default UID - if set this overrides MYSQLGetUID + +#MYSQLDefaultUID 1000 + + +# Query to execute in order to fetch the system user group or gid + +MYSQLGetGID SELECT gid FROM ftp_users WHERE username="\L" AND login_enabled="y" + + +# Optional : default GID - if set this overrides MYSQLGetGID + +#MYSQLDefaultGID 1000 + + +# Query to execute in order to fetch the home directory + +MYSQLGetDir SELECT homedir FROM ftp_users WHERE username="\L" AND login_enabled="y" + + +# Optional : query to get the maximal number of files +# Pure-FTPd must have been compiled with virtual quotas support. + +# MySQLGetQTAFS SELECT QuotaFiles FROM users WHERE User='\L' + + +# Optional : query to get the maximal disk usage (virtual quotas) +# The number should be in Megabytes. +# Pure-FTPd must have been compiled with virtual quotas support. + +MySQLGetQTASZ SELECT panel_customers.diskspace/1024 AS QuotaSize FROM panel_customers, ftp_users WHERE username = "\L" AND panel_customers.loginname = SUBSTRING_INDEX('\L', 'ftp', 1) + + +# Optional : ratios. The server has to be compiled with ratio support. + +# MySQLGetRatioUL SELECT ULRatio FROM users WHERE User='\L' +# MySQLGetRatioDL SELECT DLRatio FROM users WHERE User='\L' + + +# Optional : bandwidth throttling. +# The server has to be compiled with throttling support. +# Values are in KB/s . + +# MySQLGetBandwidthUL SELECT ULBandwidth FROM users WHERE User='\L' +# MySQLGetBandwidthDL SELECT DLBandwidth FROM users WHERE User='\L' + +# Enable ~ expansion. NEVER ENABLE THIS BLINDLY UNLESS : +# 1) You know what you are doing. +# 2) Real and virtual users match. + +# MySQLForceTildeExpansion 1 + + +# If you're using a transactionnal storage engine, you can enable SQL +# transactions to avoid races. Leave this commented if you are using the +# traditional MyIsam engine. + +# MySQLTransactions On +]]> + + + + + + + + + + + + + + + + + + + + + + + scripts/froxlor_master_cronjob.php +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + *.log { + missingok + weekly + rotate 4 + compress + delaycompress + notifempty + create + sharedscripts + postrotate + > /dev/null 2>&1 || true + endscript +} +]]> + + + + + + + + + {{settings.system.mod_fcgid_ownvhost}} + + + + + + + + + + + + + + {{settings.system.webserver}} + + + + + + {{settings.system.webserver}} + + + + + {{settings.phpfpm.enabled_ownvhost}} + + {{settings.phpfpm.vhost_httpuser}} + + + + + + {{settings.system.webserver}} + + {{settings.phpfpm.enabled_ownvhost}} + + + + + {{settings.system.webserver}} + + + + + + + + + + From 2508d855e3e8aaf9af95e43133077758300d3afa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20F=C3=B6rster=20=28Dessa=29?= Date: Tue, 3 Apr 2018 13:12:32 +0200 Subject: [PATCH 178/746] deprecate precise, as xenial configs are now available --- lib/configfiles/precise.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/configfiles/precise.xml b/lib/configfiles/precise.xml index 6d7e8b15..2e17ba72 100644 --- a/lib/configfiles/precise.xml +++ b/lib/configfiles/precise.xml @@ -1,6 +1,6 @@ - + From b3d018c506e9f351e8a80cb4da1b71c56f71e52c Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Wed, 25 Apr 2018 12:27:40 +0200 Subject: [PATCH 179/746] corrected usage of default redirect code from settings; fixes #546 Signed-off-by: Michael Kaufmann --- lib/functions/output/function.RedirectCode.php | 18 ++++++++++++++---- scripts/jobs/cron_tasks.inc.http.10.apache.php | 2 +- .../jobs/cron_tasks.inc.http.20.lighttpd.php | 2 +- scripts/jobs/cron_tasks.inc.http.30.nginx.php | 2 +- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/lib/functions/output/function.RedirectCode.php b/lib/functions/output/function.RedirectCode.php index c5df262c..5a8cd57d 100644 --- a/lib/functions/output/function.RedirectCode.php +++ b/lib/functions/output/function.RedirectCode.php @@ -36,9 +36,11 @@ function getRedirectCodesArray() { * return an array of all enabled redirect-codes * for the settings form * + * @param bool $add_desc optional, default true, add the code-description + * * @return array array of enabled redirect-codes */ -function getRedirectCodes() { +function getRedirectCodes($add_desc = true) { global $lng; @@ -47,7 +49,10 @@ function getRedirectCodes() { $codes = array(); while ($rc = $result_stmt->fetch(PDO::FETCH_ASSOC)) { - $codes[$rc['id']] = $rc['code']. ' ('.$lng['redirect_desc'][$rc['desc']].')'; + $codes[$rc['id']] = $rc['code']; + if ($add_desc) { + $codes[$rc['id']] .= ' ('.$lng['redirect_desc'][$rc['desc']].')'; + } } return $codes; @@ -58,12 +63,17 @@ function getRedirectCodes() { * domain-id * * @param integer $domainid id of the domain - * @param string $default * * @return string redirect-code */ -function getDomainRedirectCode($domainid = 0, $default = '') { +function getDomainRedirectCode($domainid = 0) { + // get system default + $default = '301'; + if (Settings::Get('customredirect.enabled') == '1') { + $all_codes = getRedirectCodes(false); + $default = $all_codes[Settings::Get('customredirect.default')]; + } $code = $default; if ($domainid > 0) { diff --git a/scripts/jobs/cron_tasks.inc.http.10.apache.php b/scripts/jobs/cron_tasks.inc.http.10.apache.php index 21225125..fc436219 100644 --- a/scripts/jobs/cron_tasks.inc.http.10.apache.php +++ b/scripts/jobs/cron_tasks.inc.http.10.apache.php @@ -964,7 +964,7 @@ class apache extends HttpConfigBase $corrected_docroot = $domain['documentroot']; // Get domain's redirect code - $code = getDomainRedirectCode($domain['id'], '301'); + $code = getDomainRedirectCode($domain['id']); $modrew_red = ''; if ($code != '') { $modrew_red = ' [R=' . $code . ';L,NE]'; diff --git a/scripts/jobs/cron_tasks.inc.http.20.lighttpd.php b/scripts/jobs/cron_tasks.inc.http.20.lighttpd.php index 84b88400..661005f6 100644 --- a/scripts/jobs/cron_tasks.inc.http.20.lighttpd.php +++ b/scripts/jobs/cron_tasks.inc.http.20.lighttpd.php @@ -467,7 +467,7 @@ class lighttpd extends HttpConfigBase $uri = $domain['documentroot']; // Get domain's redirect code - $code = getDomainRedirectCode($domain['id'], '301'); + $code = getDomainRedirectCode($domain['id']); $vhost_content .= ' url.redirect-code = ' . $code. "\n"; $vhost_content .= ' url.redirect = (' . "\n"; diff --git a/scripts/jobs/cron_tasks.inc.http.30.nginx.php b/scripts/jobs/cron_tasks.inc.http.30.nginx.php index e2eac04d..a979463b 100644 --- a/scripts/jobs/cron_tasks.inc.http.30.nginx.php +++ b/scripts/jobs/cron_tasks.inc.http.30.nginx.php @@ -486,7 +486,7 @@ class nginx extends HttpConfigBase } // Get domain's redirect code - $code = getDomainRedirectCode($domain['id'], '301'); + $code = getDomainRedirectCode($domain['id']); $vhost_content .= "\t" . 'if ($request_uri !~ ^/.well-known/acme-challenge/\w+$) {' . "\n"; $vhost_content .= "\t\t" . 'return ' . $code .' ' . $uri . '$request_uri;' . "\n"; From 75616cc727a738bd78a6df38199050b09c882f45 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Wed, 25 Apr 2018 12:27:59 +0200 Subject: [PATCH 180/746] fix typo in german language file Signed-off-by: Michael Kaufmann --- lng/german.lng.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lng/german.lng.php b/lng/german.lng.php index 9912b00f..7f44f981 100644 --- a/lng/german.lng.php +++ b/lng/german.lng.php @@ -1685,7 +1685,7 @@ $lng['serversettings']['panel_customer_hide_options']['description'] = 'Wählen $lng['serversettings']['allow_allow_customer_shell']['title'] = 'Erlaube Kunden für FTP Benutzer eine Shell auszuwählen'; $lng['serversettings']['allow_allow_customer_shell']['description'] = 'Bitte beachten: Shell Zugriff gestattet dem Benutzer verschiedene Programme auf Ihrem System auszuführen. Mit großer Vorsicht verwenden. Bitte aktiviere dies nur wenn WIRKLICH bekannt ist, was das bedeutet!!!'; $lng['serversettings']['available_shells']['title'] = 'Liste der verfügbaren Shells'; -$lng['serversettings']['available_shells']['description'] = 'Komme-getrennte Liste von Shells die der Kunde für seine FTP-Konten wählen kann.

    Hinweis: Die Standard-Shell /bin/false wird immer eine Auswahlmöglichkeit sein (wenn aktiviert), auch wenn diese Einstellung leer ist. Sie ist in jedem Fall der Standardwert für alle FTP-Konten'; +$lng['serversettings']['available_shells']['description'] = 'Komma-getrennte Liste von Shells die der Kunde für seine FTP-Konten wählen kann.

    Hinweis: Die Standard-Shell /bin/false wird immer eine Auswahlmöglichkeit sein (wenn aktiviert), auch wenn diese Einstellung leer ist. Sie ist in jedem Fall der Standardwert für alle FTP-Konten'; $lng['serversettings']['le_froxlor_enabled']['title'] = "Let's Encrypt für den froxlor Vhost verwenden"; $lng['serversettings']['le_froxlor_enabled']['description'] = "Wenn dies aktiviert ist, erstellt froxlor für seinen vhost automatisch ein Let's Encrypt Zertifikat."; $lng['serversettings']['le_froxlor_redirect']['title'] = "SSL-Weiterleitung für den froxlor Vhost aktivieren"; From 84b8cda7acba3e8945cea2d773f7f4531b9ac746 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Wed, 25 Apr 2018 12:29:30 +0200 Subject: [PATCH 181/746] allow usage of up to 255 characters in a txt record, fixes #548 Signed-off-by: Michael Kaufmann --- lib/classes/dns/class.DnsEntry.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/classes/dns/class.DnsEntry.php b/lib/classes/dns/class.DnsEntry.php index a5685741..06e18cbc 100644 --- a/lib/classes/dns/class.DnsEntry.php +++ b/lib/classes/dns/class.DnsEntry.php @@ -43,9 +43,9 @@ class DnsEntry { $_content = $this->content; // check content length for txt records for bind9 (multiline) - if (Settings::Get('system.dns_server') != 'pdns' && $this->type == 'TXT' && strlen($_content) >= 64) { + if (Settings::Get('system.dns_server') != 'pdns' && $this->type == 'TXT' && strlen($_content) >= 255) { // split string - $_contentlines = str_split($_content, 63); + $_contentlines = str_split($_content, 254); // first line $_l = array_shift($_contentlines); // check for starting quote From 67b95a301b023ed6f49e38c7a56d780034762195 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Sun, 6 May 2018 14:38:18 +0200 Subject: [PATCH 182/746] check only for existing .conf files when trying to find out whether a fpm pool config directory is empty; fixes #543 Signed-off-by: Michael Kaufmann --- scripts/jobs/cron_tasks.inc.http.10.apache.php | 5 ++--- scripts/jobs/cron_tasks.inc.http.20.lighttpd.php | 5 ++--- scripts/jobs/cron_tasks.inc.http.30.nginx.php | 5 ++--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/scripts/jobs/cron_tasks.inc.http.10.apache.php b/scripts/jobs/cron_tasks.inc.http.10.apache.php index fc436219..5f966f38 100644 --- a/scripts/jobs/cron_tasks.inc.http.10.apache.php +++ b/scripts/jobs/cron_tasks.inc.http.10.apache.php @@ -66,9 +66,8 @@ class apache extends HttpConfigBase foreach ($restart_cmds as $restart_cmd) { // check whether the config dir is empty (no domains uses this daemon) // so we need to create a dummy - $fsi = new \FilesystemIterator($restart_cmd['config_dir']); - $isDirEmpty = !$fsi->valid(); - if ($isDirEmpty) { + $_conffiles = glob(makeCorrectFile($restart_cmd['config_dir'] . "/*.conf")); + if ($_conffiles === false || empty($_conffiles)) { $this->logger->logAction(CRON_ACTION, LOG_INFO, 'apache::reload: fpm config directory "' . $restart_cmd['config_dir'] . '" is empty. Creating dummy.'); phpinterface_fpm::createDummyPool($restart_cmd['config_dir']); } diff --git a/scripts/jobs/cron_tasks.inc.http.20.lighttpd.php b/scripts/jobs/cron_tasks.inc.http.20.lighttpd.php index 661005f6..1df73682 100644 --- a/scripts/jobs/cron_tasks.inc.http.20.lighttpd.php +++ b/scripts/jobs/cron_tasks.inc.http.20.lighttpd.php @@ -66,9 +66,8 @@ class lighttpd extends HttpConfigBase foreach ($restart_cmds as $restart_cmd) { // check whether the config dir is empty (no domains uses this daemon) // so we need to create a dummy - $fsi = new \FilesystemIterator($restart_cmd['config_dir']); - $isDirEmpty = !$fsi->valid(); - if ($isDirEmpty) { + $_conffiles = glob(makeCorrectFile($restart_cmd['config_dir'] . "/*.conf")); + if ($_conffiles === false || empty($_conffiles)) { $this->logger->logAction(CRON_ACTION, LOG_INFO, 'lighttpd::reload: fpm config directory "' . $restart_cmd['config_dir'] . '" is empty. Creating dummy.'); phpinterface_fpm::createDummyPool($restart_cmd['config_dir']); } diff --git a/scripts/jobs/cron_tasks.inc.http.30.nginx.php b/scripts/jobs/cron_tasks.inc.http.30.nginx.php index a979463b..f5bb1efe 100644 --- a/scripts/jobs/cron_tasks.inc.http.30.nginx.php +++ b/scripts/jobs/cron_tasks.inc.http.30.nginx.php @@ -81,9 +81,8 @@ class nginx extends HttpConfigBase foreach ($restart_cmds as $restart_cmd) { // check whether the config dir is empty (no domains uses this daemon) // so we need to create a dummy - $fsi = new \FilesystemIterator($restart_cmd['config_dir']); - $isDirEmpty = !$fsi->valid(); - if ($isDirEmpty) { + $_conffiles = glob(makeCorrectFile($restart_cmd['config_dir'] . "/*.conf")); + if ($_conffiles === false || empty($_conffiles)) { $this->logger->logAction(CRON_ACTION, LOG_INFO, 'nginx::reload: fpm config directory "' . $restart_cmd['config_dir'] . '" is empty. Creating dummy.'); phpinterface_fpm::createDummyPool($restart_cmd['config_dir']); } From 07a3f7656807acf776cbf2b10fab12c722d52096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20F=C3=B6rster=20=28Dessa=29?= Date: Thu, 10 May 2018 16:42:27 +0200 Subject: [PATCH 183/746] remove wheezy configs --- lib/configfiles/wheezy.xml | 5588 ------------------------------------ 1 file changed, 5588 deletions(-) delete mode 100644 lib/configfiles/wheezy.xml diff --git a/lib/configfiles/wheezy.xml b/lib/configfiles/wheezy.xml deleted file mode 100644 index 06b6c3a9..00000000 --- a/lib/configfiles/wheezy.xml +++ /dev/null @@ -1,5588 +0,0 @@ - - - - - - - - - - - {{settings.system.apacheconf_vhost}} - - - - - {{settings.system.apacheconf_vhost}} - - - - - - - {{settings.system.apacheconf_diroptions}} - - - - - {{settings.system.apacheconf_diroptions}} - - - - - - - - - - - {{settings.system.deactivateddocroot}} - - - - - - - - - //service[@type='http']/general/commands - - - - {{settings.system.use_ssl}} - - - - - {{settings.phpfpm.enabled}} - - - FastCgiIpcDir - - - Order Deny,Allow - Deny from All - # Prevent accessing this path directly - Allow from env=REDIRECT_STATUS - - -]]> - - - - {{settings.system.leenabled}} - - - Order allow,deny - Allow from all - -]]> - - - - - - - //service[@type='http']/general/commands - - - - {{settings.system.use_ssl}} - - - - - {{settings.phpfpm.enabled}} - - - FastCgiIpcDir - - - Require all granted - Require env REDIRECT_STATUS - - -]]> - - - - {{settings.system.leenabled}} - - - Require all granted - -]]> - - - - - - - - - "{{settings.system.letsencryptchallengepath}}/.well-known/acme-challenge/") - -# default listening port for IPv6 falls back to the IPv4 port -include_shell "/usr/share/lighttpd/use-ipv6.pl " + server.port -include_shell "/usr/share/lighttpd/create-mime.assign.pl" -include_shell "/usr/share/lighttpd/include-conf-enabled.pl" -]]> - - - //service[@type='http']/general/commands - - {{settings.system.apacheconf_vhost}} - - > /etc/lighttpd/lighttpd.conf]]> - - - {{settings.system.apacheconf_vhost}} - - > /etc/lighttpd/lighttpd.conf]]> - - - {{settings.system.apacheconf_diroptions}} - - > /etc/lighttpd/lighttpd.conf]]> - - - {{settings.system.apacheconf_diroptions}} - - > /etc/lighttpd/lighttpd.conf]]> - - - - - - - - - - {{settings.phpfpm.enabled}} - - {{settings.system.mod_fcgid}} - - - - - - - - - - - - - {{settings.system.leenabled}} - - - - - - {{settings.phpfpm.enabled}} - - {{settings.system.mod_fcgid}} - - - - - //service[@type='http']/general/commands - - {{settings.phpfpm.enabled}} - - {{settings.system.mod_fcgid}} - - - - - - - - - - - - > /etc/bind/named.conf.local]]> - - - - - - - - - -# add these entries to the list if any speficied: - -################################# -# allow-recursion List of netmasks that are allowed to recurse -# -allow-recursion=127.0.0.1 - -################################# -# allow-recursion-override Local data even about hosts that don't exist will -# override the internet. (on/off) -# -# allow-recursion-override= - -################################# -# cache-ttl Seconds to store packets in the PacketCache -# -# cache-ttl=20 - -################################# -# chroot If set, chroot to this directory for more security -# -# chroot=/var/spool/powerdns - -################################# -# config-dir Location of configuration directory (pdns.conf) -# -config-dir=/etc/powerdns - -################################# -# config-name Name of this virtual configuration - will rename the binary image -# -# config-name= - -################################# -# control-console Debugging switch - don't use -# -# control-console=no - -################################# -# daemon Operate as a daemon -# -daemon=yes - -################################# -# default-soa-name name to insert in the SOA record if none set in the backend -# -# default-soa-name=a.misconfigured.powerdns.server - -################################# -# disable-axfr Disable zonetransfers but do allow TCP queries -# -disable-axfr=yes - -################################# -# disable-tcp Do not listen to TCP queries -# -# disable-tcp=no - -################################# -# distributor-threads Default number of Distributor (backend) threads to start -# -# distributor-threads=3 - -################################# -# fancy-records Process URL and MBOXFW records -# -# fancy-records=no - -################################# -# guardian Run within a guardian process -# -guardian=yes - -################################# -# launch Which backends to launch and order to query them in -# -# launch= - -################################# -# lazy-recursion Only recurse if question cannot be answered locally -# -lazy-recursion=yes - -################################# -# load-modules Load this module - supply absolute or relative path -# -# load-modules= - -################################# -# local-address Local IP address to which we bind -# -local-address=,127.0.0.1 - -################################# -# local-ipv6 Local IP address to which we bind -# -# local-ipv6= - -################################# -# local-port The port on which we listen -# -local-port=53 - -################################# -# log-dns-details If PDNS should log failed update requests -# -log-dns-details=yes - -################################# -# log-failed-updates If PDNS should log failed update requests -# -# log-failed-updates= - -################################# -# logfile Logfile to use -# -# logfile=/var/log/pdns.log - -################################# -# logging-facility Log under a specific facility -# -# logging-facility= - -################################# -# loglevel Amount of logging. Higher is more. Do not set below 3 -# -# loglevel=4 - -################################# -# master Act as a master -# -master=yes - -################################# -# max-queue-length Maximum queuelength before considering situation lost -# -# max-queue-length=5000 - -################################# -# max-tcp-connections Maximum number of TCP connections -# -# max-tcp-connections=10 - -################################# -# module-dir Default directory for modules -# -module-dir=/usr/lib/powerdns - -################################# -# negquery-cache-ttl Seconds to store packets in the PacketCache -# -# negquery-cache-ttl=60 - -################################# -# out-of-zone-additional-processing Do out of zone additional processing -# -# out-of-zone-additional-processing=no - -################################# -# query-cache-ttl Seconds to store packets in the PacketCache -# -# query-cache-ttl=20 - -################################# -# query-logging Hint backends that queries should be logged -# -# query-logging=no - -################################# -# queue-limit Maximum number of milliseconds to queue a query -# -# queue-limit=1500 - -################################# -# query-local-address The IP address to use as a source address for sending -# queries. -# query-local-address= - -################################# -# receiver-threads Number of receiver threads to launch -# -# receiver-threads=1 - -################################# -# recursive-cache-ttl Seconds to store packets in the PacketCache -# -# recursive-cache-ttl=10 - -################################# -# recursor If recursion is desired, IP address of a recursing nameserver -# -# recursor= - -################################# -# setgid If set, change group id to this gid for more security -# -setgid=pdns - -################################# -# setuid If set, change user id to this uid for more security -# -setuid=pdns - -################################# -# skip-cname Do not perform CNAME indirection for each query -# -# skip-cname=no - -################################# -# slave Act as a slave -# -# slave=no - -################################# -# slave-cycle-interval Reschedule failed SOA serial checks once every .. seconds -# -# slave-cycle-interval=60 - -################################# -# smtpredirector Our smtpredir MX host -# -# smtpredirector=a.misconfigured.powerdns.smtp.server - -################################# -# soa-minimum-ttl Default SOA mininum ttl -# -# soa-minimum-ttl=3600 - -################################# -# soa-refresh-default Default SOA refresh -# -# soa-refresh-default=10800 - -################################# -# soa-retry-default Default SOA retry -# -# soa-retry-default=3600 - -################################# -# soa-expire-default Default SOA expire -# -# soa-expire-default=604800 - -################################# -# soa-serial-offset Make sure that no SOA serial is less than this number -# -# soa-serial-offset=0 - -################################# -# socket-dir Where the controlsocket will live -# -socket-dir=/var/run - -################################# -# strict-rfc-axfrs Perform strictly rfc compliant axfrs (very slow) -# -# strict-rfc-axfrs=no - -################################# -# urlredirector Where we send hosts to that need to be url redirected -# -# urlredirector=127.0.0.1 - -################################# -# use-logfile Use a log file -# -# use-logfile=yes - -################################# -# webserver Start a webserver for monitoring -# -# webserver=no - -################################# -# webserver-address IP Address of webserver to listen on -# -# webserver-address=127.0.0.1 - -################################# -# webserver-password Password required for accessing the webserver -# -# webserver-password= - -################################# -# webserver-port Port of webserver to listen on -# -# webserver-port=8081 - -################################# -# webserver-print-arguments If the webserver should print arguments -# -# webserver-print-arguments=no - -################################# -# wildcard-url Process URL and MBOXFW records -# -# wildcard-url=no - -################################# -# wildcards Honor wildcards in the database -# -# wildcards= - -################################# -# version-string What should PowerDNS return for version -# allowed methods are anonymous / powerdns / full / custom -version-string=powerdns - -# include froxlor-bind-specific config -include-dir=/etc/powerdns/froxlor/ -]]> - - - - - - - - - - - - - -# add these entries to the list if any speficied: - -################################# -# allow-recursion List of netmasks that are allowed to recurse -# -allow-recursion=127.0.0.1 - -################################# -# allow-recursion-override Local data even about hosts that don't exist will -# override the internet. (on/off) -# -# allow-recursion-override= - -################################# -# cache-ttl Seconds to store packets in the PacketCache -# -# cache-ttl=20 - -################################# -# chroot If set, chroot to this directory for more security -# -# chroot=/var/spool/powerdns - -################################# -# config-dir Location of configuration directory (pdns.conf) -# -config-dir=/etc/powerdns - -################################# -# config-name Name of this virtual configuration - will rename the binary image -# -# config-name= - -################################# -# control-console Debugging switch - don't use -# -# control-console=no - -################################# -# daemon Operate as a daemon -# -daemon=yes - -################################# -# default-soa-name name to insert in the SOA record if none set in the backend -# -# default-soa-name=a.misconfigured.powerdns.server - -################################# -# disable-axfr Disable zonetransfers but do allow TCP queries -# -disable-axfr=yes - -################################# -# disable-tcp Do not listen to TCP queries -# -# disable-tcp=no - -################################# -# distributor-threads Default number of Distributor (backend) threads to start -# -# distributor-threads=3 - -################################# -# fancy-records Process URL and MBOXFW records -# -# fancy-records=no - -################################# -# guardian Run within a guardian process -# -guardian=yes - -################################# -# launch Which backends to launch and order to query them in -# -launch=bind - -################################# -# lazy-recursion Only recurse if question cannot be answered locally -# -lazy-recursion=yes - -################################# -# load-modules Load this module - supply absolute or relative path -# -# load-modules= - -################################# -# local-address Local IP address to which we bind -# -local-address=,127.0.0.1 - -################################# -# local-ipv6 Local IP address to which we bind -# -# local-ipv6= - -################################# -# local-port The port on which we listen -# -local-port=53 - -################################# -# log-dns-details If PDNS should log failed update requests -# -log-dns-details=yes - -################################# -# log-failed-updates If PDNS should log failed update requests -# -# log-failed-updates= - -################################# -# logfile Logfile to use -# -# logfile=/var/log/pdns.log - -################################# -# logging-facility Log under a specific facility -# -# logging-facility= - -################################# -# loglevel Amount of logging. Higher is more. Do not set below 3 -# -# loglevel=4 - -################################# -# master Act as a master -# -master=yes - -################################# -# max-queue-length Maximum queuelength before considering situation lost -# -# max-queue-length=5000 - -################################# -# max-tcp-connections Maximum number of TCP connections -# -# max-tcp-connections=10 - -################################# -# module-dir Default directory for modules -# -module-dir=/usr/lib/powerdns - -################################# -# negquery-cache-ttl Seconds to store packets in the PacketCache -# -# negquery-cache-ttl=60 - -################################# -# out-of-zone-additional-processing Do out of zone additional processing -# -# out-of-zone-additional-processing=no - -################################# -# query-cache-ttl Seconds to store packets in the PacketCache -# -# query-cache-ttl=20 - -################################# -# query-logging Hint backends that queries should be logged -# -# query-logging=no - -################################# -# queue-limit Maximum number of milliseconds to queue a query -# -# queue-limit=1500 - -################################# -# query-local-address The IP address to use as a source address for sending -# queries. -# query-local-address= - -################################# -# receiver-threads Number of receiver threads to launch -# -# receiver-threads=1 - -################################# -# recursive-cache-ttl Seconds to store packets in the PacketCache -# -# recursive-cache-ttl=10 - -################################# -# recursor If recursion is desired, IP address of a recursing nameserver -# -# recursor= - -################################# -# setgid If set, change group id to this gid for more security -# -setgid=pdns - -################################# -# setuid If set, change user id to this uid for more security -# -setuid=pdns - -################################# -# skip-cname Do not perform CNAME indirection for each query -# -# skip-cname=no - -################################# -# slave Act as a slave -# -# slave=no - -################################# -# slave-cycle-interval Reschedule failed SOA serial checks once every .. seconds -# -# slave-cycle-interval=60 - -################################# -# smtpredirector Our smtpredir MX host -# -# smtpredirector=a.misconfigured.powerdns.smtp.server - -################################# -# soa-minimum-ttl Default SOA mininum ttl -# -# soa-minimum-ttl=3600 - -################################# -# soa-refresh-default Default SOA refresh -# -# soa-refresh-default=10800 - -################################# -# soa-retry-default Default SOA retry -# -# soa-retry-default=3600 - -################################# -# soa-expire-default Default SOA expire -# -# soa-expire-default=604800 - -################################# -# soa-serial-offset Make sure that no SOA serial is less than this number -# -# soa-serial-offset=0 - -################################# -# socket-dir Where the controlsocket will live -# -socket-dir=/var/run - -################################# -# strict-rfc-axfrs Perform strictly rfc compliant axfrs (very slow) -# -# strict-rfc-axfrs=no - -################################# -# urlredirector Where we send hosts to that need to be url redirected -# -# urlredirector=127.0.0.1 - -################################# -# use-logfile Use a log file -# -# use-logfile=yes - -################################# -# webserver Start a webserver for monitoring -# -# webserver=no - -################################# -# webserver-address IP Address of webserver to listen on -# -# webserver-address=127.0.0.1 - -################################# -# webserver-password Password required for accessing the webserver -# -# webserver-password= - -################################# -# webserver-port Port of webserver to listen on -# -# webserver-port=8081 - -################################# -# webserver-print-arguments If the webserver should print arguments -# -# webserver-print-arguments=no - -################################# -# wildcard-url Process URL and MBOXFW records -# -# wildcard-url=no - -################################# -# wildcards Honor wildcards in the database -# -# wildcards= - -################################# -# version-string What should PowerDNS return for version -# allowed methods are anonymous / powerdns / full / custom -version-string=powerdns - -# include froxlor-bind-specific config -include-dir=/etc/powerdns/froxlor/ -]]> - - - - - named.conf - -# How often to check for zone changes. See 'Operation' section. -bind-check-interval=180 - -# Uncomment to enable Huffman compression on zone data. -# Currently saves around 20% of memory actually used, but slows down operation. -# bind-enable-huffman -]]> - - - - - - - - - - - - {{settings.system.vmail_gid}} - - - - - {{settings.system.vmail_uid}} - - - - - - - - - - - - - - - - - -password = -dbname = -hosts = -query = SELECT destination FROM mail_virtual WHERE email = '%s' AND trim(destination) <> '' -]]> - - - - -password = -dbname = -hosts = -query = SELECT domain FROM panel_domains WHERE domain = '%s' AND isemaildomain = '1' -]]> - - - - -password = -dbname = -expansion_limit = 1 -hosts = -query = SELECT CONCAT(homedir,maildir) FROM mail_users WHERE email = '%s' -]]> - - - - -password = -dbname = -hosts = -query = SELECT DISTINCT username FROM mail_users WHERE email in ((SELECT mail_virtual.email_full FROM mail_virtual WHERE mail_virtual.email = '%s' UNION SELECT mail_virtual.destination FROM mail_virtual WHERE mail_virtual.email = '%s')); -]]> - - - - -password = -dbname = -expansion_limit = 1 -hosts = -query = SELECT uid FROM mail_users WHERE email = '%s' -]]> - - - - -password = -dbname = -expansion_limit = 1 -hosts = -query = SELECT gid FROM mail_users WHERE email = '%s' -]]> - - - - -]]> - - - - - - - - - - - //service[@type='smtp']/general/commands[@index=1] - - //service[@type='smtp']/general/installs[@index=1] - - //service[@type='smtp']/general/commands[@index=2] - - - - -# set myhostname to $mydomain because Froxlor alrady uses a FQDN -myhostname = $mydomain - -# SENDING MAIL -# -# The myorigin parameter specifies the domain that locally-posted -# mail appears to come from. The default is to append $myhostname, -# which is fine for small sites. If you run a domain with multiple -# machines, you should (1) change this to $mydomain and (2) set up -# a domain-wide alias database that aliases each user to -# user@that.users.mailhost. -# -# For the sake of consistency between sender and recipient addresses, -# myorigin also specifies the default domain name that is appended -# to recipient addresses that have no @domain part. -# -# Debian GNU/Linux specific: Specifying a file name will cause the -# first line of that file to be used as the name. The Debian default -# is /etc/mailname. -# -#myorigin = /etc/mailname -#myorigin = $myhostname -#myorigin = $mydomain - -# RECEIVING MAIL - -# The inet_interfaces parameter specifies the network interface -# addresses that this mail system receives mail on. By default, -# the software claims all active interfaces on the machine. The -# parameter also controls delivery of mail to user@[ip.address]. -# -# See also the proxy_interfaces parameter, for network addresses that -# are forwarded to us via a proxy or network address translator. -# -# Note: you need to stop/start Postfix when this parameter changes. -# -inet_interfaces = all -#inet_interfaces = $myhostname -#inet_interfaces = $myhostname, localhost - -# The proxy_interfaces parameter specifies the network interface -# addresses that this mail system receives mail on by way of a -# proxy or network address translation unit. This setting extends -# the address list specified with the inet_interfaces parameter. -# -# You must specify your proxy/NAT addresses when your system is a -# backup MX host for other domains, otherwise mail delivery loops -# will happen when the primary MX host is down. -# -#proxy_interfaces = -#proxy_interfaces = 1.2.3.4 - -# The mydestination parameter specifies the list of domains that this -# machine considers itself the final destination for. -# -# These domains are routed to the delivery agent specified with the -# local_transport parameter setting. By default, that is the UNIX -# compatible delivery agent that lookups all recipients in /etc/passwd -# and /etc/aliases or their equivalent. -# -# The default is $myhostname + localhost.$mydomain. On a mail domain -# gateway, you should also include $mydomain. -# -# Do not specify the names of virtual domains - those domains are -# specified elsewhere (see VIRTUAL_README). -# -# Do not specify the names of domains that this machine is backup MX -# host for. Specify those names via the relay_domains settings for -# the SMTP server, or use permit_mx_backup if you are lazy (see -# STANDARD_CONFIGURATION_README). -# -# The local machine is always the final destination for mail addressed -# to user@[the.net.work.address] of an interface that the mail system -# receives mail on (see the inet_interfaces parameter). -# -# Specify a list of host or domain names, /file/name or type:table -# patterns, separated by commas and/or whitespace. A /file/name -# pattern is replaced by its contents; a type:table is matched when -# a name matches a lookup key (the right-hand side is ignored). -# Continue long lines by starting the next line with whitespace. -# -# See also below, section "REJECTING MAIL FOR UNKNOWN LOCAL USERS". -# -#mydestination = $myhostname, localhost.$mydomain, localhost -mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain -#mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain, -# mail.$mydomain, www.$mydomain, ftp.$mydomain - -# REJECTING MAIL FOR UNKNOWN LOCAL USERS -# -# The local_recipient_maps parameter specifies optional lookup tables -# with all names or addresses of users that are local with respect -# to $mydestination, $inet_interfaces or $proxy_interfaces. -# -# If this parameter is defined, then the SMTP server will reject -# mail for unknown local users. This parameter is defined by default. -# -# To turn off local recipient checking in the SMTP server, specify -# local_recipient_maps = (i.e. empty). -# -# The default setting assumes that you use the default Postfix local -# delivery agent for local delivery. You need to update the -# local_recipient_maps setting if: -# -# - You define $mydestination domain recipients in files other than -# /etc/passwd, /etc/aliases, or the $virtual_alias_maps files. -# For example, you define $mydestination domain recipients in -# the $virtual_mailbox_maps files. -# -# - You redefine the local delivery agent in master.cf. -# -# - You redefine the "local_transport" setting in main.cf. -# -# - You use the "luser_relay", "mailbox_transport", or "fallback_transport" -# feature of the Postfix local delivery agent (see local(8)). -# -# Details are described in the LOCAL_RECIPIENT_README file. -# -# Beware: if the Postfix SMTP server runs chrooted, you probably have -# to access the passwd file via the proxymap service, in order to -# overcome chroot restrictions. The alternative, having a copy of -# the system passwd file in the chroot jail is just not practical. -# -# The right-hand side of the lookup tables is conveniently ignored. -# In the left-hand side, specify a bare username, an @domain.tld -# wild-card, or specify a user@domain.tld address. -# -#local_recipient_maps = unix:passwd.byname $alias_maps -#local_recipient_maps = proxy:unix:passwd.byname $alias_maps -#local_recipient_maps = - -# The unknown_local_recipient_reject_code specifies the SMTP server -# response code when a recipient domain matches $mydestination or -# ${proxy,inet}_interfaces, while $local_recipient_maps is non-empty -# and the recipient address or address local-part is not found. -# -# The default setting is 550 (reject mail) but it is safer to start -# with 450 (try again later) until you are certain that your -# local_recipient_maps settings are OK. -# -unknown_local_recipient_reject_code = 550 - -# TRUST AND RELAY CONTROL - -# The mynetworks parameter specifies the list of "trusted" SMTP -# clients that have more privileges than "strangers". -# -# In particular, "trusted" SMTP clients are allowed to relay mail -# through Postfix. See the smtpd_recipient_restrictions parameter -# in postconf(5). -# -# You can specify the list of "trusted" network addresses by hand -# or you can let Postfix do it for you (which is the default). -# -# By default (mynetworks_style = subnet), Postfix "trusts" SMTP -# clients in the same IP subnetworks as the local machine. -# On Linux, this does works correctly only with interfaces specified -# with the "ifconfig" command. -# -# Specify "mynetworks_style = class" when Postfix should "trust" SMTP -# clients in the same IP class A/B/C networks as the local machine. -# Don't do this with a dialup site - it would cause Postfix to "trust" -# your entire provider's network. Instead, specify an explicit -# mynetworks list by hand, as described below. -# -# Specify "mynetworks_style = host" when Postfix should "trust" -# only the local machine. -# -#mynetworks_style = class -#mynetworks_style = subnet -#mynetworks_style = host - -# Alternatively, you can specify the mynetworks list by hand, in -# which case Postfix ignores the mynetworks_style setting. -# -# Specify an explicit list of network/netmask patterns, where the -# mask specifies the number of bits in the network part of a host -# address. -# -# You can also specify the absolute pathname of a pattern file instead -# of listing the patterns here. Specify type:table for table-based lookups -# (the value on the table right-hand side is not used). -# -#mynetworks = 168.100.189.0/28, 127.0.0.0/8 -#mynetworks = $config_directory/mynetworks -#mynetworks = hash:/etc/postfix/network_table -mynetworks = 127.0.0.0/8 - -# The relay_domains parameter restricts what destinations this system will -# relay mail to. See the smtpd_recipient_restrictions description in -# postconf(5) for detailed information. -# -# By default, Postfix relays mail -# - from "trusted" clients (IP address matches $mynetworks) to any destination, -# - from "untrusted" clients to destinations that match $relay_domains or -# subdomains thereof, except addresses with sender-specified routing. -# The default relay_domains value is $mydestination. -# -# In addition to the above, the Postfix SMTP server by default accepts mail -# that Postfix is final destination for: -# - destinations that match $inet_interfaces or $proxy_interfaces, -# - destinations that match $mydestination -# - destinations that match $virtual_alias_domains, -# - destinations that match $virtual_mailbox_domains. -# These destinations do not need to be listed in $relay_domains. -# -# Specify a list of hosts or domains, /file/name patterns or type:name -# lookup tables, separated by commas and/or whitespace. Continue -# long lines by starting the next line with whitespace. A file name -# is replaced by its contents; a type:name table is matched when a -# (parent) domain appears as lookup key. -# -# NOTE: Postfix will not automatically forward mail for domains that -# list this system as their primary or backup MX host. See the -# permit_mx_backup restriction description in postconf(5). -# -#relay_domains = $mydestination - -# INTERNET OR INTRANET - -# The relayhost parameter specifies the default host to send mail to -# when no entry is matched in the optional transport(5) table. When -# no relayhost is given, mail is routed directly to the destination. -# -# On an intranet, specify the organizational domain name. If your -# internal DNS uses no MX records, specify the name of the intranet -# gateway host instead. -# -# In the case of SMTP, specify a domain, host, host:port, [host]:port, -# [address] or [address]:port; the form [host] turns off MX lookups. -# -# If you're connected via UUCP, see also the default_transport parameter. -# -#relayhost = $mydomain -#relayhost = [gateway.my.domain] -#relayhost = [mailserver.isp.tld] -#relayhost = uucphost -#relayhost = [an.ip.add.ress] - -# REJECTING UNKNOWN RELAY USERS -# -# The relay_recipient_maps parameter specifies optional lookup tables -# with all addresses in the domains that match $relay_domains. -# -# If this parameter is defined, then the SMTP server will reject -# mail for unknown relay users. This feature is off by default. -# -# The right-hand side of the lookup tables is conveniently ignored. -# In the left-hand side, specify an @domain.tld wild-card, or specify -# a user@domain.tld address. -# -#relay_recipient_maps = hash:/etc/postfix/relay_recipients - -# INPUT RATE CONTROL -# -# The in_flow_delay configuration parameter implements mail input -# flow control. This feature is turned on by default, although it -# still needs further development (it's disabled on SCO UNIX due -# to an SCO bug). -# -# A Postfix process will pause for $in_flow_delay seconds before -# accepting a new message, when the message arrival rate exceeds the -# message delivery rate. With the default 100 SMTP server process -# limit, this limits the mail inflow to 100 messages a second more -# than the number of messages delivered per second. -# -# Specify 0 to disable the feature. Valid delays are 0..10. -# -#in_flow_delay = 1s - -# ADDRESS REWRITING -# -# The ADDRESS_REWRITING_README document gives information about -# address masquerading or other forms of address rewriting including -# username->Firstname.Lastname mapping. - -# ADDRESS REDIRECTION (VIRTUAL DOMAIN) -# -# The VIRTUAL_README document gives information about the many forms -# of domain hosting that Postfix supports. - -# "USER HAS MOVED" BOUNCE MESSAGES -# -# See the discussion in the ADDRESS_REWRITING_README document. - -# TRANSPORT MAP -# -# See the discussion in the ADDRESS_REWRITING_README document. - -# ALIAS DATABASE -# -# The alias_maps parameter specifies the list of alias databases used -# by the local delivery agent. The default list is system dependent. -# -# On systems with NIS, the default is to search the local alias -# database, then the NIS alias database. See aliases(5) for syntax -# details. -# -# If you change the alias database, run "postalias /etc/aliases" (or -# wherever your system stores the mail alias file), or simply run -# "newaliases" to build the necessary DBM or DB file. -# -# It will take a minute or so before changes become visible. Use -# "postfix reload" to eliminate the delay. -# -#alias_maps = dbm:/etc/aliases -#alias_maps = hash:/etc/aliases -#alias_maps = hash:/etc/aliases, nis:mail.aliases -#alias_maps = netinfo:/aliases - -# The alias_database parameter specifies the alias database(s) that -# are built with "newaliases" or "sendmail -bi". This is a separate -# configuration parameter, because alias_maps (see above) may specify -# tables that are not necessarily all under control by Postfix. -# -#alias_database = dbm:/etc/aliases -#alias_database = dbm:/etc/mail/aliases -#alias_database = hash:/etc/aliases -#alias_database = hash:/etc/aliases, hash:/opt/majordomo/aliases - -# ADDRESS EXTENSIONS (e.g., user+foo) -# -# The recipient_delimiter parameter specifies the separator between -# user names and address extensions (user+foo). See canonical(5), -# local(8), relocated(5) and virtual(5) for the effects this has on -# aliases, canonical, virtual, relocated and .forward file lookups. -# Basically, the software tries user+foo and .forward+foo before -# trying user and .forward. -# -#recipient_delimiter = + - -# DELIVERY TO MAILBOX -# -# The home_mailbox parameter specifies the optional pathname of a -# mailbox file relative to a user's home directory. The default -# mailbox file is /var/spool/mail/user or /var/mail/user. Specify -# "Maildir/" for qmail-style delivery (the / is required). -# -#home_mailbox = Mailbox -#home_mailbox = Maildir/ - -# The mail_spool_directory parameter specifies the directory where -# UNIX-style mailboxes are kept. The default setting depends on the -# system type. -# -#mail_spool_directory = /var/mail -#mail_spool_directory = /var/spool/mail - -# The mailbox_command parameter specifies the optional external -# command to use instead of mailbox delivery. The command is run as -# the recipient with proper HOME, SHELL and LOGNAME environment settings. -# Exception: delivery for root is done as $default_user. -# -# Other environment variables of interest: USER (recipient username), -# EXTENSION (address extension), DOMAIN (domain part of address), -# and LOCAL (the address localpart). -# -# Unlike other Postfix configuration parameters, the mailbox_command -# parameter is not subjected to $parameter substitutions. This is to -# make it easier to specify shell syntax (see example below). -# -# Avoid shell meta characters because they will force Postfix to run -# an expensive shell process. Procmail alone is expensive enough. -# -# IF YOU USE THIS TO DELIVER MAIL SYSTEM-WIDE, YOU MUST SET UP AN -# ALIAS THAT FORWARDS MAIL FOR ROOT TO A REAL USER. -# -mailbox_command = /usr/lib/dovecot/deliver -#mailbox_command = /usr/bin/procmail -a "$EXTENSION" - -# The mailbox_transport specifies the optional transport in master.cf -# to use after processing aliases and .forward files. This parameter -# has precedence over the mailbox_command, fallback_transport and -# luser_relay parameters. -# -# Specify a string of the form transport:nexthop, where transport is -# the name of a mail delivery transport defined in master.cf. The -# :nexthop part is optional. For more details see the sample transport -# configuration file. -# -# NOTE: if you use this feature for accounts not in the UNIX password -# file, then you must update the "local_recipient_maps" setting in -# the main.cf file, otherwise the SMTP server will reject mail for -# non-UNIX accounts with "User unknown in local recipient table". -# -# Cyrus IMAP over LMTP. Specify ``lmtpunix cmd="lmtpd" -# listen="/var/imap/socket/lmtp" prefork=0'' in cyrus.conf. -#mailbox_transport = lmtp:unix:/var/imap/socket/lmtp -# -# Cyrus IMAP via command line. Uncomment the "cyrus...pipe" and -# subsequent line in master.cf. -#mailbox_transport = cyrus - -# The fallback_transport specifies the optional transport in master.cf -# to use for recipients that are not found in the UNIX passwd database. -# This parameter has precedence over the luser_relay parameter. -# -# Specify a string of the form transport:nexthop, where transport is -# the name of a mail delivery transport defined in master.cf. The -# :nexthop part is optional. For more details see the sample transport -# configuration file. -# -# NOTE: if you use this feature for accounts not in the UNIX password -# file, then you must update the "local_recipient_maps" setting in -# the main.cf file, otherwise the SMTP server will reject mail for -# non-UNIX accounts with "User unknown in local recipient table". -# -#fallback_transport = lmtp:unix:/file/name -#fallback_transport = cyrus -#fallback_transport = - -# The luser_relay parameter specifies an optional destination address -# for unknown recipients. By default, mail for unknown@$mydestination, -# unknown@[$inet_interfaces] or unknown@[$proxy_interfaces] is returned -# as undeliverable. -# -# The following expansions are done on luser_relay: $user (recipient -# username), $shell (recipient shell), $home (recipient home directory), -# $recipient (full recipient address), $extension (recipient address -# extension), $domain (recipient domain), $local (entire recipient -# localpart), $recipient_delimiter. Specify ${name?value} or -# ${name:value} to expand value only when $name does (does not) exist. -# -# luser_relay works only for the default Postfix local delivery agent. -# -# NOTE: if you use this feature for accounts not in the UNIX password -# file, then you must specify "local_recipient_maps =" (i.e. empty) in -# the main.cf file, otherwise the SMTP server will reject mail for -# non-UNIX accounts with "User unknown in local recipient table". -# -#luser_relay = $user@other.host -#luser_relay = $local@other.host -#luser_relay = admin+$local - -# JUNK MAIL CONTROLS -# -# The controls listed here are only a very small subset. The file -# SMTPD_ACCESS_README provides an overview. - -# The header_checks parameter specifies an optional table with patterns -# that each logical message header is matched against, including -# headers that span multiple physical lines. -# -# By default, these patterns also apply to MIME headers and to the -# headers of attached messages. With older Postfix versions, MIME and -# attached message headers were treated as body text. -# -# For details, see "man header_checks". -# -#header_checks = regexp:/etc/postfix/header_checks - -# FAST ETRN SERVICE -# -# Postfix maintains per-destination logfiles with information about -# deferred mail, so that mail can be flushed quickly with the SMTP -# "ETRN domain.tld" command, or by executing "sendmail -qRdomain.tld". -# See the ETRN_README document for a detailed description. -# -# The fast_flush_domains parameter controls what destinations are -# eligible for this service. By default, they are all domains that -# this server is willing to relay mail to. -# -#fast_flush_domains = $relay_domains - -# SHOW SOFTWARE VERSION OR NOT -# -# The smtpd_banner parameter specifies the text that follows the 220 -# code in the SMTP server's greeting banner. Some people like to see -# the mail version advertised. By default, Postfix shows no version. -# -# You MUST specify $myhostname at the start of the text. That is an -# RFC requirement. Postfix itself does not care. -# -#smtpd_banner = $myhostname ESMTP $mail_name -#smtpd_banner = $myhostname ESMTP $mail_name ($mail_version) -smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU) - - -# PARALLEL DELIVERY TO THE SAME DESTINATION -# -# How many parallel deliveries to the same user or domain? With local -# delivery, it does not make sense to do massively parallel delivery -# to the same user, because mailbox updates must happen sequentially, -# and expensive pipelines in .forward files can cause disasters when -# too many are run at the same time. With SMTP deliveries, 10 -# simultaneous connections to the same domain could be sufficient to -# raise eyebrows. -# -# Each message delivery transport has its XXX_destination_concurrency_limit -# parameter. The default is $default_destination_concurrency_limit for -# most delivery transports. For the local delivery agent the default is 2. - -#local_destination_concurrency_limit = 2 -#default_destination_concurrency_limit = 20 - -# DEBUGGING CONTROL -# -# The debug_peer_level parameter specifies the increment in verbose -# logging level when an SMTP client or server host name or address -# matches a pattern in the debug_peer_list parameter. -# -#debug_peer_level = 2 - -# The debug_peer_list parameter specifies an optional list of domain -# or network patterns, /file/name patterns or type:name tables. When -# an SMTP client or server host name or address matches a pattern, -# increase the verbose logging level by the amount specified in the -# debug_peer_level parameter. -# -#debug_peer_list = 127.0.0.1 -#debug_peer_list = some.domain - -# The debugger_command specifies the external command that is executed -# when a Postfix daemon program is run with the -D option. -# -# Use "command .. & sleep 5" so that the debugger can attach before -# the process marches on. If you use an X-based debugger, be sure to -# set up your XAUTHORITY environment variable before starting Postfix. -# -debugger_command = - PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin - ddd $daemon_directory/$process_name $process_id & sleep 5 - -# If you can't use X, use this to capture the call stack when a -# daemon crashes. The result is in a file in the configuration -# directory, and is named after the process name and the process ID. -# -# debugger_command = -# PATH=/bin:/usr/bin:/usr/local/bin; export PATH; (echo cont; -# echo where) | gdb $daemon_directory/$process_name $process_id 2>&1 -# >$config_directory/$process_name.$process_id.log & sleep 5 -# -# Another possibility is to run gdb under a detached screen session. -# To attach to the screen sesssion, su root and run "screen -r -# " where uniquely matches one of the detached -# sessions (from "screen -list"). -# -# debugger_command = -# PATH=/bin:/usr/bin:/sbin:/usr/sbin; export PATH; screen -# -dmS $process_name gdb $daemon_directory/$process_name -# $process_id & sleep 1 - -# INSTALL-TIME CONFIGURATION INFORMATION -# -# The following parameters are used when installing a new Postfix version. -# -# sendmail_path: The full pathname of the Postfix sendmail command. -# This is the Sendmail-compatible mail posting interface. -# -sendmail_path = /usr/sbin/sendmail - -# newaliases_path: The full pathname of the Postfix newaliases command. -# This is the Sendmail-compatible command to build alias databases. -# -newaliases_path = /usr/bin/newaliases - -# mailq_path: The full pathname of the Postfix mailq command. This -# is the Sendmail-compatible mail queue listing command. -# -mailq_path = /usr/bin/mailq - -# setgid_group: The group for mail submission and queue management -# commands. This must be a group name with a numerical group ID that -# is not shared with other accounts, not even with the Postfix account. -# -setgid_group = postdrop - -# html_directory: The location of the Postfix HTML documentation. -# -html_directory = no - -# manpage_directory: The location of the Postfix on-line manual pages. -# -manpage_directory = /usr/share/man - -# sample_directory: The location of the Postfix sample configuration files. -# This parameter is obsolete as of Postfix 2.1. -# -sample_directory = /usr/share/doc/postfix - -# readme_directory: The location of the Postfix README files. -# -readme_directory = /usr/share/doc/postfix -inet_protocols = ipv4 - -append_dot_mydomain = no -biff = no -smtpd_helo_required = yes -smtpd_recipient_restrictions = permit_mynetworks, - permit_sasl_authenticated, - reject_unauth_destination, - reject_unauth_pipelining, - reject_non_fqdn_recipient -smtpd_sender_restrictions = permit_mynetworks, - reject_sender_login_mismatch, - permit_sasl_authenticated, - reject_unknown_helo_hostname, - reject_unknown_recipient_domain, - reject_unknown_sender_domain -smtpd_client_restrictions = permit_mynetworks, - permit_sasl_authenticated, - reject_unknown_client_hostname - -# Postfix 2.10 requires this option. Postfix < 2.10 ignores this. -# The option is intentionally left empty. -smtpd_relay_restrictions = - -# Maximum size of Message in bytes (50MB) -message_size_limit = 52428800 - -## SASL Auth Settings -smtpd_sasl_auth_enable = yes -smtpd_sasl_local_domain = $myhostname -broken_sasl_auth_clients = yes -## Dovecot Settings for deliver, SASL Auth and virtual transport -smtpd_sasl_type = dovecot -virtual_transport = dovecot -dovecot_destination_recipient_limit = 1 -smtpd_sasl_path = private/auth - -# Virtual delivery settings -virtual_mailbox_base = / -virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual_mailbox_maps.cf -virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual_mailbox_domains.cf -virtual_alias_maps = mysql:/etc/postfix/mysql-virtual_alias_maps.cf -smtpd_sender_login_maps = mysql:/etc/postfix/mysql-virtual_sender_permissions.cf -virtual_uid_maps = static: -virtual_gid_maps = static: - -# Local delivery settings -local_transport = local -alias_maps = $alias_database - -# Default Mailbox size, is set to 0 which means unlimited! -mailbox_size_limit = 0 -virtual_mailbox_limit = 0 - -### TLS settings -### -## TLS for outgoing mails from the server to another server -#smtp_tls_security_level = may -#smtp_tls_note_starttls_offer = yes -## TLS for incoming connections (clients or other mail servers) -#smtpd_tls_security_level = may -#smtpd_tls_cert_file = /etc/ssl/server/.pem -#smtpd_tls_key_file = $smtpd_tls_cert_file -#smtpd_tls_CAfile = /etc/ssl/certs/ca-certificates.crt -#smtpd_tls_loglevel = 1 -#smtpd_tls_received_header = yes -]]> - - - //service[@type='smtp']/general/files[@index=0] - - - - - //service[@type='smtp']/general/commands[@index=3] - - - - - //service[@type='smtp']/general/commands[@index=1] - - //service[@type='smtp']/general/installs[@index=1] - - - //service[@type='smtp']/general/commands[@index=2] - - - - -# SENDING MAIL -# -# The myorigin parameter specifies the domain that locally-posted -# mail appears to come from. The default is to append $myhostname, -# which is fine for small sites. If you run a domain with multiple -# machines, you should (1) change this to $mydomain and (2) set up -# a domain-wide alias database that aliases each user to -# user@that.users.mailhost. -# -# For the sake of consistency between sender and recipient addresses, -# myorigin also specifies the default domain name that is appended -# to recipient addresses that have no @domain part. -# -# Debian GNU/Linux specific: Specifying a file name will cause the -# first line of that file to be used as the name. The Debian default -# is /etc/mailname. -# -#myorigin = /etc/mailname -#myorigin = $myhostname -#myorigin = $mydomain - -# RECEIVING MAIL - -# The inet_interfaces parameter specifies the network interface -# addresses that this mail system receives mail on. By default, -# the software claims all active interfaces on the machine. The -# parameter also controls delivery of mail to user@[ip.address]. -# -# See also the proxy_interfaces parameter, for network addresses that -# are forwarded to us via a proxy or network address translator. -# -# Note: you need to stop/start Postfix when this parameter changes. -# -inet_interfaces = all -#inet_interfaces = $myhostname -#inet_interfaces = $myhostname, localhost - -# The proxy_interfaces parameter specifies the network interface -# addresses that this mail system receives mail on by way of a -# proxy or network address translation unit. This setting extends -# the address list specified with the inet_interfaces parameter. -# -# You must specify your proxy/NAT addresses when your system is a -# backup MX host for other domains, otherwise mail delivery loops -# will happen when the primary MX host is down. -# -#proxy_interfaces = -#proxy_interfaces = 1.2.3.4 - -# The mydestination parameter specifies the list of domains that this -# machine considers itself the final destination for. -# -# These domains are routed to the delivery agent specified with the -# local_transport parameter setting. By default, that is the UNIX -# compatible delivery agent that lookups all recipients in /etc/passwd -# and /etc/aliases or their equivalent. -# -# The default is $myhostname + localhost.$mydomain. On a mail domain -# gateway, you should also include $mydomain. -# -# Do not specify the names of virtual domains - those domains are -# specified elsewhere (see VIRTUAL_README). -# -# Do not specify the names of domains that this machine is backup MX -# host for. Specify those names via the relay_domains settings for -# the SMTP server, or use permit_mx_backup if you are lazy (see -# STANDARD_CONFIGURATION_README). -# -# The local machine is always the final destination for mail addressed -# to user@[the.net.work.address] of an interface that the mail system -# receives mail on (see the inet_interfaces parameter). -# -# Specify a list of host or domain names, /file/name or type:table -# patterns, separated by commas and/or whitespace. A /file/name -# pattern is replaced by its contents; a type:table is matched when -# a name matches a lookup key (the right-hand side is ignored). -# Continue long lines by starting the next line with whitespace. -# -# See also below, section "REJECTING MAIL FOR UNKNOWN LOCAL USERS". -# -#mydestination = $myhostname, localhost.$mydomain, localhost -mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain -#mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain, -# mail.$mydomain, www.$mydomain, ftp.$mydomain - -# REJECTING MAIL FOR UNKNOWN LOCAL USERS -# -# The local_recipient_maps parameter specifies optional lookup tables -# with all names or addresses of users that are local with respect -# to $mydestination, $inet_interfaces or $proxy_interfaces. -# -# If this parameter is defined, then the SMTP server will reject -# mail for unknown local users. This parameter is defined by default. -# -# To turn off local recipient checking in the SMTP server, specify -# local_recipient_maps = (i.e. empty). -# -# The default setting assumes that you use the default Postfix local -# delivery agent for local delivery. You need to update the -# local_recipient_maps setting if: -# -# - You define $mydestination domain recipients in files other than -# /etc/passwd, /etc/aliases, or the $virtual_alias_maps files. -# For example, you define $mydestination domain recipients in -# the $virtual_mailbox_maps files. -# -# - You redefine the local delivery agent in master.cf. -# -# - You redefine the "local_transport" setting in main.cf. -# -# - You use the "luser_relay", "mailbox_transport", or "fallback_transport" -# feature of the Postfix local delivery agent (see local(8)). -# -# Details are described in the LOCAL_RECIPIENT_README file. -# -# Beware: if the Postfix SMTP server runs chrooted, you probably have -# to access the passwd file via the proxymap service, in order to -# overcome chroot restrictions. The alternative, having a copy of -# the system passwd file in the chroot jail is just not practical. -# -# The right-hand side of the lookup tables is conveniently ignored. -# In the left-hand side, specify a bare username, an @domain.tld -# wild-card, or specify a user@domain.tld address. -# -#local_recipient_maps = unix:passwd.byname $alias_maps -#local_recipient_maps = proxy:unix:passwd.byname $alias_maps -#local_recipient_maps = - -# The unknown_local_recipient_reject_code specifies the SMTP server -# response code when a recipient domain matches $mydestination or -# ${proxy,inet}_interfaces, while $local_recipient_maps is non-empty -# and the recipient address or address local-part is not found. -# -# The default setting is 550 (reject mail) but it is safer to start -# with 450 (try again later) until you are certain that your -# local_recipient_maps settings are OK. -# -unknown_local_recipient_reject_code = 550 - -# TRUST AND RELAY CONTROL - -# The mynetworks parameter specifies the list of "trusted" SMTP -# clients that have more privileges than "strangers". -# -# In particular, "trusted" SMTP clients are allowed to relay mail -# through Postfix. See the smtpd_recipient_restrictions parameter -# in postconf(5). -# -# You can specify the list of "trusted" network addresses by hand -# or you can let Postfix do it for you (which is the default). -# -# By default (mynetworks_style = subnet), Postfix "trusts" SMTP -# clients in the same IP subnetworks as the local machine. -# On Linux, this does works correctly only with interfaces specified -# with the "ifconfig" command. -# -# Specify "mynetworks_style = class" when Postfix should "trust" SMTP -# clients in the same IP class A/B/C networks as the local machine. -# Don't do this with a dialup site - it would cause Postfix to "trust" -# your entire provider's network. Instead, specify an explicit -# mynetworks list by hand, as described below. -# -# Specify "mynetworks_style = host" when Postfix should "trust" -# only the local machine. -# -#mynetworks_style = class -#mynetworks_style = subnet -#mynetworks_style = host - -# Alternatively, you can specify the mynetworks list by hand, in -# which case Postfix ignores the mynetworks_style setting. -# -# Specify an explicit list of network/netmask patterns, where the -# mask specifies the number of bits in the network part of a host -# address. -# -# You can also specify the absolute pathname of a pattern file instead -# of listing the patterns here. Specify type:table for table-based lookups -# (the value on the table right-hand side is not used). -# -#mynetworks = 168.100.189.0/28, 127.0.0.0/8 -#mynetworks = $config_directory/mynetworks -#mynetworks = hash:/etc/postfix/network_table -mynetworks = 127.0.0.0/8 - -# The relay_domains parameter restricts what destinations this system will -# relay mail to. See the smtpd_recipient_restrictions description in -# postconf(5) for detailed information. -# -# By default, Postfix relays mail -# - from "trusted" clients (IP address matches $mynetworks) to any destination, -# - from "untrusted" clients to destinations that match $relay_domains or -# subdomains thereof, except addresses with sender-specified routing. -# The default relay_domains value is $mydestination. -# -# In addition to the above, the Postfix SMTP server by default accepts mail -# that Postfix is final destination for: -# - destinations that match $inet_interfaces or $proxy_interfaces, -# - destinations that match $mydestination -# - destinations that match $virtual_alias_domains, -# - destinations that match $virtual_mailbox_domains. -# These destinations do not need to be listed in $relay_domains. -# -# Specify a list of hosts or domains, /file/name patterns or type:name -# lookup tables, separated by commas and/or whitespace. Continue -# long lines by starting the next line with whitespace. A file name -# is replaced by its contents; a type:name table is matched when a -# (parent) domain appears as lookup key. -# -# NOTE: Postfix will not automatically forward mail for domains that -# list this system as their primary or backup MX host. See the -# permit_mx_backup restriction description in postconf(5). -# -#relay_domains = $mydestination - -# INTERNET OR INTRANET - -# The relayhost parameter specifies the default host to send mail to -# when no entry is matched in the optional transport(5) table. When -# no relayhost is given, mail is routed directly to the destination. -# -# On an intranet, specify the organizational domain name. If your -# internal DNS uses no MX records, specify the name of the intranet -# gateway host instead. -# -# In the case of SMTP, specify a domain, host, host:port, [host]:port, -# [address] or [address]:port; the form [host] turns off MX lookups. -# -# If you're connected via UUCP, see also the default_transport parameter. -# -#relayhost = $mydomain -#relayhost = [gateway.my.domain] -#relayhost = [mailserver.isp.tld] -#relayhost = uucphost -#relayhost = [an.ip.add.ress] - -# REJECTING UNKNOWN RELAY USERS -# -# The relay_recipient_maps parameter specifies optional lookup tables -# with all addresses in the domains that match $relay_domains. -# -# If this parameter is defined, then the SMTP server will reject -# mail for unknown relay users. This feature is off by default. -# -# The right-hand side of the lookup tables is conveniently ignored. -# In the left-hand side, specify an @domain.tld wild-card, or specify -# a user@domain.tld address. -# -#relay_recipient_maps = hash:/etc/postfix/relay_recipients - -# INPUT RATE CONTROL -# -# The in_flow_delay configuration parameter implements mail input -# flow control. This feature is turned on by default, although it -# still needs further development (it's disabled on SCO UNIX due -# to an SCO bug). -# -# A Postfix process will pause for $in_flow_delay seconds before -# accepting a new message, when the message arrival rate exceeds the -# message delivery rate. With the default 100 SMTP server process -# limit, this limits the mail inflow to 100 messages a second more -# than the number of messages delivered per second. -# -# Specify 0 to disable the feature. Valid delays are 0..10. -# -#in_flow_delay = 1s - -# ADDRESS REWRITING -# -# The ADDRESS_REWRITING_README document gives information about -# address masquerading or other forms of address rewriting including -# username->Firstname.Lastname mapping. - -# ADDRESS REDIRECTION (VIRTUAL DOMAIN) -# -# The VIRTUAL_README document gives information about the many forms -# of domain hosting that Postfix supports. - -# "USER HAS MOVED" BOUNCE MESSAGES -# -# See the discussion in the ADDRESS_REWRITING_README document. - -# TRANSPORT MAP -# -# See the discussion in the ADDRESS_REWRITING_README document. - -# ALIAS DATABASE -# -# The alias_maps parameter specifies the list of alias databases used -# by the local delivery agent. The default list is system dependent. -# -# On systems with NIS, the default is to search the local alias -# database, then the NIS alias database. See aliases(5) for syntax -# details. -# -# If you change the alias database, run "postalias /etc/aliases" (or -# wherever your system stores the mail alias file), or simply run -# "newaliases" to build the necessary DBM or DB file. -# -# It will take a minute or so before changes become visible. Use -# "postfix reload" to eliminate the delay. -# -#alias_maps = dbm:/etc/aliases -#alias_maps = hash:/etc/aliases -#alias_maps = hash:/etc/aliases, nis:mail.aliases -#alias_maps = netinfo:/aliases - -# The alias_database parameter specifies the alias database(s) that -# are built with "newaliases" or "sendmail -bi". This is a separate -# configuration parameter, because alias_maps (see above) may specify -# tables that are not necessarily all under control by Postfix. -# -#alias_database = dbm:/etc/aliases -#alias_database = dbm:/etc/mail/aliases -#alias_database = hash:/etc/aliases -#alias_database = hash:/etc/aliases, hash:/opt/majordomo/aliases - -# ADDRESS EXTENSIONS (e.g., user+foo) -# -# The recipient_delimiter parameter specifies the separator between -# user names and address extensions (user+foo). See canonical(5), -# local(8), relocated(5) and virtual(5) for the effects this has on -# aliases, canonical, virtual, relocated and .forward file lookups. -# Basically, the software tries user+foo and .forward+foo before -# trying user and .forward. -# -#recipient_delimiter = + - -# DELIVERY TO MAILBOX -# -# The home_mailbox parameter specifies the optional pathname of a -# mailbox file relative to a user's home directory. The default -# mailbox file is /var/spool/mail/user or /var/mail/user. Specify -# "Maildir/" for qmail-style delivery (the / is required). -# -#home_mailbox = Mailbox -#home_mailbox = Maildir/ - -# The mail_spool_directory parameter specifies the directory where -# UNIX-style mailboxes are kept. The default setting depends on the -# system type. -# -#mail_spool_directory = /var/mail -#mail_spool_directory = /var/spool/mail - -# The mailbox_command parameter specifies the optional external -# command to use instead of mailbox delivery. The command is run as -# the recipient with proper HOME, SHELL and LOGNAME environment settings. -# Exception: delivery for root is done as $default_user. -# -# Other environment variables of interest: USER (recipient username), -# EXTENSION (address extension), DOMAIN (domain part of address), -# and LOCAL (the address localpart). -# -# Unlike other Postfix configuration parameters, the mailbox_command -# parameter is not subjected to $parameter substitutions. This is to -# make it easier to specify shell syntax (see example below). -# -# Avoid shell meta characters because they will force Postfix to run -# an expensive shell process. Procmail alone is expensive enough. -# -# IF YOU USE THIS TO DELIVER MAIL SYSTEM-WIDE, YOU MUST SET UP AN -# ALIAS THAT FORWARDS MAIL FOR ROOT TO A REAL USER. -# -#mailbox_command = /usr/bin/procmail -#mailbox_command = /usr/bin/procmail -a "$EXTENSION" - -# The mailbox_transport specifies the optional transport in master.cf -# to use after processing aliases and .forward files. This parameter -# has precedence over the mailbox_command, fallback_transport and -# luser_relay parameters. -# -# Specify a string of the form transport:nexthop, where transport is -# the name of a mail delivery transport defined in master.cf. The -# :nexthop part is optional. For more details see the sample transport -# configuration file. -# -# NOTE: if you use this feature for accounts not in the UNIX password -# file, then you must update the "local_recipient_maps" setting in -# the main.cf file, otherwise the SMTP server will reject mail for -# non-UNIX accounts with "User unknown in local recipient table". -# -# Cyrus IMAP over LMTP. Specify ``lmtpunix cmd="lmtpd" -# listen="/var/imap/socket/lmtp" prefork=0'' in cyrus.conf. -#mailbox_transport = lmtp:unix:/var/imap/socket/lmtp -# -# Cyrus IMAP via command line. Uncomment the "cyrus...pipe" and -# subsequent line in master.cf. -#mailbox_transport = cyrus - -# The fallback_transport specifies the optional transport in master.cf -# to use for recipients that are not found in the UNIX passwd database. -# This parameter has precedence over the luser_relay parameter. -# -# Specify a string of the form transport:nexthop, where transport is -# the name of a mail delivery transport defined in master.cf. The -# :nexthop part is optional. For more details see the sample transport -# configuration file. -# -# NOTE: if you use this feature for accounts not in the UNIX password -# file, then you must update the "local_recipient_maps" setting in -# the main.cf file, otherwise the SMTP server will reject mail for -# non-UNIX accounts with "User unknown in local recipient table". -# -#fallback_transport = lmtp:unix:/file/name -#fallback_transport = cyrus -#fallback_transport = - -# The luser_relay parameter specifies an optional destination address -# for unknown recipients. By default, mail for unknown@$mydestination, -# unknown@[$inet_interfaces] or unknown@[$proxy_interfaces] is returned -# as undeliverable. -# -# The following expansions are done on luser_relay: $user (recipient -# username), $shell (recipient shell), $home (recipient home directory), -# $recipient (full recipient address), $extension (recipient address -# extension), $domain (recipient domain), $local (entire recipient -# localpart), $recipient_delimiter. Specify ${name?value} or -# ${name:value} to expand value only when $name does (does not) exist. -# -# luser_relay works only for the default Postfix local delivery agent. -# -# NOTE: if you use this feature for accounts not in the UNIX password -# file, then you must specify "local_recipient_maps =" (i.e. empty) in -# the main.cf file, otherwise the SMTP server will reject mail for -# non-UNIX accounts with "User unknown in local recipient table". -# -#luser_relay = $user@other.host -#luser_relay = $local@other.host -#luser_relay = admin+$local - -# JUNK MAIL CONTROLS -# -# The controls listed here are only a very small subset. The file -# SMTPD_ACCESS_README provides an overview. - -# The header_checks parameter specifies an optional table with patterns -# that each logical message header is matched against, including -# headers that span multiple physical lines. -# -# By default, these patterns also apply to MIME headers and to the -# headers of attached messages. With older Postfix versions, MIME and -# attached message headers were treated as body text. -# -# For details, see "man header_checks". -# -#header_checks = regexp:/etc/postfix/header_checks - -# FAST ETRN SERVICE -# -# Postfix maintains per-destination logfiles with information about -# deferred mail, so that mail can be flushed quickly with the SMTP -# "ETRN domain.tld" command, or by executing "sendmail -qRdomain.tld". -# See the ETRN_README document for a detailed description. -# -# The fast_flush_domains parameter controls what destinations are -# eligible for this service. By default, they are all domains that -# this server is willing to relay mail to. -# -#fast_flush_domains = $relay_domains - -# SHOW SOFTWARE VERSION OR NOT -# -# The smtpd_banner parameter specifies the text that follows the 220 -# code in the SMTP server's greeting banner. Some people like to see -# the mail version advertised. By default, Postfix shows no version. -# -# You MUST specify $myhostname at the start of the text. That is an -# RFC requirement. Postfix itself does not care. -# -#smtpd_banner = $myhostname ESMTP $mail_name -#smtpd_banner = $myhostname ESMTP $mail_name ($mail_version) -smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU) - - -# PARALLEL DELIVERY TO THE SAME DESTINATION -# -# How many parallel deliveries to the same user or domain? With local -# delivery, it does not make sense to do massively parallel delivery -# to the same user, because mailbox updates must happen sequentially, -# and expensive pipelines in .forward files can cause disasters when -# too many are run at the same time. With SMTP deliveries, 10 -# simultaneous connections to the same domain could be sufficient to -# raise eyebrows. -# -# Each message delivery transport has its XXX_destination_concurrency_limit -# parameter. The default is $default_destination_concurrency_limit for -# most delivery transports. For the local delivery agent the default is 2. - -#local_destination_concurrency_limit = 2 -#default_destination_concurrency_limit = 20 - -# DEBUGGING CONTROL -# -# The debug_peer_level parameter specifies the increment in verbose -# logging level when an SMTP client or server host name or address -# matches a pattern in the debug_peer_list parameter. -# -#debug_peer_level = 2 - -# The debug_peer_list parameter specifies an optional list of domain -# or network patterns, /file/name patterns or type:name tables. When -# an SMTP client or server host name or address matches a pattern, -# increase the verbose logging level by the amount specified in the -# debug_peer_level parameter. -# -#debug_peer_list = 127.0.0.1 -#debug_peer_list = some.domain - -# The debugger_command specifies the external command that is executed -# when a Postfix daemon program is run with the -D option. -# -# Use "command .. & sleep 5" so that the debugger can attach before -# the process marches on. If you use an X-based debugger, be sure to -# set up your XAUTHORITY environment variable before starting Postfix. -# -debugger_command = - PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin - ddd $daemon_directory/$process_name $process_id & sleep 5 - -# If you can't use X, use this to capture the call stack when a -# daemon crashes. The result is in a file in the configuration -# directory, and is named after the process name and the process ID. -# -# debugger_command = -# PATH=/bin:/usr/bin:/usr/local/bin; export PATH; (echo cont; -# echo where) | gdb $daemon_directory/$process_name $process_id 2>&1 -# >$config_directory/$process_name.$process_id.log & sleep 5 -# -# Another possibility is to run gdb under a detached screen session. -# To attach to the screen sesssion, su root and run "screen -r -# " where uniquely matches one of the detached -# sessions (from "screen -list"). -# -# debugger_command = -# PATH=/bin:/usr/bin:/sbin:/usr/sbin; export PATH; screen -# -dmS $process_name gdb $daemon_directory/$process_name -# $process_id & sleep 1 - -# INSTALL-TIME CONFIGURATION INFORMATION -# -# The following parameters are used when installing a new Postfix version. -# -# sendmail_path: The full pathname of the Postfix sendmail command. -# This is the Sendmail-compatible mail posting interface. -# -sendmail_path = /usr/sbin/sendmail - -# newaliases_path: The full pathname of the Postfix newaliases command. -# This is the Sendmail-compatible command to build alias databases. -# -newaliases_path = /usr/bin/newaliases - -# mailq_path: The full pathname of the Postfix mailq command. This -# is the Sendmail-compatible mail queue listing command. -# -mailq_path = /usr/bin/mailq - -# setgid_group: The group for mail submission and queue management -# commands. This must be a group name with a numerical group ID that -# is not shared with other accounts, not even with the Postfix account. -# -setgid_group = postdrop - -# html_directory: The location of the Postfix HTML documentation. -# -html_directory = no - -# manpage_directory: The location of the Postfix on-line manual pages. -# -manpage_directory = /usr/share/man - -# sample_directory: The location of the Postfix sample configuration files. -# This parameter is obsolete as of Postfix 2.1. -# -sample_directory = /usr/share/doc/postfix - -# readme_directory: The location of the Postfix README files. -# -readme_directory = /usr/share/doc/postfix -inet_protocols = ipv4 - -append_dot_mydomain = no -biff = no -smtpd_helo_required = yes -smtpd_recipient_restrictions = permit_mynetworks, - permit_sasl_authenticated, - reject_unauth_destination, - reject_unauth_pipelining, - reject_non_fqdn_recipient -smtpd_sender_restrictions = permit_mynetworks, - reject_sender_login_mismatch, - permit_sasl_authenticated, - reject_unknown_helo_hostname, - reject_unknown_recipient_domain, - reject_unknown_sender_domain -smtpd_client_restrictions = permit_mynetworks, - permit_sasl_authenticated, - reject_unknown_client_hostname - -# Postfix 2.10 requires this option. Postfix < 2.10 ignores this. -# The option is intentionally left empty. -smtpd_relay_restrictions = - -# Maximum size of Message in bytes (50MB) -message_size_limit = 52428800 - -## SASL Auth Settings -smtpd_sasl_auth_enable = yes -smtpd_sasl_local_domain = $myhostname -broken_sasl_auth_clients = yes - -# Virtual delivery settings -virtual_mailbox_base = / -virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual_mailbox_maps.cf -virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual_mailbox_domains.cf -virtual_alias_maps = mysql:/etc/postfix/mysql-virtual_alias_maps.cf -smtpd_sender_login_maps = mysql:/etc/postfix/mysql-virtual_sender_permissions.cf -virtual_uid_maps = static: -virtual_gid_maps = static: - -# Local delivery settings -local_transport = local -alias_maps = $alias_database - -# Default Mailbox size, is set to 0 which means unlimited! -mailbox_size_limit = 0 -virtual_mailbox_limit = 0 - -### TLS settings -### -## TLS for outgoing mails from the server to another server -#smtp_tls_security_level = may -#smtp_tls_note_starttls_offer = yes -## TLS for email client -#smtpd_tls_security_level = may -#smtpd_tls_cert_file = /etc/ssl/server/.pem -#smtpd_tls_key_file = $smtpd_tls_cert_file -#smtpd_tls_CAfile = /etc/ssl/certs/ca-certificates.crt -#smtpd_tls_loglevel = 1 -#smtpd_tls_received_header = yes -]]> - - - //service[@type='smtp']/general/files[@index=0] - - -sql_user: -sql_passwd: -sql_database: -sql_select: SELECT password FROM mail_users WHERE username='%u@%r' OR email='%u@%r' -]]> - - - //service[@type='smtp']/general/commands[@index=3] - - - - - - - - - - - - - to select which instance is used (an alternative -# to -c ). The instance name is also added to Dovecot processes -# in ps output. -#instance_name = dovecot - -# Greeting message for clients. -#login_greeting = Dovecot ready. - -# Space separated list of trusted network ranges. Connections from these -# IPs are allowed to override their IP addresses and ports (for logging and -# for authentication checks). disable_plaintext_auth is also ignored for -# these networks. Typically you'd specify your IMAP proxy servers here. -#login_trusted_networks = - -# Sepace separated list of login access check sockets (e.g. tcpwrap) -#login_access_sockets = - -# With proxy_maybe=yes if proxy destination matches any of these IPs, don't do -# proxying. This isn't necessary normally, but may be useful if the destination -# IP is e.g. a load balancer's IP. -#auth_proxy_self = - -# Show more verbose process titles (in ps). Currently shows user name and -# IP address. Useful for seeing who are actually using the IMAP processes -# (eg. shared mailboxes or if same uid is used for multiple accounts). -#verbose_proctitle = no - -# Should all processes be killed when Dovecot master process shuts down. -# Setting this to "no" means that Dovecot can be upgraded without -# forcing existing client connections to close (although that could also be -# a problem if the upgrade is e.g. because of a security fix). -#shutdown_clients = yes - -# If non-zero, run mail commands via this many connections to doveadm server, -# instead of running them directly in the same process. -#doveadm_worker_count = 0 -# UNIX socket or host:port used for connecting to doveadm server -#doveadm_socket_path = doveadm-server - -# Space separated list of environment variables that are preserved on Dovecot -# startup and passed down to all of its child processes. You can also give -# key=value pairs to always set specific settings. -#import_environment = TZ - -## -## Dictionary server settings -## - -# Dictionary can be used to store key=value lists. This is used by several -# plugins. The dictionary can be accessed either directly or though a -# dictionary server. The following dict block maps dictionary names to URIs -# when the server is used. These can then be referenced using URIs in format -# "proxy::". - -dict { - #quota = mysql:/etc/dovecot/dovecot-dict-sql.conf.ext - #expire = sqlite:/etc/dovecot/dovecot-dict-sql.conf.ext -} - -# Most of the actual configuration gets included below. The filenames are -# first sorted by their ASCII value and parsed in that order. The 00-prefixes -# in filenames are intended to make it easier to understand the ordering. -!include conf.d/*.conf - -# A config file can also tried to be included without giving an error if -# it's not found: -!include_try local.conf -]]> - - - - dbname= user= password= - -# Default password scheme. -# -# List of supported schemes is in -# http://wiki2.dovecot.org/Authentication/PasswordSchemes -# -default_pass_scheme = CRYPT - -# passdb query to retrieve the password. It can return fields: -# password - The user's password. This field must be returned. -# user - user@domain from the database. Needed with case-insensitive lookups. -# username and domain - An alternative way to represent the "user" field. -# -# The "user" field is often necessary with case-insensitive lookups to avoid -# e.g. "name" and "nAme" logins creating two different mail directories. If -# your user and domain names are in separate fields, you can return "username" -# and "domain" fields instead of "user". -# -# The query can also return other fields which have a special meaning, see -# http://wiki2.dovecot.org/PasswordDatabase/ExtraFields -# -# Commonly used available substitutions (see http://wiki2.dovecot.org/Variables -# for full list): -# %u = entire user@domain -# %n = user part of user@domain -# %d = domain part of user@domain -# -# Note that these can be used only as input to SQL query. If the query outputs -# any of these substitutions, they're not touched. Otherwise it would be -# difficult to have eg. usernames containing '%' characters. -# -# Example: -# password_query = SELECT userid AS user, pw AS password \ -# FROM users WHERE userid = '%u' AND active = 'Y' -# -#password_query = \ -# SELECT username, domain, password \ -# FROM users WHERE username = '%n' AND domain = '%d' - -# userdb query to retrieve the user information. It can return fields: -# uid - System UID (overrides mail_uid setting) -# gid - System GID (overrides mail_gid setting) -# home - Home directory -# mail - Mail location (overrides mail_location setting) -# -# None of these are strictly required. If you use a single UID and GID, and -# home or mail directory fits to a template string, you could use userdb static -# instead. For a list of all fields that can be returned, see -# http://wiki2.dovecot.org/UserDatabase/ExtraFields -# -# Examples: -# user_query = SELECT home, uid, gid FROM users WHERE userid = '%u' -# user_query = SELECT dir AS home, user AS uid, group AS gid FROM users where userid = '%u' -# user_query = SELECT home, 501 AS uid, 501 AS gid FROM users WHERE userid = '%u' -# -#user_query = \ -# SELECT home, uid, gid \ -# FROM users WHERE username = '%n' AND domain = '%d' -user_query = SELECT CONCAT(homedir, maildir) AS home, CONCAT('maildir:', homedir, maildir) AS mail, uid, gid, CONCAT('*:storage=', quota, 'M') as quota_rule FROM mail_users WHERE (username = '%u' OR email = '%u') - -# If you wish to avoid two SQL lookups (passdb + userdb), you can use -# userdb prefetch instead of userdb sql in dovecot.conf. In that case you'll -# also have to return userdb fields in password_query prefixed with "userdb_" -# string. For example: -#password_query = \ -# SELECT userid AS user, password, \ -# home AS userdb_home, uid AS userdb_uid, gid AS userdb_gid \ -# FROM users WHERE userid = '%u' -password_query = SELECT username AS user, password_enc AS password, CONCAT(homedir, maildir) AS userdb_home, uid AS userdb_uid, gid AS userdb_gid, CONCAT('maildir:', homedir, maildir) AS userdb_mail, CONCAT('*:storage=', quota, 'M') as userdb_quota_rule FROM mail_users WHERE (username = '%u' OR email = '%u') AND ((imap = 1 AND '%Ls' = 'imap') OR (pop3 = 1 AND '%Ls' = 'pop3') OR '%Ls' = 'smtp' OR '%Ls' = 'sieve') - -# Query to get a list of all usernames. -#iterate_query = SELECT username AS user FROM users -]]> - - - - to characters. For example "#@/@" means -# that '#' and '/' characters are translated to '@'. -#auth_username_translation = - -# Username formatting before it's looked up from databases. You can use -# the standard variables here, eg. %Lu would lowercase the username, %n would -# drop away the domain if it was given, or "%n-AT-%d" would change the '@' into -# "-AT-". This translation is done after auth_username_translation changes. -#auth_username_format = %Lu - -# If you want to allow master users to log in by specifying the master -# username within the normal username string (ie. not using SASL mechanism's -# support for it), you can specify the separator character here. The format -# is then . UW-IMAP uses "*" as the -# separator, so that could be a good choice. -#auth_master_user_separator = - -# Username to use for users logging in with ANONYMOUS SASL mechanism -#auth_anonymous_username = anonymous - -# Maximum number of dovecot-auth worker processes. They're used to execute -# blocking passdb and userdb queries (eg. MySQL and PAM). They're -# automatically created and destroyed as needed. -#auth_worker_max_count = 30 - -# Host name to use in GSSAPI principal names. The default is to use the -# name returned by gethostname(). Use "$ALL" (with quotes) to allow all keytab -# entries. -#auth_gssapi_hostname = - -# Kerberos keytab to use for the GSSAPI mechanism. Will use the system -# default (usually /etc/krb5.keytab) if not specified. You may need to change -# the auth service to run as root to be able to read this file. -#auth_krb5_keytab = - -# Do NTLM and GSS-SPNEGO authentication using Samba's winbind daemon and -# ntlm_auth helper. -#auth_use_winbind = no - -# Path for Samba's ntlm_auth helper binary. -#auth_winbind_helper_path = /usr/bin/ntlm_auth - -# Time to delay before replying to failed authentications. -#auth_failure_delay = 2 secs - -# Require a valid SSL client certificate or the authentication fails. -#auth_ssl_require_client_cert = no - -# Take the username from client's SSL certificate, using -# X509_NAME_get_text_by_NID() which returns the subject's DN's -# CommonName. -#auth_ssl_username_from_cert = no - -# Space separated list of wanted authentication mechanisms: -# plain login digest-md5 cram-md5 ntlm rpa apop anonymous gssapi otp skey -# gss-spnego -# NOTE: See also disable_plaintext_auth setting. -auth_mechanisms = plain login - -## -## Password and user databases -## - -# -# Password database is used to verify user's password (and nothing more). -# You can have multiple passdbs and userdbs. This is useful if you want to -# allow both system users (/etc/passwd) and virtual users to login without -# duplicating the system users into virtual database. -# -# -# -# User database specifies where mails are located and what user/group IDs -# own them. For single-UID configuration use "static" userdb. -# -# - -#!include auth-deny.conf.ext -#!include auth-master.conf.ext - -#!include auth-system.conf.ext -!include auth-sql.conf.ext -#!include auth-ldap.conf.ext -#!include auth-passwdfile.conf.ext -#!include auth-checkpassword.conf.ext -#!include auth-vpopmail.conf.ext -#!include auth-static.conf.ext -]]> - - - - -# -mail_location = mbox:~/mail:INBOX=/var/mail/%u - -# If you need to set multiple mailbox locations or want to change default -# namespace settings, you can do it by defining namespace sections. -# -# You can have private, shared and public namespaces. Private namespaces -# are for user's personal mails. Shared namespaces are for accessing other -# users' mailboxes that have been shared. Public namespaces are for shared -# mailboxes that are managed by sysadmin. If you create any shared or public -# namespaces you'll typically want to enable ACL plugin also, otherwise all -# users can access all the shared mailboxes, assuming they have permissions -# on filesystem level to do so. -namespace inbox { - # Namespace type: private, shared or public - #type = private - - # Hierarchy separator to use. You should use the same separator for all - # namespaces or some clients get confused. '/' is usually a good one. - # The default however depends on the underlying mail storage format. - #separator = - - # Prefix required to access this namespace. This needs to be different for - # all namespaces. For example "Public/". - #prefix = - - # Physical location of the mailbox. This is in same format as - # mail_location, which is also the default for it. - #location = - - # There can be only one INBOX, and this setting defines which namespace - # has it. - inbox = yes - - # If namespace is hidden, it's not advertised to clients via NAMESPACE - # extension. You'll most likely also want to set list=no. This is mostly - # useful when converting from another server with different namespaces which - # you want to deprecate but still keep working. For example you can create - # hidden namespaces with prefixes "~/mail/", "~%u/mail/" and "mail/". - #hidden = no - - # Show the mailboxes under this namespace with LIST command. This makes the - # namespace visible for clients that don't support NAMESPACE extension. - # "children" value lists child mailboxes, but hides the namespace prefix. - #list = yes - - # Namespace handles its own subscriptions. If set to "no", the parent - # namespace handles them (empty prefix should always have this as "yes") - #subscriptions = yes -} - -# Example shared namespace configuration -#namespace { - #type = shared - #separator = / - - # Mailboxes are visible under "shared/user@domain/" - # %%n, %%d and %%u are expanded to the destination user. - #prefix = shared/%%u/ - - # Mail location for other users' mailboxes. Note that %variables and ~/ - # expands to the logged in user's data. %%n, %%d, %%u and %%h expand to the - # destination user's data. - #location = maildir:%%h/Maildir:INDEX=~/Maildir/shared/%%u - - # Use the default namespace for saving subscriptions. - #subscriptions = no - - # List the shared/ namespace only if there are visible shared mailboxes. - #list = children -#} -# Should shared INBOX be visible as "shared/user" or "shared/user/INBOX"? -#mail_shared_explicit_inbox = yes - -# System user and group used to access mails. If you use multiple, userdb -# can override these by returning uid or gid fields. You can use either numbers -# or names. -#mail_uid = -#mail_gid = - -# Group to enable temporarily for privileged operations. Currently this is -# used only with INBOX when either its initial creation or dotlocking fails. -# Typically this is set to "mail" to give access to /var/mail. -#mail_privileged_group = - -# Grant access to these supplementary groups for mail processes. Typically -# these are used to set up access to shared mailboxes. Note that it may be -# dangerous to set these if users can create symlinks (e.g. if "mail" group is -# set here, ln -s /var/mail ~/mail/var could allow a user to delete others' -# mailboxes, or ln -s /secret/shared/box ~/mail/mybox would allow reading it). -mail_access_groups = vmail - -# Allow full filesystem access to clients. There's no access checks other than -# what the operating system does for the active UID/GID. It works with both -# maildir and mboxes, allowing you to prefix mailboxes names with eg. /path/ -# or ~user/. -#mail_full_filesystem_access = no - -## -## Mail processes -## - -# Don't use mmap() at all. This is required if you store indexes to shared -# filesystems (NFS or clustered filesystem). -#mmap_disable = no - -# Rely on O_EXCL to work when creating dotlock files. NFS supports O_EXCL -# since version 3, so this should be safe to use nowadays by default. -#dotlock_use_excl = yes - -# When to use fsync() or fdatasync() calls: -# optimized (default): Whenever necessary to avoid losing important data -# always: Useful with e.g. NFS when write()s are delayed -# never: Never use it (best performance, but crashes can lose data) -#mail_fsync = optimized - -# Mail storage exists in NFS. Set this to yes to make Dovecot flush NFS caches -# whenever needed. If you're using only a single mail server this isn't needed. -#mail_nfs_storage = no -# Mail index files also exist in NFS. Setting this to yes requires -# mmap_disable=yes and fsync_disable=no. -#mail_nfs_index = no - -# Locking method for index files. Alternatives are fcntl, flock and dotlock. -# Dotlocking uses some tricks which may create more disk I/O than other locking -# methods. NFS users: flock doesn't work, remember to change mmap_disable. -#lock_method = fcntl - -# Directory in which LDA/LMTP temporarily stores incoming mails >128 kB. -#mail_temp_dir = /tmp - -# Valid UID range for users, defaults to 500 and above. This is mostly -# to make sure that users can't log in as daemons or other system users. -# Note that denying root logins is hardcoded to dovecot binary and can't -# be done even if first_valid_uid is set to 0. -#first_valid_uid = 500 -#last_valid_uid = 0 - -# Valid GID range for users, defaults to non-root/wheel. Users having -# non-valid GID as primary group ID aren't allowed to log in. If user -# belongs to supplementary groups with non-valid GIDs, those groups are -# not set. -#first_valid_gid = 1 -#last_valid_gid = 0 - -# Maximum allowed length for mail keyword name. It's only forced when trying -# to create new keywords. -#mail_max_keyword_length = 50 - -# ':' separated list of directories under which chrooting is allowed for mail -# processes (ie. /var/mail will allow chrooting to /var/mail/foo/bar too). -# This setting doesn't affect login_chroot, mail_chroot or auth chroot -# settings. If this setting is empty, "/./" in home dirs are ignored. -# WARNING: Never add directories here which local users can modify, that -# may lead to root exploit. Usually this should be done only if you don't -# allow shell access for users. -#valid_chroot_dirs = - -# Default chroot directory for mail processes. This can be overridden for -# specific users in user database by giving /./ in user's home directory -# (eg. /home/./user chroots into /home). Note that usually there is no real -# need to do chrooting, Dovecot doesn't allow users to access files outside -# their mail directory anyway. If your home directories are prefixed with -# the chroot directory, append "/." to mail_chroot. -#mail_chroot = - -# UNIX socket path to master authentication server to find users. -# This is used by imap (for shared users) and lda. -#auth_socket_path = /var/run/dovecot/auth-userdb - -# Directory where to look up mail plugins. -#mail_plugin_dir = /usr/lib/dovecot/modules - -# Space separated list of plugins to load for all services. Plugins specific to -# IMAP, LDA, etc. are added to this list in their own .conf files. -#mail_plugins = - -## -## Mailbox handling optimizations -## - -# The minimum number of mails in a mailbox before updates are done to cache -# file. This allows optimizing Dovecot's behavior to do less disk writes at -# the cost of more disk reads. -#mail_cache_min_mail_count = 0 - -# When IDLE command is running, mailbox is checked once in a while to see if -# there are any new mails or other changes. This setting defines the minimum -# time to wait between those checks. Dovecot can also use dnotify, inotify and -# kqueue to find out immediately when changes occur. -#mailbox_idle_check_interval = 30 secs - -# Save mails with CR+LF instead of plain LF. This makes sending those mails -# take less CPU, especially with sendfile() syscall with Linux and FreeBSD. -# But it also creates a bit more disk I/O which may just make it slower. -# Also note that if other software reads the mboxes/maildirs, they may handle -# the extra CRs wrong and cause problems. -#mail_save_crlf = no - -# Max number of mails to keep open and prefetch to memory. This only works with -# some mailbox formats and/or operating systems. -#mail_prefetch_count = 0 - -# How often to scan for stale temporary files and delete them (0 = never). -# These should exist only after Dovecot dies in the middle of saving mails. -#mail_temp_scan_interval = 1w - -## -## Maildir-specific settings -## - -# By default LIST command returns all entries in maildir beginning with a dot. -# Enabling this option makes Dovecot return only entries which are directories. -# This is done by stat()ing each entry, so it causes more disk I/O. -# (For systems setting struct dirent->d_type, this check is free and it's -# done always regardless of this setting) -#maildir_stat_dirs = no - -# When copying a message, do it with hard links whenever possible. This makes -# the performance much better, and it's unlikely to have any side effects. -#maildir_copy_with_hardlinks = yes - -# Assume Dovecot is the only MUA accessing Maildir: Scan cur/ directory only -# when its mtime changes unexpectedly or when we can't find the mail otherwise. -#maildir_very_dirty_syncs = no - -# If enabled, Dovecot doesn't use the S= in the Maildir filenames for -# getting the mail's physical size, except when recalculating Maildir++ quota. -# This can be useful in systems where a lot of the Maildir filenames have a -# broken size. The performance hit for enabling this is very small. -#maildir_broken_filename_sizes = no - -## -## mbox-specific settings -## - -# Which locking methods to use for locking mbox. There are four available: -# dotlock: Create .lock file. This is the oldest and most NFS-safe -# solution. If you want to use /var/mail/ like directory, the users -# will need write access to that directory. -# dotlock_try: Same as dotlock, but if it fails because of permissions or -# because there isn't enough disk space, just skip it. -# fcntl : Use this if possible. Works with NFS too if lockd is used. -# flock : May not exist in all systems. Doesn't work with NFS. -# lockf : May not exist in all systems. Doesn't work with NFS. -# -# You can use multiple locking methods; if you do the order they're declared -# in is important to avoid deadlocks if other MTAs/MUAs are using multiple -# locking methods as well. Some operating systems don't allow using some of -# them simultaneously. -#mbox_read_locks = fcntl -#mbox_write_locks = dotlock fcntl - -# Maximum time to wait for lock (all of them) before aborting. -#mbox_lock_timeout = 5 mins - -# If dotlock exists but the mailbox isn't modified in any way, override the -# lock file after this much time. -#mbox_dotlock_change_timeout = 2 mins - -# When mbox changes unexpectedly we have to fully read it to find out what -# changed. If the mbox is large this can take a long time. Since the change -# is usually just a newly appended mail, it'd be faster to simply read the -# new mails. If this setting is enabled, Dovecot does this but still safely -# fallbacks to re-reading the whole mbox file whenever something in mbox isn't -# how it's expected to be. The only real downside to this setting is that if -# some other MUA changes message flags, Dovecot doesn't notice it immediately. -# Note that a full sync is done with SELECT, EXAMINE, EXPUNGE and CHECK -# commands. -#mbox_dirty_syncs = yes - -# Like mbox_dirty_syncs, but don't do full syncs even with SELECT, EXAMINE, -# EXPUNGE or CHECK commands. If this is set, mbox_dirty_syncs is ignored. -#mbox_very_dirty_syncs = no - -# Delay writing mbox headers until doing a full write sync (EXPUNGE and CHECK -# commands and when closing the mailbox). This is especially useful for POP3 -# where clients often delete all mails. The downside is that our changes -# aren't immediately visible to other MUAs. -#mbox_lazy_writes = yes - -# If mbox size is smaller than this (e.g. 100k), don't write index files. -# If an index file already exists it's still read, just not updated. -#mbox_min_index_size = 0 - -# Mail header selection algorithm to use for MD5 POP3 UIDLs when -# pop3_uidl_format=%m. For backwards compatibility we use apop3d inspired -# algorithm, but it fails if the first Received: header isn't unique in all -# mails. An alternative algorithm is "all" that selects all headers. -#mbox_md5 = apop3d - -## -## mdbox-specific settings -## - -# Maximum dbox file size until it's rotated. -#mdbox_rotate_size = 2M - -# Maximum dbox file age until it's rotated. Typically in days. Day begins -# from midnight, so 1d = today, 2d = yesterday, etc. 0 = check disabled. -#mdbox_rotate_interval = 0 - -# When creating new mdbox files, immediately preallocate their size to -# mdbox_rotate_size. This setting currently works only in Linux with some -# filesystems (ext4, xfs). -#mdbox_preallocate_space = no - -## -## Mail attachments -## - -# sdbox and mdbox support saving mail attachments to external files, which -# also allows single instance storage for them. Other backends don't support -# this for now. - -# WARNING: This feature hasn't been tested much yet. Use at your own risk. - -# Directory root where to store mail attachments. Disabled, if empty. -#mail_attachment_dir = - -# Attachments smaller than this aren't saved externally. It's also possible to -# write a plugin to disable saving specific attachments externally. -#mail_attachment_min_size = 128k - -# Filesystem backend to use for saving attachments: -# posix : No SiS done by Dovecot (but this might help FS's own deduplication) -# sis posix : SiS with immediate byte-by-byte comparison during saving -# sis-queue posix : SiS with delayed comparison and deduplication -#mail_attachment_fs = sis posix - -# Hash format to use in attachment filenames. You can add any text and -# variables: %{md4}, %{md5}, %{sha1}, %{sha256}, %{sha512}, %{size}. -# Variables can be truncated, e.g. %{sha256:80} returns only first 80 bits -#mail_attachment_hash = %{sha1} -]]> - - - - . -postmaster_address = postmaster@ - -# Hostname to use in various parts of sent mails, eg. in Message-Id. -# Default is the system's real hostname. -#hostname = - -# If user is over quota, return with temporary failure instead of -# bouncing the mail. -#quota_full_tempfail = no - -# Binary to use for sending mails. -#sendmail_path = /usr/sbin/sendmail - -# If non-empty, send mails via this SMTP host[:port] instead of sendmail. -#submission_host = - -# Subject: header to use for rejection mails. You can use the same variables -# as for rejection_reason below. -#rejection_subject = Rejected: %s - -# Human readable error message for rejection mails. You can use variables: -# %n = CRLF, %r = reason, %s = original subject, %t = recipient -#rejection_reason = Your message to <%t> was automatically rejected:%n%r - -# Delimiter character between local-part and detail in email address. -#recipient_delimiter = + - -# Header where the original recipient address (SMTP's RCPT TO: address) is taken -# from if not available elsewhere. With dovecot-lda -a parameter overrides this. -# A commonly used header for this is X-Original-To. -#lda_original_recipient_header = - -# Should saving a mail to a nonexistent mailbox automatically create it? -#lda_mailbox_autocreate = no - -# Should automatically created mailboxes be also automatically subscribed? -#lda_mailbox_autosubscribe = no - -protocol lda { - # Space separated list of plugins to load (default is global mail_plugins). - mail_plugins = $mail_plugins quota sieve -} -]]> - - - - - - - - - #service_count = 1 - - # Number of processes to always keep waiting for more connections. - #process_min_avail = 0 - - # If you set service_count=0, you probably need to grow this. - #vsz_limit = 64M -} - -service managesieve { - # Max. number of ManageSieve processes (connections) - #process_limit = 1024 -} - -# Service configuration - -protocol sieve { - # Maximum ManageSieve command line length in bytes. ManageSieve usually does - # not involve overly long command lines, so this setting will not normally - # need adjustment - #managesieve_max_line_length = 65536 - - # Maximum number of ManageSieve connections allowed for a user from each IP - # address. - # NOTE: The username is compared case-sensitively. - #mail_max_userip_connections = 10 - - # Space separated list of plugins to load (none known to be useful so far). - # Do NOT try to load IMAP plugins here. - #mail_plugins = - - # MANAGESIEVE logout format string: - # %i - total number of bytes read from client - # %o - total number of bytes sent to client - #managesieve_logout_format = bytes=%i/%o - - # To fool ManageSieve clients that are focused on CMU's timesieved you can - # specify the IMPLEMENTATION capability that Dovecot reports to clients. - # For example: 'Cyrus timsieved v2.2.13' - #managesieve_implementation_string = Dovecot Pigeonhole - - # Explicitly specify the SIEVE and NOTIFY capability reported by the server - # before login. If left unassigned these will be reported dynamically - # according to what the Sieve interpreter supports by default (after login - # this may differ depending on the user). - #managesieve_sieve_capability = - #managesieve_notify_capability = - - # The maximum number of compile errors that are returned to the client upon - # script upload or script verification. - #managesieve_max_compile_errors = 5 - - # Refer to 90-sieve.conf for script quota configuration and configuration of - # Sieve execution limits. -} -]]> - - - - = 2.1.4) : %v.%u - # Dovecot v0.99.x : %v.%u - # tpop3d : %Mf - # - # Note that Outlook 2003 seems to have problems with %v.%u format which was - # Dovecot's default, so if you're building a new server it would be a good - # idea to change this. %08Xu%08Xv should be pretty fail-safe. - # - #pop3_uidl_format = %08Xu%08Xv - - # Permanently save UIDLs sent to POP3 clients, so pop3_uidl_format changes - # won't change those UIDLs. Currently this works only with Maildir. - #pop3_save_uidl = no - - # What to do about duplicate UIDLs if they exist? - # allow: Show duplicates to clients. - # rename: Append a temporary -2, -3, etc. counter after the UIDL. - #pop3_uidl_duplicates = allow - - # POP3 logout format string: - # %i - total number of bytes read from client - # %o - total number of bytes sent to client - # %t - number of TOP commands - # %p - number of bytes sent to client as a result of TOP command - # %r - number of RETR commands - # %b - number of bytes sent to client as a result of RETR command - # %d - number of deleted messages - # %m - number of messages (before deletion) - # %s - mailbox size in bytes (before deletion) - # %u - old/new UIDL hash. may help finding out if UIDLs changed unexpectedly - pop3_logout_format = in=%i out=%o top=%t/%p retr=%r/%b del=%d/%m size=%s - - # Maximum number of POP3 connections allowed for a user from each IP address. - # NOTE: The username is compared case-sensitively. - #mail_max_userip_connections = 10 - - # Space separated list of plugins to load (default is global mail_plugins). - mail_plugins = $mail_plugins quota - - # Workarounds for various client bugs: - # outlook-no-nuls: - # Outlook and Outlook Express hang if mails contain NUL characters. - # This setting replaces them with 0x80 character. - # oe-ns-eoh: - # Outlook Express and Netscape Mail breaks if end of headers-line is - # missing. This option simply sends it if it's missing. - # The list is space-separated. - #pop3_client_workarounds = -} -]]> - - - - See sieve_before fore executing scripts before the user's personal - # script. - #sieve_default = /var/lib/dovecot/sieve/default.sieve - - # Directory for :personal include scripts for the include extension. This - # is also where the ManageSieve service stores the user's scripts. - sieve_dir = ~/sieve - - # Directory for :global include scripts for the include extension. - #sieve_global_dir = - - # Path to a script file or a directory containing script files that need to be - # executed before the user's script. If the path points to a directory, all - # the Sieve scripts contained therein (with the proper .sieve extension) are - # executed. The order of execution within a directory is determined by the - # file names, using a normal 8bit per-character comparison. Multiple script - # file or directory paths can be specified by appending an increasing number. - #sieve_before = - #sieve_before2 = - #sieve_before3 = (etc...) - - # Identical to sieve_before, only the specified scripts are executed after the - # user's script (only when keep is still in effect!). Multiple script file or - # directory paths can be specified by appending an increasing number. - #sieve_after = - #sieve_after2 = - #sieve_after2 = (etc...) - - # Which Sieve language extensions are available to users. By default, all - # supported extensions are available, except for deprecated extensions or - # those that are still under development. Some system administrators may want - # to disable certain Sieve extensions or enable those that are not available - # by default. This setting can use '+' and '-' to specify differences relative - # to the default. For example `sieve_extensions = +imapflags' will enable the - # deprecated imapflags extension in addition to all extensions were already - # enabled by default. - #sieve_extensions = +notify +imapflags - - # Which Sieve language extensions are ONLY available in global scripts. This - # can be used to restrict the use of certain Sieve extensions to administrator - # control, for instance when these extensions can cause security concerns. - # This setting has higher precedence than the `sieve_extensions' setting - # (above), meaning that the extensions enabled with this setting are never - # available to the user's personal script no matter what is specified for the - # `sieve_extensions' setting. The syntax of this setting is similar to the - # `sieve_extensions' setting, with the difference that extensions are - # enabled or disabled for exclusive use in global scripts. Currently, no - # extensions are marked as such by default. - #sieve_global_extensions = - - # The Pigeonhole Sieve interpreter can have plugins of its own. Using this - # setting, the used plugins can be specified. Check the Dovecot wiki - # (wiki2.dovecot.org) or the pigeonhole website - # (http://pigeonhole.dovecot.org) for available plugins. - #sieve_plugins = - - # The separator that is expected between the :user and :detail - # address parts introduced by the subaddress extension. This may - # also be a sequence of characters (e.g. '--'). The current - # implementation looks for the separator from the left of the - # localpart and uses the first one encountered. The :user part is - # left of the separator and the :detail part is right. This setting - # is also used by Dovecot's LMTP service. - #recipient_delimiter = + - - # The maximum size of a Sieve script. The compiler will refuse to compile any - # script larger than this limit. If set to 0, no limit on the script size is - # enforced. - #sieve_max_script_size = 1M - - # The maximum number of actions that can be performed during a single script - # execution. If set to 0, no limit on the total number of actions is enforced. - #sieve_max_actions = 32 - - # The maximum number of redirect actions that can be performed during a single - # script execution. If set to 0, no redirect actions are allowed. - #sieve_max_redirects = 4 - - # The maximum number of personal Sieve scripts a single user can have. If set - # to 0, no limit on the number of scripts is enforced. - # (Currently only relevant for ManageSieve) - #sieve_quota_max_scripts = 0 - - # The maximum amount of disk storage a single user's scripts may occupy. If - # set to 0, no limit on the used amount of disk storage is enforced. - # (Currently only relevant for ManageSieve) - #sieve_quota_max_storage = 0 -} -]]> - - - - - - - - - - //service[@type='mail']/general/installs[@index=1] - - //service[@type='mail']/general/files[@index=1] - - - - #service_count = 1 - - # Number of processes to always keep waiting for more connections. - #process_min_avail = 0 - - # If you set service_count=0, you probably need to grow this. - #vsz_limit = $default_vsz_limit -} - -service pop3-login { - inet_listener pop3 { - #port = 110 - } - inet_listener pop3s { - #port = 995 - #ssl = yes - } -} - -service lmtp { - unix_listener lmtp { - #mode = 0666 - } - - # Create inet listener only if you can't use the above UNIX socket - #inet_listener lmtp { - # Avoid making LMTP visible for the entire internet - #address = - #port = - #} -} - -service imap { - # Most of the memory goes to mmap()ing files. You may need to increase this - # limit if you have huge mailboxes. - #vsz_limit = $default_vsz_limit - - # Max. number of IMAP processes (connections) - #process_limit = 1024 -} - -service pop3 { - # Max. number of POP3 processes (connections) - #process_limit = 1024 -} - -service auth { - # auth_socket_path points to this userdb socket by default. It's typically - # used by dovecot-lda, doveadm, possibly imap process, etc. Users that have - # full permissions to this socket are able to get a list of all usernames and - # get the results of everyone's userdb lookups. - # - # The default 0666 mode allows anyone to connect to the socket, but the - # userdb lookups will succeed only if the userdb returns an "uid" field that - # matches the caller process's UID. Also if caller's uid or gid matches the - # socket's uid or gid the lookup succeeds. Anything else causes a failure. - # - # To give the caller full permissions to lookup all users, set the mode to - # something else than 0666 and Dovecot lets the kernel enforce the - # permissions (e.g. 0777 allows everyone full permissions). - unix_listener auth-userdb { - #mode = 0666 - #user = - #group = - } - - # Postfix smtp-auth - unix_listener /var/spool/postfix/private/auth { - mode = 0660 - user = postfix - group = postfix - } - # Exim4 smtp-auth - unix_listener auth-client { - mode = 0660 - user = mail - } - - # Auth process is run as this user. - #user = $default_internal_user -} - -service auth-worker { - # Auth worker process is run as root by default, so that it can access - # /etc/shadow. If this isn't necessary, the user should be changed to - # $default_internal_user. - #user = root -} - -service dict { - # If dict proxy is used, mail processes should have access to its socket. - # For example: mode=0660, group=vmail and global mail_access_groups=vmail - unix_listener dict { - #mode = 0600 - #user = - #group = - } -} -]]> - - - //service[@type='mail']/general/commands[@index=1] - - - - - //service[@type='mail']/general/installs[@index=1] - - //service[@type='mail']/general/files[@index=1] - - - - #service_count = 1 - - # Number of processes to always keep waiting for more connections. - #process_min_avail = 0 - - # If you set service_count=0, you probably need to grow this. - #vsz_limit = $default_vsz_limit -} - -service pop3-login { - inet_listener pop3 { - #port = 110 - } - inet_listener pop3s { - #port = 995 - #ssl = yes - } -} - -service lmtp { - unix_listener lmtp { - #mode = 0666 - } - - # Create inet listener only if you can't use the above UNIX socket - #inet_listener lmtp { - # Avoid making LMTP visible for the entire internet - #address = - #port = - #} -} - -service imap { - # Most of the memory goes to mmap()ing files. You may need to increase this - # limit if you have huge mailboxes. - #vsz_limit = $default_vsz_limit - - # Max. number of IMAP processes (connections) - #process_limit = 1024 -} - -service pop3 { - # Max. number of POP3 processes (connections) - #process_limit = 1024 -} - -service auth { - # auth_socket_path points to this userdb socket by default. It's typically - # used by dovecot-lda, doveadm, possibly imap process, etc. Users that have - # full permissions to this socket are able to get a list of all usernames and - # get the results of everyone's userdb lookups. - # - # The default 0666 mode allows anyone to connect to the socket, but the - # userdb lookups will succeed only if the userdb returns an "uid" field that - # matches the caller process's UID. Also if caller's uid or gid matches the - # socket's uid or gid the lookup succeeds. Anything else causes a failure. - # - # To give the caller full permissions to lookup all users, set the mode to - # something else than 0666 and Dovecot lets the kernel enforce the - # permissions (e.g. 0777 allows everyone full permissions). - unix_listener auth-userdb { - #mode = 0666 - #user = - #group = - } - - # Postfix smtp-auth - unix_listener /var/spool/postfix/private/auth { - mode = 0660 - user = postfix - group = postfix - } - # Exim4 smtp-auth - unix_listener auth-client { - mode = 0660 - user = mail - # group = Debian-exim - } - - # Auth process is run as this user. - #user = $default_internal_user -} - -service auth-worker { - # Auth worker process is run as root by default, so that it can access - # /etc/shadow. If this isn't necessary, the user should be changed to - # $default_internal_user. - #user = root -} - -service dict { - # If dict proxy is used, mail processes should have access to its socket. - # For example: mode=0660, group=vmail and global mail_access_groups=vmail - unix_listener dict { - #mode = 0600 - #user = - #group = - } -} -]]> - - - //service[@type='mail']/general/commands[@index=1] - - - - - - - - - - - -MYSQL_USERNAME -MYSQL_PASSWORD - -##NAME: SSLINFO:0 -# -# The SSL information. -# -# To use SSL-encrypted connections, define the following variables (available -# in MySQL 4.0, or higher): -# -# -# MYSQL_SSL_KEY /path/to/file -# MYSQL_SSL_CERT /path/to/file -# MYSQL_SSL_CACERT /path/to/file -# MYSQL_SSL_CAPATH /path/to/file -# MYSQL_SSL_CIPHERS ALL:!DES - -##NAME: MYSQL_SOCKET:0 -# -# MYSQL_SOCKET can be used with MySQL version 3.22 or later, it specifies the -# filesystem pipe used for the connection -# -# MYSQL_SOCKET /var/run/mysqld/mysqld.sock - -##NAME: MYSQL_PORT:0 -# -# MYSQL_PORT can be used with MySQL version 3.22 or later to specify a port to -# connect to. - -MYSQL_PORT 0 - -##NAME: MYSQL_OPT:0 -# -# Leave MYSQL_OPT as 0, unless you know what you're doing. - -MYSQL_OPT 0 - -##NAME: MYSQL_DATABASE:0 -# -# The name of the MySQL database we will open: - -MYSQL_DATABASE - -#NAME: MYSQL_CHARACTER_SET:0 -# -# This is optional. MYSQL_CHARACTER_SET installs a character set. This option -# can be used with MySQL version 4.1 or later. MySQL supports 70+ collations -# for 30+ character sets. See MySQL documentations for more detalis. -# -# MYSQL_CHARACTER_SET latin1 - -##NAME: MYSQL_USER_TABLE:0 -# -# The name of the table containing your user data. See README.authmysqlrc -# for the required fields in this table. - -MYSQL_USER_TABLE mail_users - -##NAME: MYSQL_CRYPT_PWFIELD:0 -# -# Either MYSQL_CRYPT_PWFIELD or MYSQL_CLEAR_PWFIELD must be defined. Both -# are OK too. crypted passwords go into MYSQL_CRYPT_PWFIELD, cleartext -# passwords go into MYSQL_CLEAR_PWFIELD. Cleartext passwords allow -# CRAM-MD5 authentication to be implemented. - -MYSQL_CRYPT_PWFIELD password_enc - -##NAME: MYSQL_CLEAR_PWFIELD:0 -# -# -# MYSQL_CLEAR_PWFIELD clear - -##NAME: MYSQL_DEFAULT_DOMAIN:0 -# -# If DEFAULT_DOMAIN is defined, and someone tries to log in as 'user', -# we will look up 'user@DEFAULT_DOMAIN' instead. -# -# -# DEFAULT_DOMAIN example.com - -##NAME: MYSQL_UID_FIELD:0 -# -# Other fields in the mysql table: -# -# MYSQL_UID_FIELD - contains the numerical userid of the account -# -MYSQL_UID_FIELD uid - -##NAME: MYSQL_GID_FIELD:0 -# -# Numerical groupid of the account - -MYSQL_GID_FIELD gid - -##NAME: MYSQL_LOGIN_FIELD:0 -# -# The login id, default is id. Basically the query is: -# -# SELECT MYSQL_UID_FIELD, MYSQL_GID_FIELD, ... WHERE id='loginid' -# - -MYSQL_LOGIN_FIELD username - -##NAME: MYSQL_HOME_FIELD:0 -# - -MYSQL_HOME_FIELD homedir - -##NAME: MYSQL_NAME_FIELD:0 -# -# The user's name (optional) - -#MYSQL_NAME_FIELD name - -##NAME: MYSQL_MAILDIR_FIELD:0 -# -# This is an optional field, and can be used to specify an arbitrary -# location of the maildir for the account, which normally defaults to -# $HOME/Maildir (where $HOME is read from MYSQL_HOME_FIELD). -# -# You still need to provide a MYSQL_HOME_FIELD, even if you uncomment this -# out. -# -MYSQL_MAILDIR_FIELD maildir - -##NAME: MYSQL_DEFAULTDELIVERY:0 -# -# Courier mail server only: optional field specifies custom mail delivery -# instructions for this account (if defined) -- essentially overrides -# DEFAULTDELIVERY from ${sysconfdir}/courierd -# -# MYSQL_DEFAULTDELIVERY defaultdelivery - -##NAME: MYSQL_QUOTA_FIELD:0 -# -# Define MYSQL_QUOTA_FIELD to be the name of the field that can optionally -# specify a maildir quota. See README.maildirquota for more information -# -MYSQL_QUOTA_FIELD (quota*1024*1024) - -##NAME: MYSQL_AUXOPTIONS:0 -# -# Auxiliary options. The MYSQL_AUXOPTIONS field should be a char field that -# contains a single string consisting of comma-separated "ATTRIBUTE=NAME" -# pairs. These names are additional attributes that define various per-account -# "options", as given in INSTALL's description of the "Account OPTIONS" -# setting. -# -# MYSQL_AUXOPTIONS_FIELD auxoptions -# -# You might want to try something like this, if you'd like to use a bunch -# of individual fields, instead of a single text blob: -# -MYSQL_AUXOPTIONS_FIELD CONCAT("allowimap=",imap,",allowpop3=",pop3) -# -# This will let you define fields called "disableimap", etc, with the end result -# being something that the OPTIONS parser understands. - - -##NAME: MYSQL_WHERE_CLAUSE:0 -# -# This is optional, MYSQL_WHERE_CLAUSE can be basically set to an arbitrary -# fixed string that is appended to the WHERE clause of our query -# -# MYSQL_WHERE_CLAUSE server='mailhost.example.com' - -##NAME: MYSQL_SELECT_CLAUSE:0 -# -# (EXPERIMENTAL) -# This is optional, MYSQL_SELECT_CLAUSE can be set when you have a database, -# which is structuraly different from proposed. The fixed string will -# be used to do a SELECT operation on database, which should return fields -# in order specified bellow: -# -# username, cryptpw, clearpw, uid, gid, home, maildir, quota, fullname, options -# -# The username field should include the domain (see example below). -# -# Enabling this option causes ignorance of any other field-related -# options, excluding default domain. -# -# There are two variables, which you can use. Substitution will be made -# for them, so you can put entered username (local part) and domain name -# in the right place of your query. These variables are: -# $(local_part), $(domain), $(service) -# -# If a $(domain) is empty (not given by the remote user) the default domain -# name is used in its place. -# -# $(service) will expand out to the service being authenticated: imap, imaps, -# pop3 or pop3s. Courier mail server only: service will also expand out to -# "courier", when searching for local mail account's location. In this case, -# if the "maildir" field is not empty it will be used in place of -# DEFAULTDELIVERY. Courier mail server will also use esmtp when doing -# authenticated ESMTP. -# -# This example is a little bit modified adaptation of vmail-sql -# database scheme: -# -# MYSQL_SELECT_CLAUSE SELECT CONCAT(popbox.local_part, '@', popbox.domain_name), \ -# CONCAT('{MD5}', popbox.password_hash), \ -# popbox.clearpw, \ -# domain.uid, \ -# domain.gid, \ -# CONCAT(domain.path, '/', popbox.mbox_name), \ -# '', \ -# domain.quota, \ -# '', \ -# CONCAT("disableimap=",disableimap,",disablepop3=", \ -# disablepop3,",disablewebmail=",disablewebmail, \ -# ",sharedgroup=",sharedgroup) \ -# FROM popbox, domain \ -# WHERE popbox.local_part = '$(local_part)' \ -# AND popbox.domain_name = '$(domain)' \ -# AND popbox.domain_name = domain.domain_name - - -##NAME: MYSQL_ENUMERATE_CLAUSE:1 -# -# {EXPERIMENTAL} -# Optional custom SQL query used to enumerate accounts for authenumerate, -# in order to compile a list of accounts for shared folders. The query -# should return the following fields: name, uid, gid, homedir, maildir, options -# -# Example: -# MYSQL_ENUMERATE_CLAUSE SELECT CONCAT(popbox.local_part, '@', popbox.domain_name), \ -# domain.uid, \ -# domain.gid, \ -# CONCAT(domain.path, '/', popbox.mbox_name), \ -# '', \ -# CONCAT('sharedgroup=', sharedgroup) \ -# FROM popbox, domain \ -# WHERE popbox.local_part = '$(local_part)' \ -# AND popbox.domain_name = '$(domain)' \ -# AND popbox.domain_name = domain.domain_name - - - -##NAME: MYSQL_CHPASS_CLAUSE:0 -# -# (EXPERIMENTAL) -# This is optional, MYSQL_CHPASS_CLAUSE can be set when you have a database, -# which is structuraly different from proposed. The fixed string will -# be used to do an UPDATE operation on database. In other words, it is -# used, when changing password. -# -# There are four variables, which you can use. Substitution will be made -# for them, so you can put entered username (local part) and domain name -# in the right place of your query. There variables are: -# $(local_part) , $(domain) , $(newpass) , $(newpass_crypt) -# -# If a $(domain) is empty (not given by the remote user) the default domain -# name is used in its place. -# $(newpass) contains plain password -# $(newpass_crypt) contains its crypted form -# -# MYSQL_CHPASS_CLAUSE UPDATE popbox \ -# SET clearpw='$(newpass)', \ -# password_hash='$(newpass_crypt)' \ -# WHERE local_part='$(local_part)' \ -# AND domain_name='$(domain)' -# -]]> - - - - - - - - - - - - - " -[ -f /etc/ssl/certs/proftpd_ec.crt ] || openssl req -new -x509 -nodes -newkey ec:<(openssl ecparam -name secp521r1) -keyout /etc/ssl/private/proftpd_ec.key -out /etc/ssl/certs/proftpd_ec.crt -days 3650 -subj "/C=US/ST=Some-State/O=Internet Widgits Pty Ltd/CN=" -chmod 0600 /etc/ssl/private/proftpd.key /etc/ssl/private/proftpd_ec.key -]]> - - - - - - - - FTP Server" -ServerType standalone -DeferWelcome off - -MultilineRFC2228 on -DefaultServer on -ShowSymlinks on - -TimeoutNoTransfer 600 -TimeoutStalled 600 -TimeoutIdle 1200 - -DisplayLogin welcome.msg -DisplayChdir .message true -ListOptions "-l" - -DenyFilter \*.*/ - -# Use this to jail all users in their homes -# DefaultRoot ~ - -# Users require a valid shell listed in /etc/shells to login. -# Use this directive to release that constrain. -# RequireValidShell off - -# Port 21 is the standard FTP port. -Port 21 - -# In some cases you have to specify passive ports range to by-pass -# firewall limitations. Ephemeral ports can be used for that, but -# feel free to use a more narrow range. -# PassivePorts 49152 65534 - -# If your host was NATted, this option is useful in order to -# allow passive tranfers to work. You have to use your public -# address and opening the passive ports used on your firewall as well. -# MasqueradeAddress 1.2.3.4 - -# This is useful for masquerading address with dynamic IPs: -# refresh any configured MasqueradeAddress directives every 8 hours - -# DynMasqRefresh 28800 - - -# To prevent DoS attacks, set the maximum number of child processes -# to 30. If you need to allow more than 30 concurrent connections -# at once, simply increase this value. Note that this ONLY works -# in standalone mode, in inetd mode you should use an inetd server -# that allows you to limit maximum number of processes per service -# (such as xinetd) -MaxInstances 30 - -# Set the user and group that the server normally runs at. -User proftpd -Group nogroup - -# Umask 022 is a good standard umask to prevent new files and dirs -# (second parm) from being group and world writable. -Umask 022 022 -# Normally, we want files to be overwriteable. -AllowOverwrite on - -# Uncomment this if you are using NIS or LDAP via NSS to retrieve passwords: -# PersistentPasswd off - -# This is required to use both PAM-based authentication and local passwords -# AuthOrder mod_auth_pam.c* mod_auth_unix.c - -# Be warned: use of this directive impacts CPU average load! -# Uncomment this if you like to see progress and transfer rate with ftpwho -# in downloads. That is not needed for uploads rates. -# -# UseSendFile off - -TransferLog /var/log/proftpd/xferlog -SystemLog /var/log/proftpd/proftpd.log - -# Logging onto /var/log/lastlog is enabled but set to off by default -#UseLastlog on - -# In order to keep log file dates consistent after chroot, use timezone info -# from /etc/localtime. If this is not set, and proftpd is configured to -# chroot (e.g. DefaultRoot or ), it will use the non-daylight -# savings timezone regardless of whether DST is in effect. -#SetEnv TZ :/etc/localtime - - -QuotaEngine on - - - -Ratios off - - - -# Delay engine reduces impact of the so-called Timing Attack described in -# http://www.securityfocus.com/bid/11430/discuss -# It is on by default. - -DelayEngine on - - - -ControlsEngine off -ControlsMaxClients 2 -ControlsLog /var/log/proftpd/controls.log -ControlsInterval 5 -ControlsSocket /var/run/proftpd/proftpd.sock - - - -AdminControlsEngine off - - -# -# Alternative authentication frameworks -# -#Include /etc/proftpd/ldap.conf -Include /etc/proftpd/sql.conf - -# -# This is used for FTPS connections -# -Include /etc/proftpd/tls.conf - -# -# Useful to keep VirtualHost/VirtualRoot directives separated -# -#Include /etc/proftpd/virtuals.conf - -# A basic anonymous configuration, no upload directories. - -# -# User ftp -# Group nogroup -# # We want clients to be able to login with "anonymous" as well as "ftp" -# UserAlias anonymous ftp -# # Cosmetic changes, all files belongs to ftp user -# DirFakeUser on ftp -# DirFakeGroup on ftp -# -# RequireValidShell off -# -# # Limit the maximum number of anonymous logins -# MaxClients 10 -# -# # We want 'welcome.msg' displayed at login, and '.message' displayed -# # in each newly chdired directory. -# DisplayLogin welcome.msg -# DisplayChdir .message -# -# # Limit WRITE everywhere in the anonymous chroot -# -# -# DenyAll -# -# -# -# # Uncomment this if you're brave. -# # -# # # Umask 022 is a good standard umask to prevent new files and dirs -# # # (second parm) from being group and world writable. -# # Umask 022 022 -# # -# # DenyAll -# # -# # -# # AllowAll -# # -# # -# -# - -# Include other custom configuration files -Include /etc/proftpd/conf.d/ -]]> - - - - - - - - -DefaultRoot ~ -RequireValidShell off -AuthOrder mod_sql.c - -SQLBackend mysql -SQLEngine on -SQLAuthenticate on - -SQLAuthTypes Crypt -SQLAuthenticate users* groups* -SQLConnectInfo @ -SQLUserInfo ftp_users username password uid gid homedir shell -SQLGroupInfo ftp_groups groupname gid members -SQLUserWhereClause "login_enabled = 'y'" - -SQLLog PASS login -SQLNamedQuery login UPDATE "last_login=now(), login_count=login_count+1 WHERE username='%u'" ftp_users - -SQLLog RETR download -SQLNamedQuery download UPDATE "down_count=down_count+1, down_bytes=down_bytes+%b WHERE username='%u'" ftp_users - -SQLLog STOR upload -SQLNamedQuery upload UPDATE "up_count=up_count+1, up_bytes=up_bytes+%b WHERE username='%u'" ftp_users - -QuotaEngine on -QuotaShowQuotas on -QuotaDisplayUnits Mb -QuotaLock /var/lock/ftpd.quotatab.lock -QuotaLimitTable sql:/get-quota-limit -QuotaTallyTable sql:/get-quota-tally/update-quota-tally/insert-quota-tally -SQLNamedQuery get-quota-limit SELECT "ftp_users.username AS name, ftp_quotalimits.quota_type, ftp_quotalimits.per_session, ftp_quotalimits.limit_type, panel_customers.diskspace*1024 AS bytes_in_avail, ftp_quotalimits.bytes_out_avail, ftp_quotalimits.bytes_xfer_avail, ftp_quotalimits.files_in_avail, ftp_quotalimits.files_out_avail, ftp_quotalimits.files_xfer_avail FROM ftp_users, ftp_quotalimits, panel_customers WHERE ftp_users.username = '%{0}' AND panel_customers.loginname = SUBSTRING_INDEX('%{0}', 'ftp', 1) AND quota_type ='%{1}'" -SQLNamedQuery get-quota-tally SELECT "name, quota_type, bytes_in_used,bytes_out_used, bytes_xfer_used, files_in_used, files_out_used,files_xfer_used FROM ftp_quotatallies WHERE name = '%{0}' AND quota_type = '%{1}'" -SQLNamedQuery update-quota-tally UPDATE "bytes_in_used = bytes_in_used + %{0}, bytes_out_used = bytes_out_used + %{1}, bytes_xfer_used = bytes_xfer_used + %{2}, files_in_used = files_in_used + %{3}, files_out_used= files_out_used + %{4}, files_xfer_used = files_xfer_used + %{5} WHERE name= '%{6}' AND quota_type = '%{7}'" ftp_quotatallies -SQLNamedQuery insert-quota-tally INSERT "%{0}, %{1}, %{2}, %{3}, %{4},%{5}, %{6}, %{7}" ftp_quotatallies - - -]]> - - - - -TLSEngine on -TLSLog /var/log/proftpd/tls.log -TLSProtocol TLSv1 TLSv1.1 TLSv1.2 -TLSRSACertificateFile /etc/ssl/certs/proftpd.crt -TLSRSACertificateKeyFile /etc/ssl/private/proftpd.key -TLSECCertificateFile /etc/ssl/certs/proftpd_ec.crt -TLSECCertificateKeyFile /etc/ssl/private/proftpd_ec.key -TLSOptions NoCertRequest NoSessionReuseRequired -TLSVerifyClient off - -# Are clients required to use FTP over TLS when talking to this server? -#TLSRequired on - -# Allow SSL/TLS renegotiations when the client requests them, but -# do not force the renegotations. Some clients do not support -# SSL/TLS renegotiations; when mod_tls forces a renegotiation, these -# clients will close the data connection, or there will be a timeout -# on an idle data connection. -# -#TLSRenegotiate required off - -]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# Optional : MySQL port. Don't define this if a local unix socket is used. - -# MYSQLPort 3306 - - -# Optional : define the location of mysql.sock if the server runs on this host. - -MYSQLSocket /var/run/mysqld/mysqld.sock - - -# Mandatory : user to bind the server as. - -MYSQLUser - - -# Mandatory : user password. You must have a password. - -MYSQLPassword - - -# Mandatory : database to open. - -MYSQLDatabase - - -# Mandatory : how passwords are stored -# Valid values are : "cleartext", "crypt", "sha1", "md5" and "password" -# ("password" = MySQL password() function) -# You can also use "any" to try "crypt", "sha1", "md5" *and* "password" - -MYSQLCrypt any - - -# In the following directives, parts of the strings are replaced at -# run-time before performing queries : -# -# \L is replaced by the login of the user trying to authenticate. -# \I is replaced by the IP address the user connected to. -# \P is replaced by the port number the user connected to. -# \R is replaced by the IP address the user connected from. -# \D is replaced by the remote IP address, as a long decimal number. -# -# Very complex queries can be performed using these substitution strings, -# especially for virtual hosting. - - -# Query to execute in order to fetch the password - -MYSQLGetPW SELECT password FROM ftp_users WHERE username="\L" AND login_enabled="y" - - -# Query to execute in order to fetch the system user name or uid - -MYSQLGetUID SELECT uid FROM ftp_users WHERE username="\L" AND login_enabled="y" - - -# Optional : default UID - if set this overrides MYSQLGetUID - -#MYSQLDefaultUID 1000 - - -# Query to execute in order to fetch the system user group or gid - -MYSQLGetGID SELECT gid FROM ftp_users WHERE username="\L" AND login_enabled="y" - - -# Optional : default GID - if set this overrides MYSQLGetGID - -#MYSQLDefaultGID 1000 - - -# Query to execute in order to fetch the home directory - -MYSQLGetDir SELECT homedir FROM ftp_users WHERE username="\L" AND login_enabled="y" - - -# Optional : query to get the maximal number of files -# Pure-FTPd must have been compiled with virtual quotas support. - -# MySQLGetQTAFS SELECT QuotaFiles FROM users WHERE User='\L' - - -# Optional : query to get the maximal disk usage (virtual quotas) -# The number should be in Megabytes. -# Pure-FTPd must have been compiled with virtual quotas support. - -MySQLGetQTASZ SELECT panel_customers.diskspace/1024 AS QuotaSize FROM panel_customers, ftp_users WHERE username = "\L" AND panel_customers.loginname = SUBSTRING_INDEX('\L', 'ftp', 1) - - -# Optional : ratios. The server has to be compiled with ratio support. - -# MySQLGetRatioUL SELECT ULRatio FROM users WHERE User='\L' -# MySQLGetRatioDL SELECT DLRatio FROM users WHERE User='\L' - - -# Optional : bandwidth throttling. -# The server has to be compiled with throttling support. -# Values are in KB/s . - -# MySQLGetBandwidthUL SELECT ULBandwidth FROM users WHERE User='\L' -# MySQLGetBandwidthDL SELECT DLBandwidth FROM users WHERE User='\L' - -# Enable ~ expansion. NEVER ENABLE THIS BLINDLY UNLESS : -# 1) You know what you are doing. -# 2) Real and virtual users match. - -# MySQLForceTildeExpansion 1 - - -# If you're using a transactionnal storage engine, you can enable SQL -# transactions to avoid races. Leave this commented if you are using the -# traditional MyIsam engine. - -# MySQLTransactions On -]]> - - - - - - - - - - - - - - - - - - - - - - - scripts/froxlor_master_cronjob.php -]]> - - - - - - - - - - - - - - - - - - -database -username -password -port 3306 -#socket /var/run/mysqld/mysqld.sock -]]> - - - - - {{sql.socket}} - - - - - - -password -]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *.log { - missingok - weekly - rotate 4 - compress - delaycompress - notifempty - create - sharedscripts - postrotate - > /dev/null 2>&1 || true - endscript -} -]]> - - - - - - - - - {{settings.system.mod_fcgid_ownvhost}} - - - - - - - - - - - - - - {{settings.system.webserver}} - - - - - - {{settings.system.webserver}} - - - - - - {{settings.system.webserver}} - - - - - {{settings.phpfpm.enabled_ownvhost}} - - {{settings.phpfpm.vhost_httpuser}} - - - - - - {{settings.system.webserver}} - - {{settings.phpfpm.enabled_ownvhost}} - - - - - {{settings.system.webserver}} - - - - - - - - - - From 5a070d6d9196005c6424f0476c6d63fb1a917008 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Thu, 24 May 2018 11:36:13 +0200 Subject: [PATCH 184/746] add settings to customize webserver logs Signed-off-by: Michael Kaufmann --- actions/admin/settings/130.webserver.php | 44 ++++++++++++++++++- install/froxlor.sql | 3 ++ .../updates/froxlor/0.9/update_0.9.inc.php | 11 +++++ lib/version.inc.php | 2 +- lng/english.lng.php | 8 ++++ .../jobs/cron_tasks.inc.http.10.apache.php | 18 +++++++- scripts/jobs/cron_tasks.inc.http.30.nginx.php | 8 +++- 7 files changed, 88 insertions(+), 6 deletions(-) diff --git a/actions/admin/settings/130.webserver.php b/actions/admin/settings/130.webserver.php index 4374b148..35ac4857 100644 --- a/actions/admin/settings/130.webserver.php +++ b/actions/admin/settings/130.webserver.php @@ -104,14 +104,54 @@ return array( 'save_method' => 'storeSettingField' ), 'system_logfiles_directory' => array( - 'label' => $lng['serversettings']['logfiles_directory'], + 'label' => (Settings::Get('system.webserver') != 'apache2') ? $lng['serversettings']['logfiles_directory'] : $lng['serversettings']['logfiles_directory2'], 'settinggroup' => 'system', 'varname' => 'logfiles_directory', 'type' => 'string', - 'string_type' => 'dir', + 'string_type' => (Settings::Get('system.webserver') != 'apache2') ? 'dir' : '', 'default' => '/var/customers/logs/', 'save_method' => 'storeSettingField' ), + 'system_logfiles_format' => array( + 'label' => $lng['serversettings']['logfiles_format'], + 'settinggroup' => 'system', + 'varname' => 'logfiles_format', + 'type' => 'string', + 'default' => '', + 'string_emptyallowed' => true, + 'save_method' => 'storeSettingField', + 'websrv_avail' => array( + 'apache2', + 'nginx' + ) + ), + 'system_logfiles_type' => array( + 'label' => $lng['serversettings']['logfiles_type'], + 'settinggroup' => 'system', + 'varname' => 'logfiles_type', + 'type' => 'option', + 'default' => '1', + 'option_mode' => 'one', + 'option_options' => array( + '1' => 'combined', + '2' => 'vhost_combined' + ), + 'save_method' => 'storeSettingField', + 'websrv_avail' => array( + 'apache2' + ) + ), + 'system_logfiles_piped' => array( + 'label' => $lng['serversettings']['logfiles_piped'], + 'settinggroup' => 'system', + 'varname' => 'logfiles_piped', + 'type' => 'bool', + 'default' => false, + 'save_method' => 'storeSettingField', + 'websrv_avail' => array( + 'apache2' + ) + ), 'system_customersslpath' => array( 'label' => $lng['serversettings']['customerssl_directory'], 'settinggroup' => 'system', diff --git a/install/froxlor.sql b/install/froxlor.sql index bdad8357..a3b63af7 100644 --- a/install/froxlor.sql +++ b/install/froxlor.sql @@ -656,6 +656,9 @@ opcache.interned_strings_buffer'), ('system', 'nssextrausers', '0'), ('system', 'disable_le_selfcheck', '0'), ('system', 'ssl_protocols', 'TLSv1,TLSv1.2'), + ('system', 'logfiles_format', ''), + ('system', 'logfiles_type', '1'), + ('system', 'logfiles_piped', '0'), ('panel', 'decimal_places', '4'), ('panel', 'adminmail', 'admin@SERVERNAME'), ('panel', 'phpmyadmin_url', ''), diff --git a/install/updates/froxlor/0.9/update_0.9.inc.php b/install/updates/froxlor/0.9/update_0.9.inc.php index 0e9c09e6..c1a9010e 100644 --- a/install/updates/froxlor/0.9/update_0.9.inc.php +++ b/install/updates/froxlor/0.9/update_0.9.inc.php @@ -3948,3 +3948,14 @@ if (isDatabaseVersion('201802130')) { updateToDbVersion('201802250'); } + +if (isDatabaseVersion('201802250')) { + + showUpdateStep("Adding webserver logfile settings"); + Settings::AddNew("system.logfiles_format", ''); + Settings::AddNew("system.logfiles_type", '1'); + Settings::AddNew("system.logfiles_piped", '0'); + lastStepStatus(0); + + updateToDbVersion('201805240'); +} diff --git a/lib/version.inc.php b/lib/version.inc.php index 9ad32b3a..65278766 100644 --- a/lib/version.inc.php +++ b/lib/version.inc.php @@ -19,7 +19,7 @@ $version = '0.9.39.5'; // Database version (YYYYMMDDC where C is a daily counter) -$dbversion = '201802250'; +$dbversion = '201805240'; // Distribution branding-tag (used for Debian etc.) $branding = ''; diff --git a/lng/english.lng.php b/lng/english.lng.php index fe156bf9..717948a2 100644 --- a/lng/english.lng.php +++ b/lng/english.lng.php @@ -337,6 +337,14 @@ $lng['serversettings']['documentroot_prefix']['title'] = 'Home directory'; $lng['serversettings']['documentroot_prefix']['description'] = 'Where should all home directories be stored?'; $lng['serversettings']['logfiles_directory']['title'] = 'Logfiles directory'; $lng['serversettings']['logfiles_directory']['description'] = 'Where should all log files be stored?'; +$lng['serversettings']['logfiles_directory2']['title'] = 'Logfiles directory or custom script'; +$lng['serversettings']['logfiles_directory2']['description'] = 'Where should all log files be stored? Optionally, you can specify a script here and use the placeholder {LOGFILE} if needed. In case of a custom script you will need to activate the logfiles piped option'; +$lng['serversettings']['logfiles_format']['title'] = 'Access-log format'; +$lng['serversettings']['logfiles_format']['description'] = 'Enter a custom log-format here according to your webservers specifications, leave empty for default'; +$lng['serversettings']['logfiles_type']['title'] = 'Access-log type'; +$lng['serversettings']['logfiles_type']['description'] = 'Chose between combined or vhost_combined here.'; +$lng['serversettings']['logfiles_piped']['title'] = 'Pipe webserver logfiles to specified script (see above)'; +$lng['serversettings']['logfiles_piped']['description'] = 'When using a custom script for the logfiles you need to activate this in order for it to be executed'; $lng['serversettings']['ipaddress']['title'] = 'IP-address'; $lng['serversettings']['ipaddress']['description'] = 'What\'s the main IP-address of this server?'; $lng['serversettings']['hostname']['title'] = 'Hostname'; diff --git a/scripts/jobs/cron_tasks.inc.http.10.apache.php b/scripts/jobs/cron_tasks.inc.http.10.apache.php index 5f966f38..2c5db125 100644 --- a/scripts/jobs/cron_tasks.inc.http.10.apache.php +++ b/scripts/jobs/cron_tasks.inc.http.10.apache.php @@ -733,8 +733,22 @@ class apache extends HttpConfigBase chown($access_log, Settings::Get('system.httpuser')); chgrp($access_log, Settings::Get('system.httpgroup')); - $logfiles_text .= ' ErrorLog "' . $error_log . "\"\n"; - $logfiles_text .= ' CustomLog "' . $access_log . '" combined' . "\n"; + $logtype = 'combined'; + if (Settings::Get('system.logfiles_format') != '') { + $logtype = 'frx_custom'; + $logfiles_text .= ' LogFormat "' . Settings::Get('system.logfiles_format') . '" ' . $logtype . "\n"; + } + if (Settings::Get('system.logfiles_type') == '2' && Settings::Get('system.logfiles_format') == '') { + $logtype = 'vhost_combined'; + } + + if (Settings::Get('system.logfiles_piped') == '1') { + $logfiles_text .= ' ErrorLog "|' . str_replace('{LOGFILE}', $error_log, Settings::Get('system.logfiles_directory')) . "\"\n"; + $logfiles_text .= ' CustomLog "|' . str_replace('{LOGFILE}', $access_log, Settings::Get('system.logfiles_directory')) . '" ' . $logtype . "\n"; + } else { + $logfiles_text .= ' ErrorLog "' . $error_log . '"' . "\n"; + $logfiles_text .= ' CustomLog "' . $access_log . '" ' . $logtype . "\n"; + } if (Settings::Get('system.awstats_enabled') == '1') { if ((int) $domain['parentdomainid'] == 0) { diff --git a/scripts/jobs/cron_tasks.inc.http.30.nginx.php b/scripts/jobs/cron_tasks.inc.http.30.nginx.php index f5bb1efe..c11dafeb 100644 --- a/scripts/jobs/cron_tasks.inc.http.30.nginx.php +++ b/scripts/jobs/cron_tasks.inc.http.30.nginx.php @@ -1005,7 +1005,13 @@ class nginx extends HttpConfigBase chown($access_log, Settings::Get('system.httpuser')); chgrp($access_log, Settings::Get('system.httpgroup')); - $logfiles_text .= "\t" . 'access_log ' . $access_log . ' combined;' . "\n"; + $logtype = 'combined'; + if (Settings::Get('system.logfiles_format') != '') { + $logtype = 'frx_custom'; + $logfiles_text .= "\t" . 'log_format ' . $logtype . ' "' . Settings::Get('system.logfiles_format') . '";' . "\n"; + } + + $logfiles_text .= "\t" . 'access_log ' . $access_log . ' ' . $logtype . ';' . "\n"; $logfiles_text .= "\t" . 'error_log ' . $error_log . ' error;' . "\n"; if (Settings::Get('system.awstats_enabled') == '1') { From bad680cfdbe0a1f7cd0e36fa0950b148ad31898a Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Thu, 24 May 2018 13:23:37 +0200 Subject: [PATCH 185/746] enhancements for webserver-log settings Signed-off-by: Michael Kaufmann --- install/froxlor.sql | 2 +- lng/english.lng.php | 2 +- lng/german.lng.php | 8 +++++ .../jobs/cron_tasks.inc.http.10.apache.php | 36 +++++++++++++------ 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/install/froxlor.sql b/install/froxlor.sql index a3b63af7..bda0e4d9 100644 --- a/install/froxlor.sql +++ b/install/froxlor.sql @@ -691,7 +691,7 @@ opcache.interned_strings_buffer'), ('panel', 'password_special_char', '!?<>§$%+#=@'), ('panel', 'customer_hide_options', ''), ('panel', 'version', '0.9.39.5'), - ('panel', 'db_version', '201802250'); + ('panel', 'db_version', '201805240'); DROP TABLE IF EXISTS `panel_tasks`; diff --git a/lng/english.lng.php b/lng/english.lng.php index 717948a2..2c068780 100644 --- a/lng/english.lng.php +++ b/lng/english.lng.php @@ -338,7 +338,7 @@ $lng['serversettings']['documentroot_prefix']['description'] = 'Where should all $lng['serversettings']['logfiles_directory']['title'] = 'Logfiles directory'; $lng['serversettings']['logfiles_directory']['description'] = 'Where should all log files be stored?'; $lng['serversettings']['logfiles_directory2']['title'] = 'Logfiles directory or custom script'; -$lng['serversettings']['logfiles_directory2']['description'] = 'Where should all log files be stored? Optionally, you can specify a script here and use the placeholder {LOGFILE} if needed. In case of a custom script you will need to activate the logfiles piped option'; +$lng['serversettings']['logfiles_directory2']['description'] = 'Where should all log files be stored? Optionally, you can specify a script here and use the placeholders {LOGFILE}, {DOMAIN} and {CUSTOMER} if needed. In case of a custom script you will need to activate the Pipe webserver logfiles option'; $lng['serversettings']['logfiles_format']['title'] = 'Access-log format'; $lng['serversettings']['logfiles_format']['description'] = 'Enter a custom log-format here according to your webservers specifications, leave empty for default'; $lng['serversettings']['logfiles_type']['title'] = 'Access-log type'; diff --git a/lng/german.lng.php b/lng/german.lng.php index 7f44f981..87fd914e 100644 --- a/lng/german.lng.php +++ b/lng/german.lng.php @@ -333,6 +333,14 @@ $lng['serversettings']['documentroot_prefix']['title'] = 'Heimatverzeichnis'; $lng['serversettings']['documentroot_prefix']['description'] = 'Wo sollen die Heimatverzeichnisse der Kunden liegen?'; $lng['serversettings']['logfiles_directory']['title'] = 'Webserver-Logdateien-Verzeichnis'; $lng['serversettings']['logfiles_directory']['description'] = 'Wo sollen die Logdateien des Webservers liegen?'; +$lng['serversettings']['logfiles_directory2']['title'] = 'Webserver-Logdateien-Verzeichnis oder eigenes Script'; +$lng['serversettings']['logfiles_directory2']['description'] = 'Wo sollen die Logdateien des Webservers liegen? Optional kann hier ein Script hinterlegt und die Platzhalter {LOGFILE}, {DOMAIN} und {CUSTOMER} genutzt werden, sofern nötig. Falls ein Script angegeben wird, muss die Option Webserver Logdateien umleiten gesetzt werden'; +$lng['serversettings']['logfiles_format']['title'] = 'Access-Log Format'; +$lng['serversettings']['logfiles_format']['description'] = 'Hier kann ein angepasstes Log-format entsprechend der Webserver-Dokumentation angegeben werden, leer lassen für Standard'; +$lng['serversettings']['logfiles_type']['title'] = 'Access-Log Typ'; +$lng['serversettings']['logfiles_type']['description'] = 'Wähle zwischen combined oder vhost_combined.'; +$lng['serversettings']['logfiles_piped']['title'] = 'Webserver Logdateien zu eigenem Script umleiten (siehe oben)'; +$lng['serversettings']['logfiles_piped']['description'] = 'Wenn ein Script für die Logdateien verwendet wird, muss diese Option aktiviert werden, damit der Webserver die Ausgabe an das Script weitergibt.'; $lng['serversettings']['ipaddress']['title'] = 'IP-Adresse'; $lng['serversettings']['ipaddress']['description'] = 'Welche Haupt-IP-Adresse hat der Server?'; $lng['serversettings']['hostname']['title'] = 'Hostname'; diff --git a/scripts/jobs/cron_tasks.inc.http.10.apache.php b/scripts/jobs/cron_tasks.inc.http.10.apache.php index 2c5db125..9dab54a5 100644 --- a/scripts/jobs/cron_tasks.inc.http.10.apache.php +++ b/scripts/jobs/cron_tasks.inc.http.10.apache.php @@ -722,16 +722,7 @@ class apache extends HttpConfigBase // The normal access/error - logging is enabled $error_log = makeCorrectFile(Settings::Get('system.logfiles_directory') . $domain['loginname'] . $speciallogfile . '-error.log'); - // Create the logfile if it does not exist (fixes #46) - touch($error_log); - chown($error_log, Settings::Get('system.httpuser')); - chgrp($error_log, Settings::Get('system.httpgroup')); - $access_log = makeCorrectFile(Settings::Get('system.logfiles_directory') . $domain['loginname'] . $speciallogfile . '-access.log'); - // Create the logfile if it does not exist (fixes #46) - touch($access_log); - chown($access_log, Settings::Get('system.httpuser')); - chgrp($access_log, Settings::Get('system.httpgroup')); $logtype = 'combined'; if (Settings::Get('system.logfiles_format') != '') { @@ -743,9 +734,32 @@ class apache extends HttpConfigBase } if (Settings::Get('system.logfiles_piped') == '1') { - $logfiles_text .= ' ErrorLog "|' . str_replace('{LOGFILE}', $error_log, Settings::Get('system.logfiles_directory')) . "\"\n"; - $logfiles_text .= ' CustomLog "|' . str_replace('{LOGFILE}', $access_log, Settings::Get('system.logfiles_directory')) . '" ' . $logtype . "\n"; + // don't use custom-script as path for logfile-names + $error_log = makeCorrectFile($domain['loginname'] . $speciallogfile . '-error.log'); + $access_log = makeCorrectFile($domain['loginname'] . $speciallogfile . '-access.log'); + // replace for error_log + $command = replace_variables(Settings::Get('system.logfiles_directory'), array( + 'LOGFILE' => $error_log, + 'DOMAIN' => $domain['domain'], + 'CUSTOMER' => $domain['loginname'] + )); + $logfiles_text .= ' ErrorLog "| ' . $command . "\"\n"; + // replace for access_log + $command = replace_variables(Settings::Get('system.logfiles_directory'), array( + 'LOGFILE' => $access_log, + 'DOMAIN' => $domain['domain'], + 'CUSTOMER' => $domain['loginname'] + )); + $logfiles_text .= ' CustomLog "| ' . $command . '" ' . $logtype . "\n"; } else { + // Create the logfile if it does not exist (fixes #46) + touch($error_log); + chown($error_log, Settings::Get('system.httpuser')); + chgrp($error_log, Settings::Get('system.httpgroup')); + touch($access_log); + chown($access_log, Settings::Get('system.httpuser')); + chgrp($access_log, Settings::Get('system.httpgroup')); + $logfiles_text .= ' ErrorLog "' . $error_log . '"' . "\n"; $logfiles_text .= ' CustomLog "' . $access_log . '" ' . $logtype . "\n"; } From f8236dff7be5b8ece933943442ce5a4adc284387 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Thu, 24 May 2018 14:44:59 +0200 Subject: [PATCH 186/746] fix not quite correctly webserver-log-piping Signed-off-by: Michael Kaufmann --- actions/admin/settings/130.webserver.php | 16 ++++++++++++++-- install/froxlor.sql | 3 ++- install/updates/froxlor/0.9/update_0.9.inc.php | 9 +++++++++ lib/version.inc.php | 2 +- lng/english.lng.php | 4 ++-- lng/german.lng.php | 4 ++-- scripts/jobs/cron_tasks.inc.http.10.apache.php | 9 +++------ 7 files changed, 33 insertions(+), 14 deletions(-) diff --git a/actions/admin/settings/130.webserver.php b/actions/admin/settings/130.webserver.php index 35ac4857..ad56665b 100644 --- a/actions/admin/settings/130.webserver.php +++ b/actions/admin/settings/130.webserver.php @@ -104,14 +104,26 @@ return array( 'save_method' => 'storeSettingField' ), 'system_logfiles_directory' => array( - 'label' => (Settings::Get('system.webserver') != 'apache2') ? $lng['serversettings']['logfiles_directory'] : $lng['serversettings']['logfiles_directory2'], + 'label' => $lng['serversettings']['logfiles_directory'], 'settinggroup' => 'system', 'varname' => 'logfiles_directory', 'type' => 'string', - 'string_type' => (Settings::Get('system.webserver') != 'apache2') ? 'dir' : '', + 'string_type' => 'dir', 'default' => '/var/customers/logs/', 'save_method' => 'storeSettingField' ), + 'system_logfiles_script' => array( + 'label' => $lng['serversettings']['logfiles_script'], + 'settinggroup' => 'system', + 'varname' => 'logfiles_script', + 'type' => 'string', + 'string_type' => '', + 'default' => '', + 'save_method' => 'storeSettingField', + 'websrv_avail' => array( + 'apache2' + ) + ), 'system_logfiles_format' => array( 'label' => $lng['serversettings']['logfiles_format'], 'settinggroup' => 'system', diff --git a/install/froxlor.sql b/install/froxlor.sql index bda0e4d9..f39913fb 100644 --- a/install/froxlor.sql +++ b/install/froxlor.sql @@ -659,6 +659,7 @@ opcache.interned_strings_buffer'), ('system', 'logfiles_format', ''), ('system', 'logfiles_type', '1'), ('system', 'logfiles_piped', '0'), + ('system', 'logfiles_script', ''), ('panel', 'decimal_places', '4'), ('panel', 'adminmail', 'admin@SERVERNAME'), ('panel', 'phpmyadmin_url', ''), @@ -691,7 +692,7 @@ opcache.interned_strings_buffer'), ('panel', 'password_special_char', '!?<>§$%+#=@'), ('panel', 'customer_hide_options', ''), ('panel', 'version', '0.9.39.5'), - ('panel', 'db_version', '201805240'); + ('panel', 'db_version', '201805241'); DROP TABLE IF EXISTS `panel_tasks`; diff --git a/install/updates/froxlor/0.9/update_0.9.inc.php b/install/updates/froxlor/0.9/update_0.9.inc.php index c1a9010e..e5e3c60e 100644 --- a/install/updates/froxlor/0.9/update_0.9.inc.php +++ b/install/updates/froxlor/0.9/update_0.9.inc.php @@ -3959,3 +3959,12 @@ if (isDatabaseVersion('201802250')) { updateToDbVersion('201805240'); } + +if (isDatabaseVersion('201805240')) { + + showUpdateStep("Adding webserver logfile-script settings"); + Settings::AddNew("system.logfiles_script", ''); + lastStepStatus(0); + + updateToDbVersion('201805241'); +} diff --git a/lib/version.inc.php b/lib/version.inc.php index 65278766..3f870d38 100644 --- a/lib/version.inc.php +++ b/lib/version.inc.php @@ -19,7 +19,7 @@ $version = '0.9.39.5'; // Database version (YYYYMMDDC where C is a daily counter) -$dbversion = '201805240'; +$dbversion = '201805241'; // Distribution branding-tag (used for Debian etc.) $branding = ''; diff --git a/lng/english.lng.php b/lng/english.lng.php index 2c068780..6a6ffe92 100644 --- a/lng/english.lng.php +++ b/lng/english.lng.php @@ -337,8 +337,8 @@ $lng['serversettings']['documentroot_prefix']['title'] = 'Home directory'; $lng['serversettings']['documentroot_prefix']['description'] = 'Where should all home directories be stored?'; $lng['serversettings']['logfiles_directory']['title'] = 'Logfiles directory'; $lng['serversettings']['logfiles_directory']['description'] = 'Where should all log files be stored?'; -$lng['serversettings']['logfiles_directory2']['title'] = 'Logfiles directory or custom script'; -$lng['serversettings']['logfiles_directory2']['description'] = 'Where should all log files be stored? Optionally, you can specify a script here and use the placeholders {LOGFILE}, {DOMAIN} and {CUSTOMER} if needed. In case of a custom script you will need to activate the Pipe webserver logfiles option'; +$lng['serversettings']['logfiles_script']['title'] = 'Custom script to pipe log-files to'; +$lng['serversettings']['logfiles_script']['description'] = 'You can specify a script here and use the placeholders {LOGFILE}, {DOMAIN} and {CUSTOMER} if needed. In case you want to use it you will need to activate the Pipe webserver logfiles option too. No prefixed pipe-character is needed.'; $lng['serversettings']['logfiles_format']['title'] = 'Access-log format'; $lng['serversettings']['logfiles_format']['description'] = 'Enter a custom log-format here according to your webservers specifications, leave empty for default'; $lng['serversettings']['logfiles_type']['title'] = 'Access-log type'; diff --git a/lng/german.lng.php b/lng/german.lng.php index 87fd914e..1c2936af 100644 --- a/lng/german.lng.php +++ b/lng/german.lng.php @@ -333,8 +333,8 @@ $lng['serversettings']['documentroot_prefix']['title'] = 'Heimatverzeichnis'; $lng['serversettings']['documentroot_prefix']['description'] = 'Wo sollen die Heimatverzeichnisse der Kunden liegen?'; $lng['serversettings']['logfiles_directory']['title'] = 'Webserver-Logdateien-Verzeichnis'; $lng['serversettings']['logfiles_directory']['description'] = 'Wo sollen die Logdateien des Webservers liegen?'; -$lng['serversettings']['logfiles_directory2']['title'] = 'Webserver-Logdateien-Verzeichnis oder eigenes Script'; -$lng['serversettings']['logfiles_directory2']['description'] = 'Wo sollen die Logdateien des Webservers liegen? Optional kann hier ein Script hinterlegt und die Platzhalter {LOGFILE}, {DOMAIN} und {CUSTOMER} genutzt werden, sofern nötig. Falls ein Script angegeben wird, muss die Option Webserver Logdateien umleiten gesetzt werden'; +$lng['serversettings']['logfiles_script']['title'] = 'Eigenes Script zu dem Log-Files übergeben werden'; +$lng['serversettings']['logfiles_script']['description'] = 'Hier kann ein Script an das die Loginhalte übergeben werden hinterlegt und die Platzhalter {LOGFILE}, {DOMAIN} und {CUSTOMER} genutzt werden, sofern nötig. Falls ein Script angegeben wird, muss die Option Webserver Logdateien umleiten gesetzt werden'; $lng['serversettings']['logfiles_format']['title'] = 'Access-Log Format'; $lng['serversettings']['logfiles_format']['description'] = 'Hier kann ein angepasstes Log-format entsprechend der Webserver-Dokumentation angegeben werden, leer lassen für Standard'; $lng['serversettings']['logfiles_type']['title'] = 'Access-Log Typ'; diff --git a/scripts/jobs/cron_tasks.inc.http.10.apache.php b/scripts/jobs/cron_tasks.inc.http.10.apache.php index 9dab54a5..4294ecf5 100644 --- a/scripts/jobs/cron_tasks.inc.http.10.apache.php +++ b/scripts/jobs/cron_tasks.inc.http.10.apache.php @@ -733,19 +733,16 @@ class apache extends HttpConfigBase $logtype = 'vhost_combined'; } - if (Settings::Get('system.logfiles_piped') == '1') { - // don't use custom-script as path for logfile-names - $error_log = makeCorrectFile($domain['loginname'] . $speciallogfile . '-error.log'); - $access_log = makeCorrectFile($domain['loginname'] . $speciallogfile . '-access.log'); + if (Settings::Get('system.logfiles_piped') == '1' && Settings::Get('system.logfiles_script') != '') { // replace for error_log - $command = replace_variables(Settings::Get('system.logfiles_directory'), array( + $command = replace_variables(Settings::Get('system.logfiles_script'), array( 'LOGFILE' => $error_log, 'DOMAIN' => $domain['domain'], 'CUSTOMER' => $domain['loginname'] )); $logfiles_text .= ' ErrorLog "| ' . $command . "\"\n"; // replace for access_log - $command = replace_variables(Settings::Get('system.logfiles_directory'), array( + $command = replace_variables(Settings::Get('system.logfiles_script'), array( 'LOGFILE' => $access_log, 'DOMAIN' => $domain['domain'], 'CUSTOMER' => $domain['loginname'] From 18fb422a699254e350bea40ca44a0fee93ca5641 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Thu, 24 May 2018 14:50:52 +0200 Subject: [PATCH 187/746] correct order of settings Signed-off-by: Michael Kaufmann --- actions/admin/settings/130.webserver.php | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/actions/admin/settings/130.webserver.php b/actions/admin/settings/130.webserver.php index ad56665b..4caa875f 100644 --- a/actions/admin/settings/130.webserver.php +++ b/actions/admin/settings/130.webserver.php @@ -124,6 +124,17 @@ return array( 'apache2' ) ), + 'system_logfiles_piped' => array( + 'label' => $lng['serversettings']['logfiles_piped'], + 'settinggroup' => 'system', + 'varname' => 'logfiles_piped', + 'type' => 'bool', + 'default' => false, + 'save_method' => 'storeSettingField', + 'websrv_avail' => array( + 'apache2' + ) + ), 'system_logfiles_format' => array( 'label' => $lng['serversettings']['logfiles_format'], 'settinggroup' => 'system', @@ -153,17 +164,6 @@ return array( 'apache2' ) ), - 'system_logfiles_piped' => array( - 'label' => $lng['serversettings']['logfiles_piped'], - 'settinggroup' => 'system', - 'varname' => 'logfiles_piped', - 'type' => 'bool', - 'default' => false, - 'save_method' => 'storeSettingField', - 'websrv_avail' => array( - 'apache2' - ) - ), 'system_customersslpath' => array( 'label' => $lng['serversettings']['customerssl_directory'], 'settinggroup' => 'system', From 10330f8a7ab99fe56d022eb5d14964199fe7658e Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Thu, 24 May 2018 17:44:36 +0200 Subject: [PATCH 188/746] fix fallback redirect code when customredirect is enabled and default is selected Signed-off-by: Michael Kaufmann --- lib/functions/output/function.RedirectCode.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/functions/output/function.RedirectCode.php b/lib/functions/output/function.RedirectCode.php index 5a8cd57d..79c7528c 100644 --- a/lib/functions/output/function.RedirectCode.php +++ b/lib/functions/output/function.RedirectCode.php @@ -72,7 +72,8 @@ function getDomainRedirectCode($domainid = 0) { $default = '301'; if (Settings::Get('customredirect.enabled') == '1') { $all_codes = getRedirectCodes(false); - $default = $all_codes[Settings::Get('customredirect.default')]; + $_default = $all_codes[Settings::Get('customredirect.default')]; + $default = ($_default == '---') ? $default : $_default; } $code = $default; if ($domainid > 0) { From c1e62e6be719affc003774a639de5c952ffd8ffc Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Tue, 29 May 2018 15:47:41 +0200 Subject: [PATCH 189/746] get rid of serialization completely to avoid possible code execution, fixes #555 Signed-off-by: Michael Kaufmann --- admin_domains.php | 28 ++++++++--------- customer_extras.php | 4 +-- dns_editor.php | 8 ++--- install/froxlor.sql | 2 +- install/lib/class.FroxlorInstall.php | 19 ++++++------ install/lng/english.lng.php | 1 - install/lng/german.lng.php | 1 - .../updates/froxlor/0.9/update_0.9.inc.php | 30 +++++++++++++++++++ lib/classes/output/class.paging.php | 4 +-- .../froxlor/function.CronjobFunctions.php | 2 +- lib/functions/froxlor/function.inserttask.php | 10 +++---- scripts/jobs/cron_backup.php | 2 +- scripts/jobs/cron_tasks.php | 2 +- 13 files changed, 71 insertions(+), 42 deletions(-) diff --git a/admin_domains.php b/admin_domains.php index f731d59c..c2804a16 100644 --- a/admin_domains.php +++ b/admin_domains.php @@ -528,7 +528,7 @@ if ($page == 'domains' || $page == 'overview') { $ipandports = array(); if (isset($_POST['ipandport']) && ! is_array($_POST['ipandport'])) { - $_POST['ipandport'] = unserialize($_POST['ipandport']); + $_POST['ipandport'] = json_decode($_POST['ipandport'], true); } if (isset($_POST['ipandport']) && is_array($_POST['ipandport'])) { @@ -564,7 +564,7 @@ if ($page == 'domains' || $page == 'overview') { $ssl_ipandports = array(); if (isset($_POST['ssl_ipandport']) && ! is_array($_POST['ssl_ipandport'])) { - $_POST['ssl_ipandport'] = unserialize($_POST['ssl_ipandport']); + $_POST['ssl_ipandport'] = json_decode($_POST['ssl_ipandport'], true); } // Verify SSL-Ports @@ -606,7 +606,7 @@ if ($page == 'domains' || $page == 'overview') { $ssl_redirect = 0; $letsencrypt = 0; $http2 = 0; - // we need this for the serialize + // we need this for the json-encode // if ssl is disabled or no ssl-ip/port exists $ssl_ipandports[] = - 1; @@ -622,7 +622,7 @@ if ($page == 'domains' || $page == 'overview') { $ssl_redirect = 0; $letsencrypt = 0; $http2 = 0; - // we need this for the serialize + // we need this for the json-encode // if ssl is disabled or no ssl-ip/port exists $ssl_ipandports[] = - 1; @@ -692,7 +692,7 @@ if ($page == 'domains' || $page == 'overview') { } if (count($ssl_ipandports) == 0) { - // we need this for the serialize + // we need this for the json-encode // if ssl is disabled or no ssl-ip/port exists $ssl_ipandports[] = - 1; } @@ -794,9 +794,9 @@ if ($page == 'domains' || $page == 'overview') { 'dkim' => $dkim, 'speciallogfile' => $speciallogfile, 'selectserveralias' => $serveraliasoption, - 'ipandport' => serialize($ipandports), + 'ipandport' => json_encode($ipandports), 'ssl_redirect' => $ssl_redirect, - 'ssl_ipandport' => serialize($ssl_ipandports), + 'ssl_ipandport' => json_encode($ssl_ipandports), 'phpenabled' => $phpenabled, 'openbasedir' => $openbasedir, 'phpsettingid' => $phpsettingid, @@ -1420,7 +1420,7 @@ if ($page == 'domains' || $page == 'overview') { $ipandports = array(); if (isset($_POST['ipandport']) && ! is_array($_POST['ipandport'])) { - $_POST['ipandport'] = unserialize($_POST['ipandport']); + $_POST['ipandport'] = json_decode($_POST['ipandport'], true); } if (isset($_POST['ipandport']) && is_array($_POST['ipandport'])) { @@ -1466,7 +1466,7 @@ if ($page == 'domains' || $page == 'overview') { $ssl_ipandports = array(); if (isset($_POST['ssl_ipandport']) && ! is_array($_POST['ssl_ipandport'])) { - $_POST['ssl_ipandport'] = unserialize($_POST['ssl_ipandport']); + $_POST['ssl_ipandport'] = json_decode($_POST['ssl_ipandport'], true); } if (isset($_POST['ssl_ipandport']) && is_array($_POST['ssl_ipandport'])) { @@ -1494,7 +1494,7 @@ if ($page == 'domains' || $page == 'overview') { $ssl_redirect = 0; $letsencrypt = 0; $http2 = 0; - // we need this for the serialize + // we need this for the json-encode // if ssl is disabled or no ssl-ip/port exists $ssl_ipandports[] = - 1; @@ -1510,7 +1510,7 @@ if ($page == 'domains' || $page == 'overview') { $ssl_redirect = 0; $letsencrypt = 0; $http2 = 0; - // we need this for the serialize + // we need this for the json-encode // if ssl is disabled or no ssl-ip/port exists $ssl_ipandports[] = - 1; @@ -1603,7 +1603,7 @@ if ($page == 'domains' || $page == 'overview') { } if (count($ssl_ipandports) == 0) { - // we need this for the serialize + // we need this for the json-encode // if ssl is disabled or no ssl-ip/port exists $ssl_ipandports[] = - 1; } @@ -1668,8 +1668,8 @@ if ($page == 'domains' || $page == 'overview') { 'issubof' => $issubof, 'speciallogfile' => $speciallogfile, 'speciallogverified' => $speciallogverified, - 'ipandport' => serialize($ipandports), - 'ssl_ipandport' => serialize($ssl_ipandports), + 'ipandport' => json_encode($ipandports), + 'ssl_ipandport' => json_encode($ssl_ipandports), 'letsencrypt' => $letsencrypt, 'http2' => $http2, 'hsts_maxage' => $hsts_maxage, diff --git a/customer_extras.php b/customer_extras.php index 5800805d..5ef3cb61 100644 --- a/customer_extras.php +++ b/customer_extras.php @@ -563,7 +563,7 @@ if ($page == 'overview') { $existing_backupJob = null; while ($entry = $sel_stmt->fetch()) { - $data = unserialize($entry['data']); + $data = json_decode($entry['data'], true); if ($data['customerid'] == $userinfo['customerid']) { $existing_backupJob = $entry; break; @@ -613,7 +613,7 @@ if ($page == 'overview') { if (!empty($existing_backupJob)) { $action = "abort"; - $row = unserialize($entry['data']); + $row = json_decode($entry['data'], true); $row['path'] = makeCorrectDir(str_replace($userinfo['documentroot'], "/", $row['destdir'])); $row['backup_web'] = ($row['backup_web'] == '1') ? $lng['panel']['yes'] : $lng['panel']['no']; $row['backup_mail'] = ($row['backup_mail'] == '1') ? $lng['panel']['yes'] : $lng['panel']['no']; diff --git a/dns_editor.php b/dns_editor.php index 98a4d683..75b5978e 100644 --- a/dns_editor.php +++ b/dns_editor.php @@ -208,7 +208,7 @@ if ($action == 'add_record' && ! empty($_POST)) { // check for duplicate foreach ($dom_entries as $existing_entry) { - // compare serialized string of array + // compare json-encoded string of array $check_entry = $existing_entry; // new entry has no ID yet unset($check_entry['id']); @@ -218,9 +218,9 @@ if ($action == 'add_record' && ! empty($_POST)) { $check_entry['prio'] = (int) $check_entry['prio']; $check_entry['ttl'] = (int) $check_entry['ttl']; $check_entry['domain_id'] = (int) $check_entry['domain_id']; - // serialize both - $check_entry = serialize($check_entry); - $new = serialize($new_entry); + // encode both + $check_entry = json_encode($check_entry); + $new = json_encode($new_entry); // compare if ($check_entry === $new) { $errors[] = $lng['error']['dns_duplicate_entry']; diff --git a/install/froxlor.sql b/install/froxlor.sql index f39913fb..f445a0e9 100644 --- a/install/froxlor.sql +++ b/install/froxlor.sql @@ -692,7 +692,7 @@ opcache.interned_strings_buffer'), ('panel', 'password_special_char', '!?<>§$%+#=@'), ('panel', 'customer_hide_options', ''), ('panel', 'version', '0.9.39.5'), - ('panel', 'db_version', '201805241'); + ('panel', 'db_version', '201805290'); DROP TABLE IF EXISTS `panel_tasks`; diff --git a/install/lib/class.FroxlorInstall.php b/install/lib/class.FroxlorInstall.php index 1cbbd7ca..598bf66a 100644 --- a/install/lib/class.FroxlorInstall.php +++ b/install/lib/class.FroxlorInstall.php @@ -1015,6 +1015,16 @@ class FroxlorInstall $content .= $this->_status_message('green', $this->_lng['requirements']['installed']); } + // check for json extension + $content .= $this->_status_message('begin', $this->_lng['requirements']['phpjson']); + + if (! extension_loaded('json')) { + $content .= $this->_status_message('red', $this->_lng['requirements']['notinstalled']); + $_die = true; + } else { + $content .= $this->_status_message('green', $this->_lng['requirements']['installed']); + } + // check for bcmath extension $content .= $this->_status_message('begin', $this->_lng['requirements']['phpbcmath']); @@ -1033,15 +1043,6 @@ class FroxlorInstall $content .= $this->_status_message('green', $this->_lng['requirements']['installed']); } - // check for json extension - $content .= $this->_status_message('begin', $this->_lng['requirements']['phpjson']); - - if (! extension_loaded('json')) { - $content .= $this->_status_message('orange', $this->_lng['requirements']['notinstalled'] . "
    " . $this->_lng['requirements']['jsondescription']); - } else { - $content .= $this->_status_message('green', $this->_lng['requirements']['installed']); - } - // check for open_basedir $content .= $this->_status_message('begin', $this->_lng['requirements']['openbasedir']); $php_ob = @ini_get("open_basedir"); diff --git a/install/lng/english.lng.php b/install/lng/english.lng.php index 8651bb83..a4cea2dc 100644 --- a/install/lng/english.lng.php +++ b/install/lng/english.lng.php @@ -38,7 +38,6 @@ $lng['requirements']['phpzip'] = 'PHP zip-extension...'; $lng['requirements']['phpjson'] = 'PHP json-extension...'; $lng['requirements']['bcmathdescription'] = 'Traffic-calculation related functions will not work correctly!'; $lng['requirements']['zipdescription'] = 'The auto-update feature requires the zip extension.'; -$lng['requirements']['jsondescription'] = 'The settings import/export feature requires the json extension.'; $lng['requirements']['openbasedir'] = 'open_basedir...'; $lng['requirements']['openbasedirenabled'] = 'Froxlor will not work properly with open_basedir enabled. Please disable open_basedir for Froxlor in the coresponding php.ini'; $lng['requirements']['diedbecauseofrequirements'] = 'Cannot install Froxlor without these requirements! Try to fix them and retry.'; diff --git a/install/lng/german.lng.php b/install/lng/german.lng.php index d18e94c0..4e4638db 100644 --- a/install/lng/german.lng.php +++ b/install/lng/german.lng.php @@ -38,7 +38,6 @@ $lng['requirements']['phpzip'] = 'PHP zip-Erweiterung...'; $lng['requirements']['phpjson'] = 'PHP json-Erweiterung...'; $lng['requirements']['bcmathdescription'] = 'Traffic-Berechnungs bezogene Funktionen stehen nicht vollständig zur Verfügung!'; $lng['requirements']['zipdescription'] = 'Die Auto-Update Funktion benötigt die zip Erweiterung.'; -$lng['requirements']['jsondescription'] = 'Die Einstellungen Import/Export Funktion benötigt die json Erweiterung.'; $lng['requirements']['openbasedir'] = 'open_basedir genutzt wird...'; $lng['requirements']['openbasedirenabled'] = 'Froxlor wird mit aktiviertem open_basedir nicht vollständig funktionieren. Bitte deaktivieren Sie open_basedir für Froxlor in der entsprechenden php.ini'; $lng['requirements']['diedbecauseofrequirements'] = 'Kann Froxlor ohne diese Voraussetzungen nicht installieren! Beheben Sie die angezeigten Probleme und versuchen Sie es erneut.'; diff --git a/install/updates/froxlor/0.9/update_0.9.inc.php b/install/updates/froxlor/0.9/update_0.9.inc.php index e5e3c60e..17432b94 100644 --- a/install/updates/froxlor/0.9/update_0.9.inc.php +++ b/install/updates/froxlor/0.9/update_0.9.inc.php @@ -3968,3 +3968,33 @@ if (isDatabaseVersion('201805240')) { updateToDbVersion('201805241'); } + +if (isDatabaseVersion('201805241')) { + + $do_update = true; + showUpdateStep("Checking for required PHP json-extension"); + if (! extension_loaded('json')) { + $do_update = false; + lastStepStatus(2, 'not installed'); + } else { + lastStepStatus(0); + + showUpdateStep("Checking for current cronjobs that need converting"); + $result_tasks_stmt = Database::query(" + SELECT * FROM `" . TABLE_PANEL_TASKS . "` ORDER BY `id` ASC + "); + $upd_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_TASKS . "` SET `data` = :data WHERE `id` = :taskid"); + while ($row = $result_tasks_stmt->fetch(PDO::FETCH_ASSOC)) { + if (! empty($row['data'])) { + $data = unserialize($row['data']); + Database::pexecute($upd_stmt, array( + 'data' => json_encode($data), + 'taskid' => $row['id'] + )); + } + } + lastStepStatus(0); + + updateToDbVersion('201805290'); + } +} diff --git a/lib/classes/output/class.paging.php b/lib/classes/output/class.paging.php index 8ee2bc25..64643b7d 100644 --- a/lib/classes/output/class.paging.php +++ b/lib/classes/output/class.paging.php @@ -114,7 +114,7 @@ class paging { $this->userinfo = $userinfo; if (!is_array($this->userinfo['lastpaging'])) { - $this->userinfo['lastpaging'] = unserialize($this->userinfo['lastpaging']); + $this->userinfo['lastpaging'] = json_decode($this->userinfo['lastpaging'], true); } $this->table = $table; @@ -224,7 +224,7 @@ class paging { AND `adminsession` = :adminsession "); $upd_data = array( - 'lastpaging' => serialize($this->userinfo['lastpaging']), + 'lastpaging' => json_encode($this->userinfo['lastpaging']), 'hash' => $userinfo['hash'], 'userid' => $userinfo['userid'], 'ipaddr' => $userinfo['ipaddress'], diff --git a/lib/functions/froxlor/function.CronjobFunctions.php b/lib/functions/froxlor/function.CronjobFunctions.php index a2c09785..0b5f80ff 100644 --- a/lib/functions/froxlor/function.CronjobFunctions.php +++ b/lib/functions/froxlor/function.CronjobFunctions.php @@ -63,7 +63,7 @@ function getOutstandingTasks() { while ($row = $result->fetch(PDO::FETCH_ASSOC)) { if ($row['data'] != '') { - $row['data'] = unserialize($row['data']); + $row['data'] = json_decode($row['data'], true); } // rebuilding webserver-configuration diff --git a/lib/functions/froxlor/function.inserttask.php b/lib/functions/froxlor/function.inserttask.php index 3f2cdbde..705bf77e 100644 --- a/lib/functions/froxlor/function.inserttask.php +++ b/lib/functions/froxlor/function.inserttask.php @@ -70,7 +70,7 @@ function inserttask($type, $param1 = '', $param2 = '', $param3 = '', $param4 = ' $data['uid'] = $param2; $data['gid'] = $param3; $data['store_defaultindex'] = $param4; - $data = serialize($data); + $data = json_encode($data); Database::pexecute($ins_stmt, array('type' => '2', 'data' => $data)); } elseif ($type == '6' @@ -78,7 +78,7 @@ function inserttask($type, $param1 = '', $param2 = '', $param3 = '', $param4 = ' ) { $data = array(); $data['loginname'] = $param1; - $data = serialize($data); + $data = json_encode($data); Database::pexecute($ins_stmt, array('type' => '6', 'data' => $data)); } elseif ($type == '7' @@ -88,7 +88,7 @@ function inserttask($type, $param1 = '', $param2 = '', $param3 = '', $param4 = ' $data = array(); $data['loginname'] = $param1; $data['email'] = $param2; - $data = serialize($data); + $data = json_encode($data); Database::pexecute($ins_stmt, array('type' => '7', 'data' => $data)); } elseif ($type == '8' @@ -98,13 +98,13 @@ function inserttask($type, $param1 = '', $param2 = '', $param3 = '', $param4 = ' $data = array(); $data['loginname'] = $param1; $data['homedir'] = $param2; - $data = serialize($data); + $data = json_encode($data); Database::pexecute($ins_stmt, array('type' => '8', 'data' => $data)); } elseif ($type == '20' && is_array($param1) ) { - $data = serialize($param1); + $data = json_encode($param1); Database::pexecute($ins_stmt, array('type' => '20', 'data' => $data)); } } diff --git a/scripts/jobs/cron_backup.php b/scripts/jobs/cron_backup.php index b01fe1ed..131a2004 100644 --- a/scripts/jobs/cron_backup.php +++ b/scripts/jobs/cron_backup.php @@ -79,7 +79,7 @@ $all_jobs = $result_tasks_stmt->fetchAll(); foreach ($all_jobs as $row) { if ($row['data'] != '') { - $row['data'] = unserialize($row['data']); + $row['data'] = json_decode($row['data'], true); } if (is_array($row['data'])) { diff --git a/scripts/jobs/cron_tasks.php b/scripts/jobs/cron_tasks.php index b7176a53..48cbbb66 100644 --- a/scripts/jobs/cron_tasks.php +++ b/scripts/jobs/cron_tasks.php @@ -43,7 +43,7 @@ while ($row = $result_tasks_stmt->fetch(PDO::FETCH_ASSOC)) { $resultIDs[] = $row['id']; if ($row['data'] != '') { - $row['data'] = unserialize($row['data']); + $row['data'] = json_decode($row['data'], true); } /** From b80bdcbc4fe6149cb696944e25433a8479ca13d4 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Tue, 29 May 2018 15:54:44 +0200 Subject: [PATCH 190/746] forgot to add version file Signed-off-by: Michael Kaufmann --- lib/version.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/version.inc.php b/lib/version.inc.php index 3f870d38..eb1a527a 100644 --- a/lib/version.inc.php +++ b/lib/version.inc.php @@ -19,7 +19,7 @@ $version = '0.9.39.5'; // Database version (YYYYMMDDC where C is a daily counter) -$dbversion = '201805241'; +$dbversion = '201805290'; // Distribution branding-tag (used for Debian etc.) $branding = ''; From 72835c56ada3f86403c740631d9644c756782242 Mon Sep 17 00:00:00 2001 From: Daniel Reichelt Date: Tue, 5 Jun 2018 06:51:10 +0200 Subject: [PATCH 191/746] trim trailing whitespace --- admin_domains.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/admin_domains.php b/admin_domains.php index c2804a16..b12ca648 100644 --- a/admin_domains.php +++ b/admin_domains.php @@ -1124,7 +1124,6 @@ if ($page == 'domains' || $page == 'overview') { } } } elseif ($action == 'edit' && $id != 0) { - $result_stmt = Database::prepare(" SELECT `d`.*, `c`.`customerid` FROM `" . TABLE_PANEL_DOMAINS . "` `d` @@ -2250,12 +2249,12 @@ if ($page == 'domains' || $page == 'overview') { } } } elseif ($action == 'jqGetCustomerPHPConfigs') { - + $customerid = intval($_POST['customerid']); $allowed_phpconfigs = getCustomerDetail($customerid, 'allowed_phpconfigs'); echo !empty($allowed_phpconfigs) ? $allowed_phpconfigs : json_encode(array()); exit; - + } elseif ($action == 'import') { if (isset($_POST['send']) && $_POST['send'] == 'send') { From 2ba4137e7da5917db676b674d412fe2f39ce0269 Mon Sep 17 00:00:00 2001 From: Daniel Reichelt Date: Tue, 5 Jun 2018 06:51:20 +0200 Subject: [PATCH 192/746] fix triggering an LE CSR when changing www on a main domain Prior to this, LE CSRs were triggered only when the wwwserveralias was changed on alias domains, but not on main domains. Fixes #526 --- admin_domains.php | 8 ++++++++ customer_domains.php | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/admin_domains.php b/admin_domains.php index b12ca648..00ab4143 100644 --- a/admin_domains.php +++ b/admin_domains.php @@ -2027,7 +2027,15 @@ if ($page == 'domains' || $page == 'overview') { } else if ($result['wwwserveralias'] != $wwwserveralias || $result['letsencrypt'] != $letsencrypt) { // or when wwwserveralias or letsencrypt was changed + triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $log); + + if ($aliasdomain === 0) { + // in case the wwwserveralias is set on a main domain, $aliasdomain is 0 + // --> the call just above to triggerLetsEncryptCSRForAliasDestinationDomain + // is a noop...let's repeat it with the domain id of the main domain + triggerLetsEncryptCSRForAliasDestinationDomain($id, $log); + } } $log->logAction(ADM_ACTION, LOG_INFO, "edited domain #" . $id); diff --git a/customer_domains.php b/customer_domains.php index 23bbb07f..a84b2366 100644 --- a/customer_domains.php +++ b/customer_domains.php @@ -773,7 +773,15 @@ if ($page == 'overview') { triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $log); } elseif ($result['wwwserveralias'] != $wwwserveralias || $result['letsencrypt'] != $letsencrypt) { // or when wwwserveralias or letsencrypt was changed + triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $log); + + if ($aliasdomain === 0) { + // in case the wwwserveralias is set on a main domain, $aliasdomain is 0 + // --> the call just above to triggerLetsEncryptCSRForAliasDestinationDomain + // is a noop...let's repeat it with the domain id of the main domain + triggerLetsEncryptCSRForAliasDestinationDomain($id, $log); + } } // check whether LE has been disabled, so we remove the certificate From 06ef81cc5bd3dd34898abbc05cef04f6e4547ab4 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Sun, 17 Jun 2018 09:41:31 +0200 Subject: [PATCH 193/746] adjust year in copyright in std-customer-index file, thx to demlak Signed-off-by: Michael Kaufmann --- templates/misc/standardcustomer/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/misc/standardcustomer/index.html b/templates/misc/standardcustomer/index.html index de82cf3b..109e2d88 100644 --- a/templates/misc/standardcustomer/index.html +++ b/templates/misc/standardcustomer/index.html @@ -57,7 +57,7 @@ From aa881560cc996c38cbf8c20ee62854e27f72c73c Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Tue, 19 Jun 2018 21:46:11 +0200 Subject: [PATCH 194/746] deny access to tickets not owned by current user, thx to chbi Signed-off-by: Michael Kaufmann --- customer_tickets.php | 30 +- lib/classes/ticket/class.ticket.php | 489 ++++++++++++++-------------- 2 files changed, 273 insertions(+), 246 deletions(-) diff --git a/customer_tickets.php b/customer_tickets.php index 1a7508b9..d237ac06 100644 --- a/customer_tickets.php +++ b/customer_tickets.php @@ -238,7 +238,11 @@ if ($page == 'overview') { } } elseif ($action == 'answer' && $id != 0) { if (isset($_POST['send']) && $_POST['send'] == 'send') { - $replyticket = ticket::getInstanceOf($userinfo, -1); + try { + $replyticket = ticket::getInstanceOf($userinfo, -1); + } catch(Exception $e) { + standard_error($e->getMessage()); + } $replyticket->Set('subject', validate($_POST['subject'], 'subject'), true, false); $replyticket->Set('priority', validate($_POST['priority'], 'priority'), true, false); $replyticket->Set('message', validate(str_replace("\r\n", "\n", $_POST['message']), 'message', '/^[^\0]*$/'), true, false); @@ -272,7 +276,11 @@ if ($page == 'overview') { } } else { $ticket_replies = ''; - $mainticket = ticket::getInstanceOf($userinfo, (int)$id); + try { + $mainticket = ticket::getInstanceOf($userinfo, (int)$id); + } catch(Exception $e) { + standard_error($e->getMessage()); + } $dt = date("d.m.Y H:i\h", $mainticket->Get('dt')); $status = ticket::getStatusText($lng, $mainticket->Get('status')); @@ -351,7 +359,11 @@ if ($page == 'overview') { } elseif ($action == 'close' && $id != 0) { if (isset($_POST['send']) && $_POST['send'] == 'send') { $now = time(); - $mainticket = ticket::getInstanceOf($userinfo, (int)$id); + try { + $mainticket = ticket::getInstanceOf($userinfo, (int)$id); + } catch(Exception $e) { + standard_error($e->getMessage()); + } $mainticket->Set('lastchange', $now, true, true); $mainticket->Set('lastreplier', '0', true, true); $mainticket->Set('status', '3', true, true); @@ -359,7 +371,11 @@ if ($page == 'overview') { $log->logAction(USR_ACTION, LOG_NOTICE, "closed support-ticket '" . $mainticket->Get('subject') . "'"); redirectTo($filename, array('page' => $page, 's' => $s)); } else { - $mainticket = ticket::getInstanceOf($userinfo, (int)$id); + try { + $mainticket = ticket::getInstanceOf($userinfo, (int)$id); + } catch(Exception $e) { + standard_error($e->getMessage()); + } ask_yesno('ticket_reallyclose', $filename, array('id' => $id, 'page' => $page, 'action' => $action), $mainticket->Get('subject')); } } elseif ($action == 'reopen' && $id != 0) { @@ -377,7 +393,11 @@ if ($page == 'overview') { } $now = time(); - $mainticket = ticket::getInstanceOf($userinfo, (int)$id); + try { + $mainticket = ticket::getInstanceOf($userinfo, (int)$id); + } catch(Exception $e) { + standard_error($e->getMessage()); + } $mainticket->Set('lastchange', $now, true, true); $mainticket->Set('lastreplier', '0', true, true); $mainticket->Set('status', '0', true, true); diff --git a/lib/classes/ticket/class.ticket.php b/lib/classes/ticket/class.ticket.php index a9d69ff8..4fb06eb4 100644 --- a/lib/classes/ticket/class.ticket.php +++ b/lib/classes/ticket/class.ticket.php @@ -19,46 +19,53 @@ * * Support Tickets - Tickets-Class */ - -class ticket { +class ticket +{ /** * Userinfo + * * @var array */ private $userinfo = array(); /** * Ticket ID - * @var tid + * + * @var int */ private $tid = - 1; /** * Ticket Data Array - * @var t_data + * + * @var array */ private $t_data = array(); /** * Ticket-Object-Array - * @var tickets + * + * @var ticket */ - static private $tickets = array(); + private static $tickets = array(); /** * Class constructor. * - * @param array userinfo - * @param int ticket id + * @param + * array userinfo + * @param + * int ticket id */ - private function __construct($userinfo, $tid = - 1) { + private function __construct($userinfo, $tid = - 1) + { $this->userinfo = $userinfo; $this->tid = $tid; - + // initialize data array $this->initData(); - + // read data from database $this->readData(); } @@ -66,21 +73,24 @@ class ticket { /** * Singleton ftw ;-) * - * @param array userinfo - * @param int ticket id + * @param + * array userinfo + * @param + * int ticket id */ - static public function getInstanceOf($_usernfo, $_tid) { - if (!isset(self::$tickets[$_tid])) { - self::$tickets[$_tid] = new ticket($_usernfo, $_tid); + static public function getInstanceOf($_usernfo, $_tid) + { + if (! isset(self::$tickets[$_tid . '-' . $_usernfo['userid']])) { + self::$tickets[$_tid . '-' . $_usernfo['userid']] = new ticket($_usernfo, $_tid); } - return self::$tickets[$_tid]; + return self::$tickets[$_tid . '-' . $_usernfo['userid']]; } /** * Initialize data-array */ - private function initData() { - + private function initData() + { $this->Set('customer', 0, true, true); $this->Set('admin', 1, true, true); $this->Set('subject', '', true, true); @@ -100,16 +110,33 @@ class ticket { /** * Read ticket data from database. */ - private function readData() { - - if (isset($this->tid) - && $this->tid != - 1 - ) { - $_ticket_stmt = Database::prepare(' - SELECT * FROM `' . TABLE_PANEL_TICKETS . '` WHERE `id` = :tid' - ); - $_ticket = Database::pexecute_first($_ticket_stmt, array('tid' => $this->tid)); - + private function readData() + { + if (isset($this->tid) && $this->tid != - 1) { + + if ($this->userinfo['customerid'] > 0) { + $_ticket_stmt = Database::prepare(' + SELECT * FROM `' . TABLE_PANEL_TICKETS . '` WHERE `id` = :tid AND `customerid` = :cid'); + $tdata = array( + 'tid' => $this->tid, + 'cid' => $this->userinfo['customerid'] + ); + } else { + $_ticket_stmt = Database::prepare(' + SELECT * FROM `' . TABLE_PANEL_TICKETS . '` WHERE `id` = :tid' . ($this->userinfo['customers_see_all'] ? '' : ' AND `adminid` = :adminid')); + $tdata = array( + 'tid' => $this->tid + ); + if ($this->userinfo['customers_see_all'] != '1') { + $tdata['adminid'] = $this->userinfo['adminid']; + } + } + $_ticket = Database::pexecute_first($_ticket_stmt, $tdata); + + if ($_ticket == false) { + throw new Exception("Invalid ticket id"); + } + $this->Set('customer', $_ticket['customerid'], true, false); $this->Set('admin', $_ticket['adminid'], true, false); $this->Set('subject', $_ticket['subject'], true, false); @@ -130,8 +157,8 @@ class ticket { /** * Insert data to database */ - public function Insert() { - + public function Insert() + { $ins_stmt = Database::prepare(" INSERT INTO `" . TABLE_PANEL_TICKETS . "` SET `customerid` = :customerid, @@ -146,8 +173,7 @@ class ticket { `status` = :status, `lastreplier` = :lastreplier, `by` = :by, - `answerto` = :answerto" - ); + `answerto` = :answerto"); $ins_data = array( 'customerid' => $this->Get('customer'), 'adminid' => $this->Get('admin'), @@ -171,8 +197,9 @@ class ticket { /** * Update data in database */ - public function Update() { - + public function Update() + { + // Update "main" ticket $upd_stmt = Database::prepare(' UPDATE `' . TABLE_PANEL_TICKETS . '` SET @@ -180,8 +207,7 @@ class ticket { `lastchange` = :lastchange, `status` = :status, `lastreplier` = :lastreplier - WHERE `id` = :tid' - ); + WHERE `id` = :tid'); $upd_data = array( 'priority' => $this->Get('priority'), 'lastchange' => $this->Get('lastchange'), @@ -196,38 +222,44 @@ class ticket { /** * Moves a ticket to the archive */ - public function Archive() { - + public function Archive() + { + // Update "main" ticket $upd_stmt = Database::prepare(' - UPDATE `' . TABLE_PANEL_TICKETS . '` SET `archived` = "1" WHERE `id` = :tid' - ); - Database::pexecute($upd_stmt, array('tid' => $this->tid)); - + UPDATE `' . TABLE_PANEL_TICKETS . '` SET `archived` = "1" WHERE `id` = :tid'); + Database::pexecute($upd_stmt, array( + 'tid' => $this->tid + )); + // Update "answers" to ticket $upd_stmt = Database::prepare(' - UPDATE `' . TABLE_PANEL_TICKETS . '` SET `archived` = "1" WHERE `answerto` = :tid' - ); - Database::pexecute($upd_stmt, array('tid' => $this->tid)); + UPDATE `' . TABLE_PANEL_TICKETS . '` SET `archived` = "1" WHERE `answerto` = :tid'); + Database::pexecute($upd_stmt, array( + 'tid' => $this->tid + )); return true; } /** * Remove ticket from database */ - public function Delete() { - + public function Delete() + { + // Delete "main" ticket $del_stmt = Database::prepare(' - DELETE FROM `' . TABLE_PANEL_TICKETS . '` WHERE `id` = :tid' - ); - Database::pexecute($del_stmt, array('tid' => $this->tid)); - + DELETE FROM `' . TABLE_PANEL_TICKETS . '` WHERE `id` = :tid'); + Database::pexecute($del_stmt, array( + 'tid' => $this->tid + )); + // Delete "answers" to ticket" $del_stmt = Database::prepare(' - DELETE FROM `' . TABLE_PANEL_TICKETS . '` WHERE `answerto` = :tid' - ); - Database::pexecute($del_stmt, array('tid' => $this->tid)); + DELETE FROM `' . TABLE_PANEL_TICKETS . '` WHERE `answerto` = :tid'); + Database::pexecute($del_stmt, array( + 'tid' => $this->tid + )); return true; } @@ -237,16 +269,17 @@ class ticket { public function sendMail($customerid = - 1, $template_subject = null, $default_subject = null, $template_body = null, $default_body = null) { global $mail, $theme; - + // Some checks are to be made here in the future if ($customerid != - 1) { // Get e-mail message for customer $usr_stmt = Database::prepare(' SELECT `name`, `firstname`, `company`, `email` - FROM `' . TABLE_PANEL_CUSTOMERS . '` WHERE `customerid` = :customerid' - ); - $usr = Database::pexecute_first($usr_stmt, array('customerid' => $customerid)); - + FROM `' . TABLE_PANEL_CUSTOMERS . '` WHERE `customerid` = :customerid'); + $usr = Database::pexecute_first($usr_stmt, array( + 'customerid' => $customerid + )); + $replace_arr = array( 'FIRSTNAME' => $usr['firstname'], 'NAME' => $usr['name'], @@ -268,23 +301,21 @@ class ticket { SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` WHERE `adminid`= :adminid AND `language`= :lang - AND `templategroup`= 'mails' AND `varname`= :tplsubject" - ); + AND `templategroup`= 'mails' AND `varname`= :tplsubject"); $result = Database::pexecute_first($result_stmt, $tpl_seldata); $mail_subject = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $default_subject), $replace_arr)); - + unset($tpl_seldata['tplsubject']); $tpl_seldata['tplmailbody'] = $template_body; - + $result_stmt = Database::prepare(" SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "` WHERE `adminid`= :adminid AND `language`= :lang - AND `templategroup`= 'mails' AND `varname`= :tplmailbody" - ); + AND `templategroup`= 'mails' AND `varname`= :tplmailbody"); $result = Database::pexecute_first($result_stmt, $tpl_seldata); $mail_body = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $default_body), $replace_arr)); - + if ($customerid != - 1) { $_mailerror = false; try { @@ -294,28 +325,30 @@ class ticket { $mail->MsgHTML(str_replace("\n", "
    ", $mail_body)); $mail->AddAddress($usr['email'], $usr['firstname'] . ' ' . $usr['name']); $mail->Send(); - } catch(phpmailerException $e) { + } catch (phpmailerException $e) { $mailerr_msg = $e->errorMessage(); $_mailerror = true; } catch (Exception $e) { $mailerr_msg = $e->getMessage(); $_mailerror = true; } - + if ($_mailerror) { - $rstlog = FroxlorLogger::getInstanceOf(array('loginname' => 'ticket_class')); + $rstlog = FroxlorLogger::getInstanceOf(array( + 'loginname' => 'ticket_class' + )); $rstlog->logAction(ADM_ACTION, LOG_ERR, "Error sending mail: " . $mailerr_msg); standard_error('errorsendingmail', $usr['email']); } $mail->ClearAddresses(); - } else { - + $admin_stmt = Database::prepare(" SELECT `name`, `email` FROM `" . TABLE_PANEL_ADMINS . "` - WHERE `adminid` = :adminid" - ); - $admin = Database::pexecute_first($admin_stmt, array('adminid' => $this->userinfo['adminid'])); + WHERE `adminid` = :adminid"); + $admin = Database::pexecute_first($admin_stmt, array( + 'adminid' => $this->userinfo['adminid'] + )); $_mailerror = false; try { $mail->SetFrom(Settings::Get('ticket.noreply_email'), Settings::Get('ticket.noreply_name')); @@ -324,20 +357,22 @@ class ticket { $mail->MsgHTML(str_replace("\n", "
    ", $mail_body)); $mail->AddAddress($admin['email'], $admin['name']); $mail->Send(); - } catch(phpmailerException $e) { + } catch (phpmailerException $e) { $mailerr_msg = $e->errorMessage(); $_mailerror = true; } catch (Exception $e) { $mailerr_msg = $e->getMessage(); $_mailerror = true; } - + if ($_mailerror) { - $rstlog = FroxlorLogger::getInstanceOf(array('loginname' => 'ticket_class')); + $rstlog = FroxlorLogger::getInstanceOf(array( + 'loginname' => 'ticket_class' + )); $rstlog->logAction(ADM_ACTION, LOG_ERR, "Error sending mail: " . $mailerr_msg); standard_error('errorsendingmail', $admin['email']); } - + $mail->ClearAddresses(); } } @@ -345,21 +380,18 @@ class ticket { /** * Add a support-categories */ - static public function addCategory($_category = null, $_admin = 1, $_order = 1) { - - if ($_category != null - && $_category != '' - ) { + static public function addCategory($_category = null, $_admin = 1, $_order = 1) + { + if ($_category != null && $_category != '') { if ($_order < 1) { $_order = 1; } - + $ins_stmt = Database::prepare(" INSERT INTO `" . TABLE_PANEL_TICKET_CATS . "` SET `name` = :name, `adminid` = :adminid, - `logicalorder` = :lo" - ); + `logicalorder` = :lo"); $ins_data = array( 'name' => $_category, 'adminid' => $_admin, @@ -374,23 +406,24 @@ class ticket { /** * Edit a support-categories */ - static public function editCategory($_category = null, $_id = 0, $_order = 1) { - - if ($_category != null - && $_category != '' - && $_id != 0 - ) { + static public function editCategory($_category = null, $_id = 0, $_order = 1) + { + if ($_category != null && $_category != '' && $_id != 0) { if ($_order < 1) { $_order = 1; } - + $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_TICKET_CATS . "` SET `name` = :name, `logicalorder` = :lo WHERE `id` = :id "); - Database::pexecute($upd_stmt, array('name' => $_category, 'lo' => $_order, 'id' => $_id)); + Database::pexecute($upd_stmt, array( + 'name' => $_category, + 'lo' => $_order, + 'id' => $_id + )); return true; } return false; @@ -399,40 +432,43 @@ class ticket { /** * Delete a support-categories */ - static public function deleteCategory($_id = 0) { - + static public function deleteCategory($_id = 0) + { if ($_id != 0) { - + $result_stmt = Database::prepare(" SELECT COUNT(`id`) as `numtickets` FROM `" . TABLE_PANEL_TICKETS . "` - WHERE `category` = :cat" - ); - $result = Database::pexecute_first($result_stmt, array('cat' => $_id)); - + WHERE `category` = :cat"); + $result = Database::pexecute_first($result_stmt, array( + 'cat' => $_id + )); + if ($result['numtickets'] == "0") { $del_stmt = Database::prepare(" - DELETE FROM `" . TABLE_PANEL_TICKET_CATS . "` WHERE `id` = :id" - ); - Database::pexecute($del_stmt, array('id' => $_id)); + DELETE FROM `" . TABLE_PANEL_TICKET_CATS . "` WHERE `id` = :id"); + Database::pexecute($del_stmt, array( + 'id' => $_id + )); return true; } else { return false; } } - + return false; } /** * Return a support-category-name */ - static public function getCategoryName($_id = 0) { - + static public function getCategoryName($_id = 0) + { if ($_id != 0) { $stmt = Database::prepare(" - SELECT `name` FROM `" . TABLE_PANEL_TICKET_CATS . "` WHERE `id` = :id" - ); - $category = Database::pexecute_first($stmt, array('id' => $_id)); + SELECT `name` FROM `" . TABLE_PANEL_TICKET_CATS . "` WHERE `id` = :id"); + $category = Database::pexecute_first($stmt, array( + 'id' => $_id + )); return $category['name']; } return null; @@ -440,32 +476,33 @@ class ticket { /** * get the highest order number - * - * @param object $_uid admin-id (optional) - * + * + * @param object $_uid + * admin-id (optional) + * * @return int highest order number */ - static public function getHighestOrderNumber($_uid = 0) { - + static public function getHighestOrderNumber($_uid = 0) + { $where = ''; $sel_data = array(); if ($_uid > 0) { $where = " WHERE `adminid` = :adminid"; $sel_data['adminid'] = $_uid; } - $sql = "SELECT MAX(`logicalorder`) as `highestorder` FROM `" . TABLE_PANEL_TICKET_CATS . "`".$where.";"; + $sql = "SELECT MAX(`logicalorder`) as `highestorder` FROM `" . TABLE_PANEL_TICKET_CATS . "`" . $where . ";"; $result_stmt = Database::prepare($sql); $result = Database::pexecute_first($result_stmt, $sel_data); - return (isset($result['highestorder']) ? (int)$result['highestorder'] : 0); + return (isset($result['highestorder']) ? (int) $result['highestorder'] : 0); } /** * returns the last x archived tickets */ - static public function getLastArchived($_num = 10, $_admin = 1) { - + static public function getLastArchived($_num = 10, $_admin = 1) + { if ($_num > 0) { - + $archived = array(); $counter = 0; $result_stmt = Database::prepare(" @@ -477,12 +514,13 @@ class ticket { FROM `" . TABLE_PANEL_TICKETS . "` `main` WHERE `main`.`answerto` = '0' AND `main`.`archived` = '1' AND `main`.`adminid` = :adminid - ORDER BY `main`.`lastchange` DESC LIMIT 0, ".(int)$_num - ); - Database::pexecute($result_stmt, array('adminid' => $_admin)); - + ORDER BY `main`.`lastchange` DESC LIMIT 0, " . (int) $_num); + Database::pexecute($result_stmt, array( + 'adminid' => $_admin + )); + while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { - + $archived[$counter]['id'] = $row['id']; $archived[$counter]['customerid'] = $row['customerid']; $archived[$counter]['adminid'] = $row['adminid']; @@ -496,9 +534,9 @@ class ticket { $archived[$counter]['lastchange'] = $row['lastchange']; $archived[$counter]['status'] = $row['status']; $archived[$counter]['by'] = $row['by']; - $counter++; + $counter ++; } - + if (isset($archived[0]['id'])) { return $archived; } else { @@ -516,129 +554,102 @@ class ticket { static public function getArchiveSearchStatement($subject = null, $priority = null, $fromdate = null, $todate = null, $message = null, $customer = - 1, $admin = 1, $categories = null) { $search_params = array(); - + $query = " SELECT `main`.*, ( SELECT COUNT(`sub`.`id`) FROM `" . TABLE_PANEL_TICKETS . "` `sub` WHERE `sub`.`answerto` = `main`.`id` ) as `ticket_answers` FROM `" . TABLE_PANEL_TICKETS . "` `main` - WHERE `main`.`archived` = '1' AND `main`.`adminid` = :admin" - ; - + WHERE `main`.`archived` = '1' AND `main`.`adminid` = :admin"; + $search_params['admin'] = $admin; - - if ($subject != NULL - && $subject != '' - ) { + + if ($subject != NULL && $subject != '') { $query .= " AND `main`.`subject` LIKE :subject"; - $search_params['subject'] = "%".$subject."%"; + $search_params['subject'] = "%" . $subject . "%"; } - - if ($priority != null - && isset($priority[0]) - && $priority[0] != '' - ) { - - if (isset($priority[1]) - && $priority[1] != '' - ) { - - if (isset($priority[2]) - && $priority[2] != '' - ) { - + + if ($priority != null && isset($priority[0]) && $priority[0] != '') { + + if (isset($priority[1]) && $priority[1] != '') { + + if (isset($priority[2]) && $priority[2] != '') { + $query .= " AND (`main`.`priority` = '1' OR `main`.`priority` = '2' OR `main`.`priority` = '3')"; - } else { - + $query .= " AND (`main`.`priority` = '1' OR `main`.`priority` = '1')"; } - - } elseif (isset($priority[2]) - && $priority[2] != '' - ) { - + } elseif (isset($priority[2]) && $priority[2] != '') { + $query .= " AND (`main`.`priority` = '1' OR `main`.`priority` = '3')"; - } else { $query .= " AND `main`.`priority` = '1'"; } - - } elseif($priority != null - && isset($priority[1]) - && $priority[1] != '' - ) { - if (isset($priority[2]) - && $priority[2] != '' - ) { + } elseif ($priority != null && isset($priority[1]) && $priority[1] != '') { + if (isset($priority[2]) && $priority[2] != '') { $query .= " AND (`main`.`priority` = '2' OR `main`.`priority` = '3')"; } else { $query .= " AND `main`.`priority` = '2'"; } - - } elseif($priority != null) { - - if (isset($priority[3]) - && $priority[3] != '' - ) { + } elseif ($priority != null) { + + if (isset($priority[3]) && $priority[3] != '') { $query .= " AND `main`.`priority` = '3'"; } } - - if ($fromdate != null - && $fromdate > 0 - ) { + + if ($fromdate != null && $fromdate > 0) { $query .= " AND `main`.`lastchange` > :fromdate"; $search_params['fromdate'] = strtotime($fromdate); } - - if ($todate != null - && $todate > 0 - ) { + + if ($todate != null && $todate > 0) { $query .= " AND `main`.`lastchange` < :todate"; $search_params['todate'] = strtotime($todate); } - - if ($message != null - && $message != '' - ) { + + if ($message != null && $message != '') { $query .= " AND `main`.`message` LIKE :message"; - $search_params['message'] = "%".$message."%"; + $search_params['message'] = "%" . $message . "%"; } - + if ($customer != - 1) { $query .= " AND `main`.`customerid` = :customer"; $search_params['customer'] = $customer; } - + if ($categories != null) { - + $cats = array(); foreach ($categories as $index => $catid) { if ($catid != "") { $cats[] = $catid; } } - + if (count($cats) > 0) { $query .= " AND ("; } - + foreach ($cats as $catid) { if (isset($catid) && $catid > 0) { - $query .= "`main`.`category` = :catid_".$catid." OR "; - $search_params['catid_'.$catid] = $catid; + $query .= "`main`.`category` = :catid_" . $catid . " OR "; + $search_params['catid_' . $catid] = $catid; } } - + if (count($cats) > 0) { $query = substr($query, 0, strlen($query) - 3); $query .= ") "; } } - - return array('0' => $query, '1' => $search_params); + + return array( + '0' => $query, + '1' => $search_params + ); } /** @@ -646,8 +657,7 @@ class ticket { */ static public function getStatusText($_lng, $_status = 0) { - switch($_status) - { + switch ($_status) { case 0: return $_lng['ticket']['open']; break; @@ -668,8 +678,7 @@ class ticket { */ static public function getPriorityText($_lng, $_priority = 0) { - switch($_priority) - { + switch ($_priority) { case 1: return $_lng['ticket']['high']; break; @@ -684,19 +693,19 @@ class ticket { private function convertLatin1ToHtml($str) { - $html_entities = array ( - "Ä" => "Ä", - "ä" => "ä", - "Ö" => "Ö", - "ö" => "ö", - "Ü" => "Ü", - "ü" => "ü", - "ß" => "ß" - /* - * @TODO continue this table for all the special-characters - */ + $html_entities = array( + "Ä" => "Ä", + "ä" => "ä", + "Ö" => "Ö", + "ö" => "ö", + "Ü" => "Ü", + "ü" => "ü", + "ß" => "ß" + /* + * @TODO continue this table for all the special-characters + */ ); - + foreach ($html_entities as $key => $value) { $str = str_replace($key, $value, $str); } @@ -706,45 +715,47 @@ class ticket { /** * function customerHasTickets * - * @param int customer-id - * - * @return array/bool array of ticket-ids if customer has any, else false + * @param + * int customer-id + * + * @return array/bool array of ticket-ids if customer has any, else false */ - static public function customerHasTickets($_cid = 0) { - + static public function customerHasTickets($_cid = 0) + { if ($_cid != 0) { $result_stmt = Database::prepare(" - SELECT `id` FROM `" . TABLE_PANEL_TICKETS . "` WHERE `customerid` = :cid" - ); - Database::pexecute($result_stmt, array('cid' => $_cid)); - + SELECT `id` FROM `" . TABLE_PANEL_TICKETS . "` WHERE `customerid` = :cid"); + Database::pexecute($result_stmt, array( + 'cid' => $_cid + )); + $tickets = array(); while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { $tickets[] = $row['id']; } - + return $tickets; } - + return false; } /** * Get a data-var */ - public function Get($_var = '', $_vartrusted = false) { - + public function Get($_var = '', $_vartrusted = false) + { if ($_var != '') { - if (!$_vartrusted) { + if (! $_vartrusted) { $_var = htmlspecialchars($_var); } - + if (isset($this->t_data[$_var])) { if (strtolower($_var) == 'message') { // avoid double line-breaks, #1413 $this->t_data[$_var] = str_replace("
    \n", "\n", $this->t_data[$_var]); return nl2br($this->t_data[$_var]); - } elseif(strtolower($_var) == 'subject') { + } elseif (strtolower($_var) == 'subject') { return nl2br($this->t_data[$_var]); } else { return $this->t_data[$_var]; @@ -758,25 +769,21 @@ class ticket { /** * Set a data-var */ - public function Set($_var = '', $_value = '', $_vartrusted = false, $_valuetrusted = false) { - - if ($_var != '' - && $_value != '' - ) { - if (!$_vartrusted) { + public function Set($_var = '', $_value = '', $_vartrusted = false, $_valuetrusted = false) + { + if ($_var != '' && $_value != '') { + if (! $_vartrusted) { $_var = strip_tags($_var); } - - if (!$_valuetrusted) { + + if (! $_valuetrusted) { $_value = strip_tags($_value, '
    '); } - - if (strtolower($_var) == 'message' - || strtolower($_var) == 'subject' - ) { + + if (strtolower($_var) == 'message' || strtolower($_var) == 'subject') { $_value = $this->convertLatin1ToHtml($_value); } - + $this->t_data[$_var] = $_value; } } From 436d141bd1c6f06a66b6d6e593d359afc3c2a80e Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Thu, 21 Jun 2018 07:52:11 +0200 Subject: [PATCH 195/746] fix ticket access when posting answer Signed-off-by: Michael Kaufmann --- customer_tickets.php | 7 +++++-- lib/classes/ticket/class.ticket.php | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/customer_tickets.php b/customer_tickets.php index d237ac06..5262adb5 100644 --- a/customer_tickets.php +++ b/customer_tickets.php @@ -250,6 +250,11 @@ if ($page == 'overview') { if ($replyticket->Get('message') == null) { standard_error(array('stringisempty', 'mymessage')); } else { + try { + $mainticket = ticket::getInstanceOf($userinfo, (int)$id); + } catch(Exception $e) { + standard_error($e->getMessage()); + } $now = time(); $replyticket->Set('customer', (int)$userinfo['customerid'], true, true); $replyticket->Set('lastchange', $now, true, true); @@ -260,8 +265,6 @@ if ($page == 'overview') { $replyticket->Insert(); // Update priority if changed - $mainticket = ticket::getInstanceOf($userinfo, (int)$id); - if ($replyticket->Get('priority') != $mainticket->Get('priority')) { $mainticket->Set('priority', $replyticket->Get('priority'), true); } diff --git a/lib/classes/ticket/class.ticket.php b/lib/classes/ticket/class.ticket.php index 4fb06eb4..b3b14384 100644 --- a/lib/classes/ticket/class.ticket.php +++ b/lib/classes/ticket/class.ticket.php @@ -46,7 +46,7 @@ class ticket /** * Ticket-Object-Array * - * @var ticket + * @var ticket[] */ private static $tickets = array(); From da993985618cb3efeb61f42cd6c4f39a42e6045a Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Thu, 21 Jun 2018 07:55:35 +0200 Subject: [PATCH 196/746] update jquery-ui Signed-off-by: Michael Kaufmann --- css/images/ui-bg_glass_55_fbf9ee_1x400.png | Bin 0 -> 335 bytes css/images/ui-bg_glass_65_ffffff_1x400.png | Bin 280 -> 207 bytes css/images/ui-bg_glass_75_dadada_1x400.png | Bin 0 -> 262 bytes css/images/ui-bg_glass_75_e6e6e6_1x400.png | Bin 0 -> 262 bytes css/images/ui-bg_glass_95_fef1ec_1x400.png | Bin 0 -> 332 bytes .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin 0 -> 280 bytes css/images/ui-icons_222222_256x240.png | Bin 4862 -> 6922 bytes css/images/ui-icons_2e83ff_256x240.png | Bin 0 -> 4549 bytes css/images/ui-icons_454545_256x240.png | Bin 0 -> 6992 bytes css/images/ui-icons_888888_256x240.png | Bin 0 -> 6999 bytes css/images/ui-icons_cd0a0a_256x240.png | Bin 0 -> 4549 bytes css/jquery-ui.min.css | 10 +++++----- 12 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 css/images/ui-bg_glass_55_fbf9ee_1x400.png create mode 100644 css/images/ui-bg_glass_75_dadada_1x400.png create mode 100644 css/images/ui-bg_glass_75_e6e6e6_1x400.png create mode 100644 css/images/ui-bg_glass_95_fef1ec_1x400.png create mode 100644 css/images/ui-bg_highlight-soft_75_cccccc_1x100.png create mode 100644 css/images/ui-icons_2e83ff_256x240.png create mode 100644 css/images/ui-icons_454545_256x240.png create mode 100644 css/images/ui-icons_888888_256x240.png create mode 100644 css/images/ui-icons_cd0a0a_256x240.png diff --git a/css/images/ui-bg_glass_55_fbf9ee_1x400.png b/css/images/ui-bg_glass_55_fbf9ee_1x400.png new file mode 100644 index 0000000000000000000000000000000000000000..118209b4b6adcabef21f26f93b63b04263cd38d3 GIT binary patch literal 335 zcmeAS@N?(olHy`uVBq!ia0vp^j6gI&fCnc6a#?2AmP!?*K(O3p^r= zfwTu0yPeFo12TF&T^vI^j=w#x$i?I+((tf;UXnmgbH|3oY>pC!)f}(GR!16S-u+#{ ze6YEqRkW=8vGl=5qArKM<9}TC-}iEvB{zdaTcX5$wyRTK&ALYGq)eZD3$! kV9wV{HQiD+2?Uy7F}l3=FCzt`Q|Ei6yC4 gx%nxXX_X8{28P%*m@1za0%~CJboFyt=akR{01NjSYXATM delta 168 zcmX@lID=_|iW^J1qpu?a!^VE@KZ&di3=E9LLGDfr>(0r%1acITJ%W507^>757#dm_ z7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD?*A#qe+wT>~I83NbXZGBma_w9qv$wlXkC wd9_Ces7RXUCGx5b?-VBQkUm|IuXOmYJrBRJgj{Vx zMbNnqUkncy+qa2-mWYc>swkcIuvGK#>(0d)B7)5f`@$Ei28nH~0h*~=;u=wsl30>z zm0Xkxq!^403@vmG%ybQmLk!HVj7_Z!OtcLQtPBhqZ+a@AXvob^$xN%nt>Ht<$2mX^ N44$rjF6*2UngCW^PcZ-h literal 0 HcmV?d00001 diff --git a/css/images/ui-bg_glass_75_e6e6e6_1x400.png b/css/images/ui-bg_glass_75_e6e6e6_1x400.png new file mode 100644 index 0000000000000000000000000000000000000000..5c738d545a82220d1cbf6bc45be9d2194806f696 GIT binary patch literal 262 zcmeAS@N?(olHy`uVBq!ia0vp^j6gI&0LWmFTHNUZq?nSt-Ch3w7g=q17Rci)@Q5r1 z(jH*!b~4)z#PD=+46!(!TrvH)L6@80)r*_cdCvDr%)6ghVL16=s@mbz7H!uRdGeDa z?kzLg)16i!f8fKx84s0>4afpGrm9eRnfr++(ft7(l<4sQm6b-rgDVb@NxHWue`8Wrt Ofx*+&&t;ucLK6U?tWQ4x literal 0 HcmV?d00001 diff --git a/css/images/ui-bg_glass_95_fef1ec_1x400.png b/css/images/ui-bg_glass_95_fef1ec_1x400.png new file mode 100644 index 0000000000000000000000000000000000000000..1d828735a5b5a2d0e88aac81b90f0dbb5dff162c GIT binary patch literal 332 zcmeAS@N?(olHy`uVBq!ia0vp^j6gI&fCnc6a#?2AmP!?*K(O3p^r= zfwTu0yPeFo12VciT^vI^j=w#>k(V)1qW$CZ|6)SVV-&*#dav<$DMuV&n0Dbpw@a>=^7Y^7?@ibn_3x|Xd4(<85lI) h^i)96kei>9nO2Eg!-tlSbATEcJYD@<);T3K0RXc|bS3}* literal 0 HcmV?d00001 diff --git a/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png new file mode 100644 index 0000000000000000000000000000000000000000..4d4a7e9280a7021f5d3a6c5004637a588f481f24 GIT binary patch literal 280 zcmeAS@N?(olHy`uVBq!ia0vp^j6j?s03;ZUuHXC*q?nSt-Ch3w7g=q17Rci)@Q5r1 z(jH*!b~4)z$cXZEaSV~ToLo`U+vu0Ue0cG9p8hWqa?gxxGLm=1A1u)Cewe3oSeCaf zI$k30UHXoTXA5lSJe(zTcE%W-S*bfB&J`pw9sa4-R?IGW?p~6`>jMSP&M+u3 zY@9al)zrvpHlQu4C9V-ADTyViR>?)FK#IZ0z|cb1z)aV`IK;r*%GlJ(z(m`?z{|9pEC2TIxHV+jYr=H%}!7RG)%@|?KBm>pyGpu&ojtW_wyym4g`c9 zco$2c8`p{vTVxgdal_?TR2q}t~bOfL?S*Jz>-hj zej%U-ol64Z=H=<4vts*B%Lz$=z-A~15GXb3wI}HLd?o-|{AomRiY`p@EGlXSgLObn zHiI+BXw_n(p}o|pYMok=31u8OMO?~bm-n`CS;fRowPbbSwTgu0=*7l(0v$^yp&hkM zDNj1Y0bXyV)+53{VIwdGW4Fo5{;9<>33wL?Z3XX8uv#Z=U!_NVfTGUbDr4v}Tu zR`?wX3hn^0$(t{SX1-GeGS~e&#-4Kh2MVt#b5SU8CjmH-w$O;aCD6t?ZrfdZEbk@Y zJqZfG&3O7WH7q-{({FD9v;hC5@$-r}DY+5dbrzbDrH+tT&pfKJUur|7-n>I<<=u4E zqp>W4q^FJ|mzJ;N(7*VMG zU2BTU!JR%!p+}0CBhHUm_PbWLt8EWDyGtR zVU)%zz+8FYJEe|QI~>NS9o>Rn3~1g%$0eQ`!lR@ZF2O92__?mx1rtva8{14&Kj1L0 zDaZ(xRC21rv8=fJO|)hhkn=UJGcknv`SnzJbkjtkmm+>?J%~|f+$2Sbx8vOAIgY2t zqQT*KpE$b>z=kxqpl_!>-2K;g2dm}3JV23iDpVU^-or__aU{~34xQEW(|mEprL&b- zAQSAsLrN#rJ|@c*=_coAKi$0zq;K9IiTC{?&;K{{PJd1H8|KNh0y%+oVJU9m3t9~>S^EF8ZniI_zTt3H+C~_J}%``4jjf5yho+NMLCrcAJN*P z^^L0M2Nk|oV{v_&cv{>_czfr@d)!?(MX2RGno!%Jx$h65%CqQ#A0d5;| zSSLyd*lk78{Jmt_-J$L6#h)e1B{JA5+7@mTA2cKQHMTmEgVqts${HgsFO~C=B_Q*( zHl%u4XCA+FdC1CvH?&IhdHSiEW#v4eIhhZEQ+hV>M>Dkvof`t}fb!@dj0LrbRqUg5 z8nXf_+F;+Ir4&w^l=4m~sy#TR$Q8xfPB%72n=*iaN9EKWeXj`CkLbf69eW>JCVDPZ z7L|9kyXOYG(A0Zx(eZiSb@JFFdT6i-rdyVPce<|Y|A~7p%E!+Vm5pq)E_mjl3TB&{ z7xkGp$iSk&UEtWes|0wb9iDlJf)%nsDc$a1kP0__Pvgop<&Te#x#)w#k-3S#)*eW* znUIBK40-siM_X970Y(XHD$9MH=f zxxGx(wa4Z;q^W3V0>lRag@tvPcuViWb!iG;t2 z5-KFMfg<4J#MU$lBUwKHsdM*=Xc&102XwKxclrG1%i=4}FJahwrX;H zNwp>$UiNsQ3MaH`j_r!Cf!2nGp?oQYWGDyw*oRPD)Jji};0ay%lW)Y}e_~utKE<_L zAW#===qs>H+NFwJaOrgI)RmNr*QaH#W&q37EwXhR=bE;rxBx~wP_0#}8%G*VMyw0t zOXoQ8i8jP(H)=AY#xJECZlA{!*{lM`Wp((OZTS&YORwOY5MEdRRM@+^ANnn3WENTU zA3OQOy5)T|{ zY;G!nWz5+Wt(#<-Sr@5cgAv1gCuY^U)Ic+>!QAozYINV2tObk6EFYy2AmNW0n)r&g zKC+K(98CT!SCiRNi%Qcy2?^*e&zkhXK-*gD+1)epREUD@_8y6#qo9GdvS=?m7pBQ` zkL)xGZw}JHFBf3X*09PMs%@LyD-6@bK$YKo{x5a39#&fGyXO3w6XU>6jJg zQxb0!eyd)r8P}79d{lXm@XwyGF;M~{Y|HRvIVJl6MGu643K(3krt#!mUF&&tM z-{qM2sSo2s1@j!}h>Z+rA6Pc(peiB|1BSb{g@P{1VOv-kb2MJ5|L!vR}v>GSI@0<7h7Ey`Iq`4cv)8v39ONQF*(#v*Db zNB_~;W^ZHpEvX>TpNcjEwqgc~S;?zigBbTgVqHJ$%9~EWjd99l7Ya*mEn>GbEF!ic zTiJw8JQJ^ti0A(>+*#ha6svdftjY|(XtMW!CU}#pC^AEGg0!z^e{u6KNctmD_L=|1h_F85eW|d& z+iZ@#_$CIQXazAg$AB$+7?7Q|`Sca-nC$sw5+|$(DDuQc{)a!^hpV0Kx)WHV*$GjO z|LEJ?QV~#Qf$SxPaJFBkim9;NXMLaE_V9$4CY5uDt)dIV@{AVCq{`geO@^4SpUoRc zPVkO7ol5!RI8EDt^|cnZYF*_zKo{IflvWA_^?@N?-+@@l++5RRzEjGcx9-;pT`$Bw zYqm3fyCB@{Bh2RL_l6IB3$|M?yE=cV3>a6u#0604l9gb^&P;UI&K6>^A3rei z_AsGQ4Ph7qJMYk+^0a=4`12DAaYopombf^7U%29p-_H&Hu0FqO+@n`^e)LD62&Z84ykM?K4rpE-FOj2zS#H`pWk3AGV5%vZY0F1 z&VL&c0mopw@;XD4Qf8}=H2Iu{Vc9X=d9#KP5u$xsT}<}l7uBv2d>vWdXQF`>_6zd>Qkqko0@L$$)M-(3;=I!1JeN+rI>;xQSPDrF{CFaxPI^sok@u+J#M za|fi2jTJ73_5xC67LB)FzWAh)!m=%c+%oP!)FAN02CIm?G2@F7h~Gn>I;UiEEu;C< z);%{mKeh{tOdolre6l`Ubu6F4{op|tGN=Ad<=3cywRTiuWg{O(T@R}k`(MIXtG*44-UH&+4)(f(HhC1 zXsTq@i1&P?`-bqFAKa=oz$VRWlUTm*f{!U8!!N~$vB$ElIJZ0-MPoSq2y8Z>$Os?- zGitN8Lb*rCC$GeItn-ey9Ac6UJ+857Sgm5CLje5}kRry*T%TnPML@s<9Yg&9Jc>75 z9_UG@JJPA!V$7TfAcPr|@Es-aqeJt6yVLvzFUY(eR}|GGERaSJoA{bPCayxFF9gc6 z^N|sr=T+@yO}@iNU&Th$wC-VJBDztJZk!3#RCDb&k;!k4YXrXpK0LoRkco;%sq4p9 zqSP9OCLHKHxQVL(9&zrHL3fl~$FAr4$$+J%-=#(cT0I+BR?h`OnYD2FF^**(Xzz2s z#U!p1!yAQ8q zF_PD>c=N0{AM_3a7t9mUp^?$L6f#bY6OSkSHJZ6=c};=~Evb&aDUPk24+w1dPxB>; zHCbF&kbo=5)dNhQ1*Cr@omSHHWAO^|I5C1T@)P0Udp&wAPqepw9%h%6p1ZG#IOnP# z{uXSe?kNT%HP|H3^^N89$WH*`M;j3TK;=ta4?i2?JNb2U@R(s7rOV3j1>qa@j@KcH zJlr;{VG$cF0|Rt*46bb~aV|-q z73E4Wdq=&cryrwo%JgSG+kUsHH$wBn&on#@&z-V!vo&}R{c2AiEHqO?WQ-+-$LQ;| zSzsy5l3p8|_9NdxMK)PB!T>0P7k z&0CLgF@ySTJ=Bov9-*8aasg4`HBXV7GNBwSFWbfYpSUZwDVpY=r`DKG;QAq+)te2G z3jx&_i{~qs7fS+B*mZzdUd0prA#xjI_$&supo1tyP>sGKT!YLB=Cpj>xaiZ2&;OCd z7@~3TTVdi#^s^JD;Do?PT*OL`OohekdMverH^CeOJu2iQJPN++A9PQ@_?i+-l31q7 zsGebe^Uz2N$&&Im!nq>i#lZi3quVjmk&ofh?(5xyFQC{RbF-nue6Q`*?X*_#){G{@ zK6VgEs(n5yGLa=RVXuM-b3K?mqz0qy^q&O@{4h|_%MO1OeV8r4a$+ILh@P7F>Sjy* z`NPx!G`aDXuVsN(6|i63h#SzB>Hv^01!&-CYDW(j*2dAsem{P{o%?R3<|we8Z3{o$ zD#M;c1M7HJ1P6B8TCQRtdbsTW{Digg;=Tbh>ZLgI{R((4_UG(Sikptwn^Z`kcz&IY z0+NRJ^MlZY_n%$*l{qZ6#K6QWkMt{#=`}~c-spzOmr&=#tG8Vx`41nE*dJ5BnDXwx zM?U;=@oq53vM7qIZp5zz{&;QGv8O^S^(hKv?&=`g5=s3wuV{6Ql*moEnrz!8Ni>)P z>1uAq3&nbGE6lGHHl(l1UYr>`$O|3n3Y$mc{};v-(f4;jf;K*GAnTFZ17Hz43@FgV ziYF@6)04!;wB=B(dn0QU9tZzoF)5My#Ad3M^JND`IGi!5bS+h+B2$1^BL@Pt`IAbo zD)j5t9t+h&e7A5eJdOfMt4c+KJ8IXSSBX?Fe}VfE;+FW({Smz{KLO_Vg}SY0Xr^yF z1yh>@=f`l(!w&jrxu=|7G0z1wFqE5uJoW2WH~)%B^K257mHJg-(EIY$j=GMdObJV1 zRn|;yWG0Gk2CwO6bswvia-uxcDePIVR%08pf&{wJ-AYdv*)72*>^j5x#3UaM-E{il zk~&2g5nB`XZu_S3SN{e)AjE5wOs_bC_`Ncj;;$v=fvJIad(>fnQ|6AI{V&`SZ2_}CjpF}SlN#1n&An*VSa%egX%sCk_;{f35__sJ zOE^+UR`2JCbE!`(`mV4sOQK%d_;w9jI>M$c5^Y3wN@(kv{fh7uACHiAg-*_{UV3Ox zxp}8j@X_c)@`B5g4*{rYr=jQ7nnbQeic%Bgn-hWX^W$9FWS`dwVo76vy%ts0>Zbh2UvGm^{MpxM@{%>Y143I($OJfe zEv*E>MJ(+Tv`bAP4K~lx5gs3mEVZ?7tZYrBH;-V9J5Bx-)`^MLk$l_PRpp6?^F_wo zlV%W&v?Ydr?*)k05&BBKTxMXpTaA6|Qmo%`o%)m6vov2j7OvRi9Z-kplQ`yu@^3=f zFd6Ivjkw9pp-AQo&E};^*;RXBXsMp`DT#SIUOwl_*1H*_!HuuEZ?> z5!DSZS)x?A%vwFV!O1iQO%o;fK9I(% z$+BLxXIs^+|8ROTUlMBHcFokLkLgvLEWN5zy-40Pk>a#Pdr7^9HIuYjas)=?c8yXs zJKg~ZJkTHb(x7#`Et7pBoDc1#3##xyM z3}}lJ#$VdHnRhOTxUsU!Ziv*)`uE55lnXMS3zQr&NjQNO(;olsf)1uNe71-LxfUTn z>)M(OJ_&>rg74}GVOS@^kx`pSzJb@RY4ShyR zgpl6Y%~N}Ed?s`=A9k)Ga-YfSiz8Z%{}{sCRT*RI%FPz!@xcRbf2MJM9o#p_NqdNim~b;E?;dRhDlGrPjCy_vl2 z_70gh4;SVyo?cOaCAh=6SOzQ}en{jzFI_0%XEfx8j zNWFIdL-nht-6wHKB=T~D9QGsl%>mhNv>zIk6IJ=yW)5w2`To@u?IeJ~`~XHE8+$%1 z6-iC^E)-)noXcFG{Fo*BHxaic9kEceQ2597`9X1NMmjE_^I4Ed+t8GRH6Y85>O-3x z>hnmv8;jPzLWJ)VXN+#P0)|6EGy1ZcLFp{NzV|oH)>N!1@Uy-{QrJ#+3_B zTly^uFUV;yC^X#J^A>qTXUr%*tpBT)vbR?ZQiy~%TFl%B;!F_^kLIfY`61qpq>rN( zGq1E~?$>2+Qq4f;m`^g zi@XO$y03_pVr*+*)_~0g%)f9}BCi~w)cNz^d^6ql=&8w|k8@#HcvG$iCMHlhwd2Rn z%KB1{`F!7>?v5BeW%NC*Ej(?6t=w&%41kxL2g1S4$HBv+>9qA|%Yi%f`(u%+1YR zVPExc0nRSg_V0ZE{{V^eY0{?v^1qFso#H| Q^5g_ikWrPcku(qaFX2Wbe*gdg literal 4862 zcmYjV2{hE*`~S|@jAl%|)}$;mq9kIHeH}~oB_eCq3R!02wGY0Ot@PTd2w6%LB1@Jr zB3TlNtiwc8#-6n>{QchZJO5wzocnq1bD!tjbC=J3&V6peB~!g4Y$w2SURAkfXORcDiU{gfW_V*rva&_aE+EwpzBb>ig)um4s1K zsjet%Xq>EYv}(4t66`(c+>Pgi000SC>fcBg#8U1InSCyx^dDbpS@Am+TcUB^BELJt z5`ODEWGPaWn}*{NKJDYG8fQa4{l$O5%(-Q6v!$?#Q;PMH7If#Fkbh`7>nfa9E)@c* z|F~w%LlloVcIsDg)+HQUrN%d^>T7>f&`;S0Tw%4bh<9LS*l_HhkbDI+r3ReB!O%2| zW4m+tEwIcVlr0!ZC5n5N#W z%@F43h;_Dy?3BJkXP3fXmpj|SlJn!ar4gr&AWk{18|no8;D7mIDZHo^Oa68B?uc}a zx1r&5$i^9B2SyHwpAi8F5M5XP=WEQsZ@>Uh({|^_3V1Kx5ZopK1}rk8>VPgR4TAty zSpJ;FzmuY*wIxGV!8sqny#&`o^a!E3>RKzDlXa* z*FMDbdnI1^SRsTXKJ<+uH`Ffrvn-~amjf|8*}1d5S{66bFPtkpb{~Nxv>A-_APq2x zP2tY6z&EnDgw605D#~+TutVmQfEMz59M?>QVl=?>4%GL@7WAiu5f7}R^j?1M{v<7ngy^FK&&79 zc6{YN{P*~LMK7yi-pN&8kn4MQlnG>{k{kRjCBBxJa)wgDnKCLJhW1@Tbd{OwizP2B ziwTDVesj;azZ4`73X5>r@~gJvvp}(b{!&c5Bk`?YRd@vs{=u(5tM(M-)Dq`qt;8E>}Zz z+yfb{Nu^|bYs#rHh8c(QhZ}EsO#CBR6y(68wM5g|zYmXtH_5@_g$U|=>e>){Er;Kf}t!Vw@uy0;hyoD-)p^eCN`t)*T zO2|4MriboN@?dmwOVCrI@ZX7-9iGV}t{Gf&6a56780M$MJz~eA>WGb>-e0Gll26uV z&3Km?hMgBo9eOLOriARr0ts2x{wWPMd|e`~JcqaRpD>zqKHqtQ8i_*CP_E;eg$7s! zG{q+m-+Uo|uXP6?2h{OPB-LcznP=&&jf-MTjh?|zw$$AhJmZ{)vu}XkZ8@uf|AkbW zG$5mpL_yW4Mhgwd=8w36(*=uaFv3)Wz3S1Xc5iREO$H(;C48-SZYwHR{!z1sQkn%} zBirJc=6Yua`4Zz8U3y$_w&IqZq1lmPT+f^IPYVWzHYNVvWP3VVJu1`IP3s%qi#$c7 z`!+dBH!qbCeas0h@k$}GGrTt*2Mz??3oRea8{W?W{X4iBGr5Rr(E5+kTs zYeMV7n!2WxBz%NVh~RkJ23`3@u^`E4{pzQ|YM>llpB?n9vF(lW4D;~Tpn@7q>Zq2Z zC+Be#2ZD=csYiLa{}Rf5g3#m=(R_sPnAtLq-5PvWhQMA_XI9KbjfVp=Jo#_plTKJ` z3~jOEb5y;hdvC^q#m=thOuZdwITtCj^ZF^o z4deQrIGrQ#e(v%soU{!T8Sj@a#0l`DDwzx1SRYyTqUOfAvAUtH+Fji;?%=k< z`&pb&q_}Q72kpWPDZ%7Qsaf~3ku|tdq4PT*#$=f^$G$|-&*4kYRGw{NJc3?M!~sEe z`KFS$o+!uOJG{k%;zGkLv=vse?za_9Dj7ja+CY^JWMa=0hR;q-=p7j`nQ+hsK7vI7 z$5w2fKM2Dw;|?Nr?LF#oc)dB*uu*j`K=BAEmG8igw+1j4eeYC!)0&o_Q9L;&3p zG25_ML00*D?rx7aQ*Y{xi&_wE#Fp1$*Zmi%qaSI=%3{hYPA$qaHh_iUxb$|(`}oPN za8pB-#Zt^GpdGvf|6f2E7IM0Q#Ns7w9`wGeDuBf*J(tf3YdN3TFqKxWJhR|S3;moz zco00w`0Ja}9woi6O68J2#r$|@Ri4%1YB_kk)vITAYz~8ebyj~kR4W7bm%sjr-2dxq zrva0>wj8ATb*tu|WVX!=BYErIf=$F<(u6BXHM$#?)~%Dl&FS7Z&(4=JwhUL*3irA{ z1rD6XJA`#zUhHma?_z(N)bV*if&TM@4c^l>`?B_4%Dbc8&19y==+DzCdp?Ipqzt>> zo=keNx>Z%Wnycw}bl;8{AfY)Iyw-JdYMrHT*zW>Dmf_ZrjsLx!kN^avz3rxTj%`n`o;pcVU$$`zA5I0(GX;mN zgFdPYox%v`Wliqa&rp?-9F;O;&#d1zq!c?qv+PE5Fz%LTPBKi23r|!r^z5WeXJ( zQ4kQoEa_mcA@}=Rb>36XD|S6$#w7-f=55@(-Ym&}qKewJAqmP@(Uu-SCf zmoKaaUP7|n_R1ON8L?<}*bjjJYF)T$RXEj4X7E%!Iyzr8Y?t@w4)FXseIQUM-M4N2 zZGoJ-dApr^pq*LVCvMu6=3rB8$jUbH$JlWV0oYsloGi&^JDV%t<;0yWB0nfL*6A`W z{WmJ+@1O5?e1vZz73~ywq;5 zjwEm0d$NzrzA%hKPdvUbXSRVBJxad(GYU+9Zogb4=cJK$@0b?EPmd%F=1~XT@4tH~ z#)@`Ms-3jprCrgDCf#&O0toX3jf(mpk(V|eyIW($^zwN<(-g%$Kh#$P@2u-kw7_vw zCOY!YOi1nLdLZ#HRfx+h-)dT9_G3+H_Cu&9#eUQLX7&}IXY4BtY4Ydhn?pXXH8%vC zy|&h{_E~ut1r7;PaeuE5Os4Jx&bgY0uYLB_nAiczj6oq@dr57h=Zrn?2h zJxnl0K^{~s&Dbfi-g#a}On7~TCbP3&{L^p$sPeS}L6iG`wOzlxm*pl(DO3JFSOb%; zfd?}dkg2+CS$3%_J!SP56hq2KUSFx(XF3^ysyD@daq^c6&&R$GOcH!Gw>+LubN%|u zg+zxv+}bNO+3&P{tvF5&n$>aFcsV98UxNdm)5+3%4%%Phalf>nY+;^U@6 zb_|vz(|p^8YjN>A7{mh+=5P*>Wrt9>NAon>9&`c3vdw5-YUR9zq7Xc9aLsj1JT1@o zOOU)R2*k7g`NE|rzPc8;hWUxZB@LXjHBNlf0$I zsa#m<62@r&Z+)AWKHcqFvF-j3T(LZ*q_B*lBWMuLx-nq={*|&>9$E2Oo)Cu}1QHVE zn&u>O>}$2ZaMRj&w@;Jm{2&I4gDflL)plCy;+faMPVc%?fLXYZB8->*{hWL0=DbQs zZx@GATUfcSyvt3)2rcYKy2^ zUgTPxSLjv8+TC?VFP*zL;w6V|kJx@6^$G9ui;u?7Q?X(dW7KEHFg;#ybS(+a#@01u z3azXrgho=o1=-JO;K3L39tG^MqL!fI{FNVBwj54qV%=^)P^Vx|oGd$`44E8x n2P`ngkV_;MjgJN*eRHt0aIr{$q)gr!$ diff --git a/css/images/ui-icons_2e83ff_256x240.png b/css/images/ui-icons_2e83ff_256x240.png new file mode 100644 index 0000000000000000000000000000000000000000..f2bf8388370920783b94285cb75827ce4b4cc1c5 GIT binary patch literal 4549 zcmeHK_fr#0w@yL`gaFc{D^V0dT0jt_h7t^f4$^rEQj}g5g#baS(xnI}2(MH@h)9zn zp$O6h3{7bfL3)uU1VTRa&U`cXhx;GgXLk0S-Pvc(?z1yz&UtKXc$0;Rp9uf}u;`+- zO#uMlsSDU*q&uA_boCBTH&~7J%~4X3wErLfRY0J5#phHY>p)XOGXNjkH87)b>!WPx zi?{rcu2_Cf*?yJ_D%_fA4E(%}@D&*|iD3aE9Ub8Q5I;s1Fr`|n8{`2cJ2=F%MrZyms{v7&r0rf>&2b|hJ zJ{q9YZDkcFg(n&&yxSR~su6zj=B! z7A|uH*>BQlsX*L!-`H5IUM{a6X_BjqY{XC%d=Nks#?H9fVRw$pQY7V-8n`;H-J#Dp7{n{!; zoScGT%oF%zObcvCO77SmyV$UtWUT!copFB4dE_K&eiX_Bnfp5`Pve~cFs%LR)&K(m zdcm5KpKirZWW@5_y?@P4--zd%eDXBF!_~L|3IGp2!7H2QyzkN0hXh z@EnU9yPkCh-Twur>ORhmMa{YS(GCGKTKAP8QGU~#pi?RSqDf1=TYV`9(zki;!L9`0 z!2fFCr|G6poHD?}IA3l{==(~lGugVI)Ry5|MVtg+UblwXLe(BwDTybz5zFbGGA4D# zm{^>@0knH8Ff!QT!pKg%f+tpK90VG1o3$w^WWSSrM*_7RTL(Na_%s<2HK57(Ba=iG z;jb)+4{lTQ{;CB&*+o8{_eX9$0$&9!RQW{z`gY|d$}e)8cI+gK7=ahxdPuk2dsWmawrJ;2QWsD=7HV^ zhDaN|xWU^oGXPXc{z#0n#$&~IfuK)twCPs@S58~Oo^55S`qvpIhKT3jO(2Y%-K{D_ zSY33w_!l~feJ$|;H=|XqdfaeoN2ih*F6V8WDL4$!{r5eGT!LzDhCP95*1)?ruWy1qxxFz}wN z6uJtQ`(7N6@8n?JDGr$v%k#F)g*;_C=_jC=I505l2H1pl+hnX{(J+*KGw-Z8umX@Z^>(P1?+*XcvNAyxeGfWJFry_MPXlwX~=_J&H8Z+HF2pQ4!FvZ@F4Sv)hxIRg?Bm3B_+zC_jek=&*sYe5s5<dK8(qtsM2W;kk!+)V^>Eqo@;d=~*CQpi~Ig;n+J5?dX0%zn(GE_9&2Gpy;#_sEo z8%hfcAeZukljD?@z=nVjZ7nvCTKf*?&PT7qe;OF`&$dT$l(v&dWV1oCxH}!1yXoYxeVd%wR*6#Yp9oBh7e#A$`D5%4U@ zac@v|fT;g4_0Ay|y>fNWPRm&8JJZzVAIa{P&5p>t3ZzAj{-B6uCArK0o~jKC(?WmI zdW-6QtmCZGiD{Xl3%=D8kf$>7HH zO_EM_%_ehwH-;!gPTts`Mtn9Wkkr}YqTU;dXDp#rD{>2o&-7FiYWFLAI%=}>AW$EsTU5j za#ZtF%}c-vvZHt(AP>1qZ3nBorw!8`RD}by zD|$w?oQsIWi!@>qA?!!&ygxcyCp&$Q zmImn#xK^d}!o*@*51$#?f}Ckas*Bf9i=J%qSFFEYQvXG_RHKmUH4FHW#$z5HEWkBd zER}0l$v7qTZ6_iF3+J(9L&XASw<7*#^!s?Ryex}K!57skgL~bW85=7p)Pv2ip%Qwr ziiIQo*_=VZ`y8QmONUUZJT4+9Ab(f-f_lY9TiO+0pV7iJD)&I3*0tH8-d&_Xvq`I1 z?AJg_JC;aWxP3vw+4{zl>l(6z;+ccT`^_h1Thi-KDod*I^f2#lfz{?1wUM^+&%*ZI z`Vi${KZ4#+TZVta_*Q$h?LH4jzMW)X%$Js9c@`w+)%kZN_??H3qTcWsfI>4!q zF+I)XZim*@*JV%4kG^H+Bx`_G%slQjwAbFAMgdr)-#1=rv6&`u*Gjp(33vY)`(^C+ zB*~$-kk8-JQS-WU*5b3(d{MemqFGk}$d-sp`dz+Qp1~w74>S#rC?<;jkh3DhcXm$h zD_qC#R#jrJL2!^Gj;-neW3H}4-?b}bbeM{%xh#^F5Kj2*tN3_k8$+>Z5pV5QuRAJw}DP+|O# zWq8L{5ZuGNWv>buVHTyrgaz%(_VboV;|#tryvFqB3LLAlo85Mg4t`K#dE)|@vs}wZ znJlt^!2(nJ_T;xYS)=O(1(U&Q9}ftbXNFye;a?#d`T z2M@!O^4*7hyk3aO8+@m^Wf*4GtW4Sa?H+e{vR5e}51#=vyG0A-nEZ~lSMWeg-CCJ> z6Fok9?q-XJfK@xk%Ppb!Yr=K)JD%3tt!oZL#SdPAO1r=_?x2~v8*!7^9bqN|sBLgQ zLn$-no{>XvU#s@i8?F!WS4%1xJdE_taD5X62`FS7dvAZX%XJxL(Zve_%^9g(rBj|A zg3<}MD60z}+ec>W?ggbv5yCd-CzXXe{A0mf1A9_aV6MVGx)&viEUP}kaC!aWsk>@2 z?QW5H=FGkY-@HnT2?KyS!?N@}1<1!4Zp0egiXXW*q+iw{Za@@IFWch5lR}b#9XcL? zLm{s}mMC)Gc#Obqk&dL`op>#>skmJ&`CoQvLQ)_z(ey2KdQq8WEZm$_y|vwKU+1X* z?(E+Jmi<`*2sh_K=h~V2bqV@dZDR5jCGuu74um(xvKbc_DQd}JMxUo@ME57ERLQ>Y zn0$2bnI044AMJ?*=`-nrUf0wOEG(q13^PN3in*M~NR%2M*1PLm6rh zaI*NRQuzyw>0mjaCde#U?2f`xi2rCn;g(rM-kneJ&oUWA`DdfTXd0Fe1Fbq}mw=#k zLyL-jUb@{L(-w!;=PK6=eWf~$q7}Q1Y^#i%wLR1v%FkvC8qFIug$g7MP}lSzW_hLK z&V?IZ7Msu2NtU7x_-|g}WO)9ud(AxIFiaH<*Byz_Q8SmOQbVrVpVwu*sfTm}ax5M^ zDfe^qS~JuE<4g+Ve~~hu~c^@&DcW10?qfv z^?9H9y@vspU*;soonncFfzVeAPMu(y2g5gNKc-izg~Qb=9a z>8U!`al!HjbD%2L*zKLbOb6-I$uGZFvZ>O%>+I^Z<9CQjGqu!aF<=)%d`0jk zy&J-ttxq~j^W-=Rj}6h@SxvFbIGGg2Mtq3;tchRu;fa2k+V=)wg_vJC3 z{V@8mQ_CK#2@Y;bhaZ+n@LAO!M&=JPSV3xC*+wFH&lTc(H#A0-&|fIaaF1ez;|AwZ z2Admk1wKxpKCkQ-5Ha_iDN3e)9halVFZK(6l!=RDJmcQ|{mcAtc9n< zxaeu+Offpsowy5vl>acGXMT1z@!T-T`(BKn| zf&Ur@``uLtcxW{=V|O-&-Z-=>{iK*aeR8tzStS1c`oPFI**G`Us~cYtQKtT3F5|?q z8$z)`X3$nvX2(I5%_pIoAXaMCIxic3jJ7g?90D~Wm#2?)v=anr3^l|jIgdP>cuyPe z%Yi7XKxfB57bPcums10fMaalYBCbiw$e7E>DaqeZl93fhAe0aYM44Op{~~z#IJ>z% z{J#kryTd%E1in8J%zRt}gB<-`02bo? F{sUG;a@YU> literal 0 HcmV?d00001 diff --git a/css/images/ui-icons_454545_256x240.png b/css/images/ui-icons_454545_256x240.png new file mode 100644 index 0000000000000000000000000000000000000000..d6169e8bf9389ab9b5b7d2c6f0c5fe3e4d363105 GIT binary patch literal 6992 zcmZ{Jbx<76vhMDpfgr(y1`QHmaR~%lf_s1jw?%>l3$g?j_W;3NgS%T`aR}}Z9Kzy3 zmf&*v-FxcQJNKRU$4vKBbyv@PGu>13O_;i>93BoO4gdhaQ;>&f0sz2A6>yA={@BZA zI!itZkeQMk1Rz`XI+62n^yHKL2bV`F|KIKHpzRv~fYM$8BK6MW*I@?C4l4JhU&5+Z zX?B6>M{wc*nf5t->YI0m%k!c6iV6D-HiA+N=Q{Bvn#`}m4j85ZvFzIFY|%J>xxbN+ z)KhhOMWAack{rDyA-k^9P{-{Y^-4&T)}WQ|)zSX7U*Jm-kJzQ#7H}DD8x^^@Hd^Id zO}JQr`JnFakU}mtj z1A#B^`J~>9Aj!aCs_5V1v5fm^rO5!bpr=#5Ec4gVZ+s}W-{jz#54|=utEYqV2QDVqeWLoK&SDCg)CL8cuq(~CMMZgT%6AWTd@*7 z6&71eU>T)Xe>ATJzEE*l7!d_4EfiRtlm6_$KqoujO=FRN&<+w0vNs>&-_Ed+(5C5$ zmkYkr=0{kY`cMqEqv<( z_g<1p@{QK8lz=aO1*Gv*be8bxc48H*~?C5sKsk6Y+G94lgYv>T~L|y)zUs?t_{Tkesms}ypzUDGt zRKP9jp1&y^@&RZ)xNxKyc}wp*1n`O+;#zYQ0#N>+ zwL0RCF;cJTN#50JeIN3zGCFJL?|Pv`sHdd9Okxo&0#6;1k;Hzh-x1*puL6C4Rf06- z@a?%qB!?Rcm-Kxu^;0S$aw3oi+ReC&%oRq{3G>G&kxB}sam-E4#4k>x`2Edch79Z`l~i|Vub!Rci8uT%U5Qm- zqzrUOWT)C)V7C0Ot{iK@**yiK6J*$P=vs^p<%g&3l?e^uQP(SX{G^~DEz@uDlSwWC z6HFn2>d!B-S;p7EJ46TDw;VlxIsE?PQTPYF)AjM54|{7S7g#H5O?B`H**ZP3#Rcm* z14Vjb`0wAD5v>;=@y1`jQyS87ZX^RYr;e045q~!y3PGK@B^ND$Oe^By*Pn{6o@PbJ z!lX~i+!?Z!hDHPe+ix8>vxm$X+}l3qzB?$x6vTUXWbL&MukaQe@6BjA#BLZ(saaH| zggtTHoi}V+RQiLAF|y}jtq~lUu*VwWLl%v`4A%T<8BjfHLPOI12-{xii)iECHzfHd zr62uYRj@yB%Cen7i_&0M+83`9EAR~5s_^5&9d+V~;i7mZj`xA~1xnBw*ODz`UG%eN zwZ3T!bX~sAc;6U3Vl=#&c<0aF%RcBv$yV`;1STH=o*?s*_U2Co?5UH4rIxGb-_86C z0V)FXqarc$Tp9jU$_LA;l|P&~n77aoKeiVuBf+GjpUp$fXB6kUWgiU(@RP6-ZEZ^! z6&A#>(=ZWB&pdpM;h|6VqZAmie8G@Fcf$1v{(lni-_NX(c?|LZXW$5{s?(yKDNjFt#Y{0GhcICF7V&#Q=1gJ<&oth1v|juBqBGovPk-dcNT(hWqc zWJ6H4Mr-_$uiQGVcZ-Kh6@v_>YV?tg4|uPt-H=98>l5Q)dqz{@eqQ+5i>@XOx=Lw0Xjqh0_w6?9r}i zHz^HH<3>U^Z|^f;3Ltd0j-A)8wr?S-+)b`ua{R?dJbK>}>9YdzQs=FH5)}5q~n^@mpnbkDf>;vFH7gTfo1GuAqIgTs<^LXm9fV(}{jL}_UeSnA>mB(5 z*^HS7)v)lv+8RF85S{DN^}DAJ3$UL@K!=hf7KRfi<8`F6N!2;flN(lV=4S+jIOp$ z&wiGBqp2e~U)Y*0XNoN*D6?F9C?0~OqzKTZ|CIO*v}MVErdyBA9oC0?Y9)*eri{4v zt17yAMm?p@V*cb#Hvw)krZA;q@IpgTXX8_PoUdYZBVTm!*+lbrHvpo=gZ{7Ug@PHF ztf)&_aQ*FG3cvp$*nZCdEfz4`hu8SiZY!R){{6K*wg|@|8S79F+w9cQ{?B=>`ku`f zj?Tob2hPNf?6M|5^{zL0{x-1?h%b{7uO!`AjnZ#vx{8VYmf}L=)Coi^a4gtedw<1E z&98NFQ%awC#S$?PlDy6A7gUAtEOwf6Sub2*l}b`7VqbPLO7zKEBi+3)buf$LF-a0! z-IDu7roL^8V&MvTlFnupTDE2HrkpfNtkC`xk!nfmAWQ$kb~wBHTLHj)w{aO{pHet( zo-t4j3FMTX{Z2ibk_a&om22FD=D2D+L7fOa-^2!!;`&#lgUhpn)<0_`P zPAvs$6X-Wo(Xn{=i6A|sYBOBI?JZNFts7##k;ah^QKUbGI$vXZW(eBj6Q1I4CQjPm zp`vkoMXCER^+U(lGG(QG6C00(0K-*e&q_Kb$V!DRqfj(5@@bgKCR7{k9p%>dY+)5d zepzE*kZG)Y%=Wp@W6NHyjIv-KVWidJy$;`Fyyp-ZL@k|n6d z-ud<^O;$1tvrmX&CF`^y6LQen#4l1DbFF=xV*Yko(y@L=4Q9N#dHKuS8Wr8@783TREimPeayuWEgh(Glh^D46x(av1+! z=ktQ1Q76vNEP&P`)WfsgTSyflWj4;eDSyt^%R~t!*1NQk333xEZ3xBQBShRxrz252+a?C9G|#oO^9xDr1KUz&nUjD8&f#!VBPS`fm^ zw>P4kEeImD>VXgHnOPp0i||U+e(-EYGW5Y+WvPV0E7E7u3QmP97?d z?zh65fjF@N^Hj`Kvw#meID+CQJB936N&AZ(oty?pl82s38*l2mW@&lEV3(rtTdb#N zx*h>C)Vh;#NMk^vQiF-Kq2kCP!@}(Q%!oys4KG^=Y|om ze(PAL;ga^D@4{2ToH*SqLxjs(rN!;|qAg9SuDPfDHc#+fUE+%1a5gSHvD!d&2l8zOK;zr_$Z+%6C|;Y@#iL$H0Y z^@`<)>wP)#c`2-;>1#!*76jk2r7JjI><+gry3kWX4ds)$71awl@jgwo~@Ilplz0QPW( zX;U;YX`3iK@B7RgQG8K};*vsN(XM*PT7$M&OpOuawA{WXNSjI)J;UI*F=e@+eBbl+ z#fE2F=g|oUATxJ}Dx5GHh${qqC~3}@K6K;x%gA^W$d!B^xH zNejV0tgIQ+@vt}JZ7~Xq2H=W z0$=)iMqAv`u~Ja7#=bYxJ@bky^*YHB{cjxy=HKG!>^(xQl>}3JV2B9yOS!*jkZBg-Z^~jGb zq+rflL_Oy^Hqtv_Rqc98JA`0C+2!&UvDh9yxzT2s%R4FjMRLdJGtAX`n=F)Yw%r7X zEEK$}pA-<568b@uSA_NLi@GRHOSiP0t@fgRw{Qa6_{Vj3@E}v%t?ucIDFD5T!5TGg zb$;4K?M@B3##@3N2UbESO+uxFV9}S!?~|GvCC3)0PQ{t_pZ0q+$J}gsQ@{m8v!^9D z!uk!rV~%gr@sb@iQEC+s+Q7MbSeoZ>lJ=P>O>l%uU6C}CNWihy3-Mnp`e=;jtEFco zB*=DczVn^bRSez9S|{9}z&#^*SV!vJMrO#Z-p$l4e?39lFmcc&-8#eIlGKk9U-@Rx zQ8?r7Hi2K3cHku#z%g9i#S?^&J^1xI+3RVN<5h&32x9DnAsP}bi&rJX;n1QjO4{IU z`gFZveK0;i1m*<#?j>t#+r)}_(Hi+!+L>AyUSO;yV4LgWBE;FP} zXB?1wA8H5NLIQ-)9-bfwYGy~Kjnc?oi}k-MbtTcyt$AMgYrFCC9Vx@W9iNuAh+W_C zP@Jl$^SqK8o~%R!cDy5xdyma=sL{7yGB!8*p{pRS+G1;X=$-Ml z(aNAbWRU>^IWXqS_f3Je18@EP?*LiQsQ&kxPg-8VrEw+mEw$$f8>*?f(Y2Bjw97)DTN2-B>D7QwR#`K8n?=j6Q6}{j2P>-q6?B=Q=7lc zRHYqg4)nXZ6kN5^UsR-*@-liMF(NixSw-tOn1(cV(O0q}g|R}DzFZN?hWIExV60HT z+OQV`iRJJ1q9SXlBeWwPwyba%SfrT&xkBAO2e1mjBRe67vIEi*{p&gOwGA-*@h)g} z&gz%j5(cDCVB-J*MPMxpo{Kga48P!aylFLNP{jHQ<&!B{)6ha^m0axgnrIV zz9w!NG363dJEd*R*Ls6LrSSDOWDHIi{^t zEE3l>G=eji-}6FFEho;T-3N+*lWM%swipv`flWB_LN+e6?0S#2 zYv<{a>K~iF&*C?S70EItez?Omk$QevNDjc#AhSIV6`Ko+zNfTE@GX?=F)dZETbYxe z*A}X=Z~pxMmCc8OCcyi4YuY z4X9)<>d1HDCn!(%kK7oGN5RgUXWyzvWMO;64A20#9;hV zjA!3^9W|tk<25zIOgbNAbI>DRQSNPz`Xm{u+<-idJD<&ZK$@9KFIcD*Poz(6uQfI? zBtt?r`uM(A4J?4fTR>9e#ua`GAyOvU3!17$j5Q2JnP{%>>sPkQBa*;+2YETi(`BaL zNL-6ba&k3EI%ZUCR%JK=w!;*a&a%|L8arxN+ocAjxOxHevxqvcl zyWdPk5!#m6&TM7Rz}@VeGS?0wx|~@D0g@^iJbk3ara@H1!9=Ijl0xTJ^k+giPbG%) zyaVkjG&5Vc*USApUEd2-%_}d5{KJG@CLsPFn(q{3A`08*@gG3XCT&&x5lbb|e$M!M zv754_za7E=;A|;&Nj7&)PdhHaTsN(1uq9+Y-VKxfyQ3mnx| z?5!Q$`)8Wq1TzHZzD}lM+pU6}x}tQq()Mmwu5S5KZL4}!#&6)m*^@N}$phJY276T! zFZDF7SG5QpJw0)3pph-X(h;_ zUU*UTZEidO=VXj^vvkj@wB!B#>4{K&fKAIKOUrv40bNCgBX*s`r@xSjf-ZzGy5r}& z3P+RfVUB9XTlk9tKJaV!hxZ%qtofSx5A*=v@Ae(PciD{cUxrLWfH8;ry`cG>!KSpR z{yofDff(^S$&^$Mw1eF=jBKA9^j*})vouc)6JIs-HIq9g^jpYfhnRHfTd<7SS0m;i z$;&b#3D;0tzPK+4$<~x1q8pgM5djc|@GKz!{?%@4_wFSrDgLB&?LgU6kmGu_It&fF z6CVdmFpYnvgHzqTF-h6FOp_-!9+2*x8pD1^MZ-wLY{=#N-s3tSQaw@Hpr+YT#d{|k zcDHAs#6Sd(&;$}0y!!_rUr~F6+V;*E&TgK^bs4)So_HDILoRQ7iBH3I#1=P|#8F$x zE1H=2j#Lh2rxq}U#J5j4 zG@Dw^$S=tpC%^jM3D6PG*OLEh`;MukDe)cg?EV*d_upAZPZ_YptwyKXB8M?Bi>N+Z ztl)JuZ0R4*5k{XjU#<`c@)8#+%J2^fYy-?wG!0e6W;R)kclJqUa37(Y%IbH7V0r>6 z&=}GBDflKC323|mlZBL%xx9fl!8p<1Sn+}8q?)njX<9{dzV@Ks8bqf| zutHPq3mfmH#uxxqkv`y&HLM&itjaWfgvW$+8H)r4`F?~P%tV-W`AA^xJh3nzCj;uI zw$?}ZZh;R;*i9euW7`k60YCg-0a)2KUU)Dlcpuyb<3>FAZ zkmbGIJ!jwU+aLE<-@aAd=d0V*UG?2~ElnjNd>VWJ06?UoEdLq+KznQgj&U&`?+SU| z(vJe&T1`nF08=?)XMB9Z^HetSd6dfkyc7prBmn>#Uln;7-N1#z+z?m0-mrcthj}6w zs$IVc?0MB*NqLmqw0W&IykMhE<715Sw-rtey|Ppy$7Oa-7Uhic!q-qv=X<ow+JFz5Rtu#ncw$ia8BfaFp!Yi96&?N2M}Pz&3@|7>i@(XAg^AF=PIm% zdBX$f@sJ{q&aiPuPurDeR0+DWJ9uu32I4}3?gYQhgrA-CT%pJpK+O(p>XrI$V0R{U zxLL1P<*w&<4n&}vYVOvojf#PYDL4(ItoaT&oAo$3+?MM4==HjngxIQx}zMlWZ ztE?V0R?Bd4DcZR=d`R8wyk(WBBHVPK@fsNZ>@O^w0O_toe=?tOEEq`?M-7ebA&~A_ z$AYgPqmO$AqB*|o00_~AOGrx|)t(}H*c*UIHwEa(Q$Z_J63g&6xe|Ue7;_`*+AEjo z$>}B}YxZb@2A}g}(mwvKc3PbCPzw_bm@esZ(8r*d#mGwhy3LL5ob@cd$X%d0i*q&O zZg0x1xL9N4(u^ob0@@kq&I~8E@yO@hM^k5B%M>x&YbG95kUJE!MJH8`2%b}rHt6N8 zvgb0-wCgJt_Mh%h5V|ghbguuhZ0H?s1j2bALH81fm46qZjcQ2!%@Ok1f@Hj=rJW+2 zx%6M~{O7m-Pq=&pW6$X}Or`HSb&5_A2=lW@-ENQ%sj&|!f0-)qkUvh#yOjXKf{j47){seq)+IN+TT6w#5e; z-*Rbnh(^uun-FEGIa0yLpU}F#GyH*=!)l?j_-fp5t|G%g(Gib!Vcw`o+%4n}FHC?K zNW5?|zY&kdZWBGM^!;Ve_)7pOQZ z#y=CNiOf#$!7D1x)`Oe98F8A^t;|ViZem=VG{a8Ov!eETW%fhn8PQs?*#0|j3?&}H zQgLiWto=;)HPT|v;pN6w4Bfuv8ayF!!dE!$*VmLAH`&O4yBhxCy&xaQ4Q{`{+P@eK zET+!LUS?ySJU5TVeI3=$EuvGs%OOqpyA0(;c0;X?z)eT&XIkPX>b6IMZUoeIt0O@l z)TBE_r#j-sdJ7~zx6DeBjTdeIqLIyy_)0S_VZ;2j)4xFXpb}G!L1awC|Nrz122ncmG|%UxLJPaPgtw`DmX2w=n6CF)bm{BeRb(ca>s$T~6+-_K81 zszu{Z^Wnbz13nfBz8xmAb9*uAEy!-v-x_A~Q}siS0zl)%fbfT$EG-YZ^g`F8XbpNx zcY@n+1yY~sR@-{#(9`?d;JQN&Hzx>z7hg#3LfPMRRj<1~QnD49Xr;-u{}qttfSY$# z9~-zB5kMq%TIY|VU2^c@a+KoT5?1Em5gAwe52HpPYe>dWSI5@Xv`QRNaq!f#soe?? zX!sSe@T@Y?Lp4l!pauu`9hH+oVk<{D)V;gcZMAwZ&#`7X}FO?s#hB}pS}_&1FNXwWA`#RH*JE>`3onp2%ses#ccCT{QNf5 ztUwTqY1`c4Ok3Ij@-x%Y4XHMPiAhRgL)8KDF2uGY^LwAkvXyGZFHG%d>i5BACL(hS=02&!5CF~lIP3>L$4Kn4Afe%0k&~ec-L>NT>Vr& z-+V-;CM(TpCyUC*ORpm^G9>w*8^V9>e@`XQ9N+z5aS}|X28ft6589H8Y5Or5`vz?3 zxz2@?Dq?Rh^|ohQ_qlA9s4=tf*tC@M?h77#CP(f|Qvpu7n}V8OIZgAlsgmJN#=kya z=~O;XDq(8-cs2OdS#x^iRTeEw9bg;qTfz3iGk53=%4i<}x#n(gPSTA?^6FWc-$L!} z3~}NADQjs>a-@4=BCT&{pEN71rr&+PcNaJ|X!^w(k@Yq7KCRdFSr`dDngvcgfJKk% z-hipFTFS%Zchw}rpUKmpWD;5SU!HBj&*<=tdDs|`0wF*cNBBTe`jGjD+uC<$#39vSjr268557Ekkk`o)3ZcuxDj z&yz=YzwcxL=%CblrlcoU$G%;8OvR3ZN%4@bre0jb`kE-F#G=*Mq~h>*k4m&ETb(L}gn!Hjoxm!x;q`X)b~mKK{0;ApRz~}m4)#0AKDCM8 zQKnWCeJ3zPUTbi&}+-HV@&jM2RH?HYfA@_ zhBt2Q(k2^TXNJ$cxyh{3lEElE7j-peQ0|w-lY-=S_PFaS+P7Y~SqWG&A0S!!T1%ID zM9!y`>>Ez1@c`HF242r%v$hkF1g^9tP9%M^lMAFcgmu(`?{%8y!exJrPwHnx_$Np%9@p0rNk~za0J+JoR>ov}a|I7zJ9=r~u zKhxV!G0K2GqB7J1(VP%KJS{U8$ar`e-1n*Z8t*H{YH`(D1aTQ| znGcBGr5q(22XB?mmYRh7S0PvGow4sL2i8;qdII_U4S@2 z;Cuh7H^DHq#(BVk=>~lazw}C~@iYe1?B}BFEt&=IQsNw9fN3oUpn`LBPh)5s(FHZ| z#4-T&Q5wGp68+#K3U{_rqq^w990T>!jX7reXx8;|H@NE2?IfKHova!%MUZzSCeJ!f z+A}cy7U1Q`odG@69{*ft6rPg}Rq^6`ax>ZSt_B^{<1Al#uV})(kp&(lA!2f)v3$b= zTfEOKxG7<2Q^gGD4;V4fo>EaNi4>!BI`()1uHjIW_Mo8%-f#@18wawG#rBu<^ z_$dxvm_U`V{zqs$rllbS`5QXh%me7--g8-1aYKVS?pd#mr%JF){7-lyGPQ1UMt(xJ zcgt1Sej11S0(_x}+9^n!=5}yJ@9?rgjb9n1M`_yAl%PT>(XWQfRN(!sRwM z;Uh0((ce?s-w23V!V%wd=+;V8^K>83Yjx|f$@i?C-KkL}_=;Bw+rxX@L)@P4-RsvB zg>cv=%UskAyr-C}DYZ<9J*?6gPCPn(_|W96QMe3yX}U~QMIm;UP2nS0?&}Oqp9@;g znp1ekjD7^_)akR!Ox$FqPkEKi_;T@0Rq#~%wP1KMfPeu>Ji+r%*BXU#y80TejN*tT zf_+gFlkP`14&fhu3yg^vadYoNqwj{)iEL@EBtmn^LlenEjaof7gOQC=0-td(O^b^d z*5LN*Lml6*ZcW44>?7(!>P?)vkMSg*-`x}Dvqy^#TZtiixa*MfFtv@RjM0IF@Yib> zyo`+d>#{HI?1fu1exJH`jkOc#??>P5&G3zWspz9fiOR07Wqbj7pkdhJ3We1iGj)Aa zc}Ea04BzV!ys^8TQa0!xyIH}7Y|k+I8>*;LWgjo!vhFR@A6lM8SiHXL3`(8&$$ z3-pT;_m`U>fLd`VSq3%&X!f1AjGG#B@Q?P0Zl$)Zc zFY>3ig1|@Usj1t~meSf_%zsS);zUcTl`B4DNjLX;qQ>SJu&S39D0_J(W3TfM79`}9 zrepV8JdBZhJeJ~q+B)1(LCINX2M^Z6X7s_*j+O|{I8G(w6x`Akz{(Jo zHdj8^A^tC_K0PVN3E-t-O^MB`ie;!9TSq@=?R@95!~#QVVEujIH!oBZz7;Qq4$5=zew@8Ib>j46f0WWke5 z!38}clJCN>j5oNqR9v67SZ2NGrM3+4`M)e&8{Vpkqh59sb>t_Tw8SJ|`p7YL&4($3 zgXO{{IAai-BWq6|C0R>_{MI*G+{DjXYcUW@%OG-J(|=JpPJ9zVsj%g zxDzn~F8BsrOq;TAr#|I6L5cLU*XbDG3XnQ-`zu3q;VcJ?3>k2?3uLZFpcVMO##!fV zzvG+mV#hZV)f@6p7=1c9_s*y%bYF6S@!C@ugJSHt#D{gHi^kIzGayYn{6^y|Gj)MM zmWPa`0inl3W#?s&EH~=cId@xsBKsE{Up5NZOI}!!s$4FadZpTu9(?D}>6$7P9Q(Y( zF!1JiCqX6R!<6JPa}w1? z#M@IA8=UjXd$euB4ld%N*?fRh4ep6HAT!%0`#C;6X{wtVk8ml+8X_{kF3Doe2>8}L zkp`~BRozA@MwuCEMY!I)Hh6}agMGyukrh5xlbAMNW4-2eL>?~joh!Da0+ZYMixk$H zMiusW5E;HG^D;?L5gbkaj%@zo$2&B4%zJVugF{nJS*I{hu=qD_E?)|@f_k!=g`KZ~ z29(SLciCm-Y_8vjhq@A#=NM2hlOWXs)`NVjPtx<@d?%t*8wlpVR#7jGaDXf{P82`C z^go;k^XeR}EM5x_x;x@Yx}V2|e9X&#ikm>= zF!=!gtlXdT0UC@W)&o3+n^V`r$y>Do3J=EYcO@&P&|RXjMdJDTX$oydK1h5Ywb7_8{@qx!8Yx2oRj~Zzm9QwL z$+=m6q#5~5dw>0^&3aHzsm>DP*XED>*}jID+MjakrSZ&(CE(Qna6RuD<|^wVjsGj^ z!i1mSd6(+OFqS+?UBg9g=p87YXHz=ASgeoI^G!_vTaCth}Yv84AHI8%Ej zCv>L;tR072%L;N!?039-xqP{2<<^J}q+BOL(zuu(*lwZq6LL&%zVqtScp>0z-|f;U z6B$%<=^?--XWF&b{AzVGiP+3ij(REP3vgZ)+Yu;6}f}G%WjtRbFN#Z|}yiY^jAfnnA zZ?Tm%i6*r+_d~9$-`u=vmDn{>y=g8KVOumoEDyfniGByHAGH?8H=KNXR*{D(PyoY} z4jWx*)4~cky{0<{cEMqjR=uT2jeR^n9zt-QphV)A$0I>I5=Kp;8NXMPROtW;R#I@5 z$_?NPo)MzjC0g5(S^HSQX>zg{e@~|@Yj)AkpTc_*&bd9zIw%X~!|UF~cAE*Frc(Hp zj@}l;MeWU1(ZkW5(G2bS0@E9aD~3UGx-x>@pMn^dz&Rn0YPG$q&kmSeq_qM1T>9Qm zYI^YnW9*nJFo$bphmyM%(<**JE@a@vqfa*zrfR$6@t)&dcR=-kLi$xG`N*Gf*YfXh za~m5tA`E&hxpb0Zce$yJ}~q3%Kjz`Kv_? zn)3c?d1e4l8!E5uxo?KLyI{|k(A4Ul$R|NQgJnxzSS9{hN1tT`$b4h5B!|MH#gS3b zssRIJ#@v&2Vre{WoTCq!^e>1%KI*VMj^gTxajcSOn~~TEF45sTA+4#ERq}y4QD|;g z1ateBghNF{wT?^Zy}>nJO>3TJ8W9@%5UB#Ueuj+xa1tzLhoAk>Q$QW{^=YH1zN3tr zyJbAqdyvuxEFi~u;n$MYsgekbXAx6zV?BB&JBOu*CkE)7{9#LitCT@Rq%u`#smT?? zp0QX}uWXyJuk=utr&yGdhHY`!DcHMps&UtWlV{>?`e3%#n8S6$R(YU9y0;=Ud2nOhSNm{1I}mTso8#HO&u-C(#%}Js3&$!SNK(&=~0>4yW71nE-1| z^d|N5LLYfY&p?xZB~k^@YIAY4K7`XI6`myqf^3fSz*oUi&vtClWO`KAZte6+!ak88WHwv zGfoaz^Lx${xwsZ#!TC6%M%q^PIt1Tt05yo@Q&BLrmPPOWse$_EkN6@*zl&vDX;d$7 z|EWSk!$^aN2E|xQhNTQPTp+lgnJhb^w;x!+w z)bEaRZu;bO#G`w>=0gDH8&X%&@4ZPZAc)zb2iU{<1>K!_SEJ3D%mdm_6Q_8l=xeDcUR=6MQ(!Dh+(WM1t;i z7h%@cKOjQ^$Z@9kYg59yiiiJOTDRQ*zC4 zD5_utUXY+w)4(MUa9*a{JVdj8QsQWFhUP+5+Dqqt_XEPD5nv=-|BzKxD&o8p&j`@G zF}BMzZYtqyeYt=xe)Hx5Cw<2;!K0P%x)K!QB{ z{JQ)?Vqj4*egQ5JNDKr5l{r=Xi-DVmt&@Gw|8F33_LKV2;MqTB(DAVM4Y2aI1IXBV hSlcnGxLG;ay|%Nm4e}ba6Ms|!s3>U4SIb(y`47Y|2vz_9 literal 0 HcmV?d00001 diff --git a/css/images/ui-icons_cd0a0a_256x240.png b/css/images/ui-icons_cd0a0a_256x240.png new file mode 100644 index 0000000000000000000000000000000000000000..49370189231d006600b0f0c2967cad1583eba634 GIT binary patch literal 4549 zcmeHK_fr#0w@yL`gaFc{D^V0dT0jt_h7t_DO6Mg=QF;{=0tBf_mm;7byix@LktRh# z5u`-G(3BPtq!;M~LO%4)d^7il`ybqAcJ`dz*=Nq~vomMTd17R6n~8y!0RR9nX`?kw z007{b3)rHkIh!Z6b&k$9n2q$zPy_~s{~!NVK!y;wdM1!nfQf-AfQRJVvlOKeK;) zG(fA@T?GI*=cKKvX7=#=x*g1MvYr*vi+;15^~~;rE_=zlF{dxTu6`?DZK_QC?%~-~ zxXc!0w@Ia@0%SGD=VuM=t|036I5|bK3akyK@NV;ce80zy5LQw^%%g1EddqL}P7 zAP;7}p`4_>$jI<)(!NRDe~wUl2Lbz~U&xhZ6L-Mi58IN#JcL!Jb>u7w;&Pb=G?j|j z+4;j5CveHSt*{{pnG-wAV&ig>k>+1?#rZDhk&-C+Q7Ct0?m<+Z+Iv1=SjY7(0$nxq zk`*~W-I5nikKwr=c*9oDk~SS+7*M7oAkBSuy`fgUbnRO05@D;12PXct`w1wF~RUTU^h$gt=%W0m`Cw0Xb zn_s*Iw0*)iGT7-%&x*f>!&j*t1{iXhwkybGzn6Yb1ht-6`9IYEJQ)#1P^bTyNu&tz zRu;qux2t%4(*U3DBA?9rA-5ibuLB(35_6kYoAFm)34GMo`X6zo%ywrpzY_^)vF71v z)c2DilF6NXvKP}tDJWcNaLins3AqfCH1H&imJMu>z!m++EvXggAWG(8=5>-NUj^6s z--27)SG?akzYm$a3a^&4wImGm;GVa2Nl&w9g&{h=3GsU|SJ(O9RTJZ{*ZjGb%Jfc+ zX06OTMtgY|8_(2ecn@67x%Df1xi&r;S}liv1eMcq0lt3$SpBYGUl8;UV2l>c1q}p- zNE*Jp#oakW04gPa!be%*FrvFa&}TT>pnd6MHd5HfX1#5!D$Y}-)3(ZGeUFxo@PVWd zx(b&0Q5=u!Vq@MZ4w)0l^Rmf>JYzT=s75ic>B6iUVH29|ldKT3rw2 z7Gh*hjvi(>`v}{1HJ85LD;er=0;Y$u&ItTUV2(HFWfzQClC6ab%TP(LRGVTNH^`_z zA7|@bXmO(D;;V>nutl~0u>Y5ul?kfo7b`xX7D1_PWN0lf~n1=@8PLsIw}H5Zv*Rg+HW z!(_+Lw~tJl)iFnf2vU%9AO=SqUcWpXl7Ft39=a4SLB9Rx2~%QDeTJWJ4u-p5_H{2E z(|7*qVrrjs0LtTAyHoj0_fvw#4vfiwGN~ATw_yz2$U_hDuMN}wb1e65` z+Ghf(l_fEw)!FpWqsX)^N80DKc#a`~JHE;gfLs3X+|TV@M}7P--Eo^VeWU*(^W??N z%wC`l?kiP859ewE-=i}yep*Z+B|RpBm@`%o$|E}fD_5#G@C{bxlll1)Sh^i)!)}T; zQ~e*%(M(=SMwLDKjJ)4^ZW?@L9;fu|N-UJYfiyqgr3}FrI3ZV(p~B(TpguJgRv-J^ zP->VzsgxI-9H+1ZHUNZZYOsJ*I(FE1K6xJf(@39xz9W*Yw1Y@A9TMsjgDbu?Sa`7k zDq0gF)vAZWdw)bK*{dbJVpX?8Up#zp)Il~72C}OIxH%Ey(rJ{FJau zhKB8mRZN&kGCF11=fL>Ui{O#C2i4Qv6Fb&<4U6WLy{FwZENz$8MOqGIc|0$qX>8Xg z?hQ&4@OqC@?;UZ_D%SMww2q~|H%VRone1lK;(*MnK$_?14GLLQlDhpKC|ffzEDRK_ zw<_P6`tn;glyNI-sfxdd{cYPeVRA|Co#I{Lm$9>b<3KMXp(_8FZ9ex%nR=Es_Qkma z`HzI66uuh7!p~l*XLpXdU=}n5f&13L1=l#xn4@i;KuZA zqE>e8CSya7E?$6?yuEj=BHxelBq8z~51*mm%5lBl`asD(`QpvXymT%;)hYaD9sk^L ze7|@({~~P)EkFL%j{>ih5dr;g(c&b@IriTc%0&S>Vx)9SyvRFTG;7G`i$+W)o><+L zBdN>KTyZo_2 z61ldO^ivYwcOo({a4riLR4ib2E8=fP55$S&WtmszJ=_c%Dxnpr zSUBdL%^3uI$PwtUun(okVk2_=^LHgLsa9;Xr(N^$9xY6xa1sJEZp;q#?IQVFjN3$F zzXgyxFnH?1-AiIlR=1wsRFkeQo;iH7-*Q^ECAt2zvZMw_3-kIOP-CX6GSXiDMbNHC z526_CTdgzHp5d1;zSU7hZj{$2RQcA zr>7a;@6@>drtGQN@%QYUWHqposr!S*j=Hho&p7*3(4JItk~u;cmZTzm6SD z67Bm6dHgIK)NeXvEk0k(7p5u2n|AwyZ1C8mgYw1l3_hJXlea1Ks)$6?K zl|iMnSc^VFh3(rmbACHjw9 z2KQ|E!M)sDcFK?uMqvs}kl(IsKW~XRPUjQDZA5#az@aL;#dY`i@FzKzJIjP4 zDWmQKL;K+VHqEKG93SJamsHZZ8|s|n_%00Mlgl{q+Wumj>paS&jpGNJ(Nnrhr#w3O zB@^zDR~J5YjLgI44VIg&8m=OY$&t*qOqEvV=Ufma>04+ROY(@s*BZ*?v z0iPGEj9IvBR7rJrOe29RP|XUYvBqnG89jiVNk9%o0UF3h*w#_fg;`oH+%y^Z_iA!{ zr;DE}6~9s$4wwCFgG_@(?#V5M_>KA(Zka~p-TNH>Jd;kCcQz`Fs%G&hz_OEi1qfO< zFs~TkrrGT^X|->Ap>(s*N21FxTE557rpnMs(_O{B{CqaQ;k;pUC|?qRvZezu%_|*u zD%|ii-+ZA;v=Fw(efJC}!ShetYUitu!j#c)?U4vA6*EZ+CFHu@MQ!HWI!IR_+v4HV za$g6}H3Ka$*0@0SchdF}LUc0%R=bFTyf&53R-kVTjKnbegaszWQrr~OV+*Sns6MxD z&U?@AJ@UW$DknMa3`7*bg;$!3&b=n^FG7`ysp%B-%p*E_3{z_hkyBEuyG&+sa zS>K8)@OBLKer>mahzWEeE13LsoR6En+AaK1CN7TtoLkS2uk*u^CscejmI(?_1unou zh0iKyvf;U&C!yCD1H{73%1^Ru&w7luJzC)_^7|2AfmT7D0dB1jVG&rRL|`k$$E zyf@fb@2^6@L#v?~yR$L0Mxj;dr^U4Clau|=BXNQ2gpqNQQEsSb53VAjO!ebj#;HXQ zglvt>psuXUj)N*&PD3|A%#^BiZWi1ab!7rM1ZqMqPoHRMCh$|~YVl8V9(y!%pEcfB z15lO$P7VRi3XXoxX9ggRkdhTg+z^+NGLw=~kiDfKB`u0TC?F7sGS~9|Mey`?a&>w1 ze-qSphq=xOJbxmXdbWU+9qu`sI6F8!^6hn2Iy(f=Mj2>Us$(Ai E2b3c!L;wH) literal 0 HcmV?d00001 diff --git a/css/jquery-ui.min.css b/css/jquery-ui.min.css index 3c6d34c0..b0a15796 100644 --- a/css/jquery-ui.min.css +++ b/css/jquery-ui.min.css @@ -1,7 +1,7 @@ -/*! jQuery UI - v1.10.4 - 2014-04-02 +/*! jQuery UI - v1.10.4 - 2018-06-04 * http://jqueryui.com -* Includes: jquery.ui.core.css, jquery.ui.accordion.css, jquery.ui.autocomplete.css, jquery.ui.button.css, jquery.ui.datepicker.css, jquery.ui.dialog.css, jquery.ui.menu.css, jquery.ui.progressbar.css, jquery.ui.resizable.css, jquery.ui.selectable.css, jquery.ui.slider.css, jquery.ui.spinner.css, jquery.ui.tabs.css, jquery.ui.tooltip.css, jquery.ui.theme.css -* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS%2CTahoma%2CVerdana%2CArial%2Csans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=gloss_wave&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=highlight_soft&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=glass&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=glass&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=highlight_soft&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=diagonals_thick&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=diagonals_thick&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=flat&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px -* Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */ +* Includes: jquery.ui.core.css, jquery.ui.resizable.css, jquery.ui.selectable.css, jquery.ui.accordion.css, jquery.ui.autocomplete.css, jquery.ui.button.css, jquery.ui.datepicker.css, jquery.ui.dialog.css, jquery.ui.menu.css, jquery.ui.progressbar.css, jquery.ui.slider.css, jquery.ui.spinner.css, jquery.ui.tabs.css, jquery.ui.tooltip.css, jquery.ui.theme.css +* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana%2CArial%2Csans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=highlight_soft&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=flat&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=glass&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=glass&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=glass&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=glass&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px +* Copyright jQuery Foundation and other contributors; Licensed MIT */ -.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{min-height:0}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin-top:2px;padding:.5em .5em .5em .7em;min-height:0}.ui-accordion .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-noicons{padding-left:.7em}.ui-accordion .ui-accordion-icons .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-header .ui-accordion-header-icon{position:absolute;left:.5em;top:50%;margin-top:-8px}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;overflow:auto}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-button{display:inline-block;position:relative;padding:0;line-height:normal;margin-right:.1em;cursor:pointer;vertical-align:middle;text-align:center;overflow:visible}.ui-button,.ui-button:link,.ui-button:visited,.ui-button:hover,.ui-button:active{text-decoration:none}.ui-button-icon-only{width:2.2em}button.ui-button-icon-only{width:2.4em}.ui-button-icons-only{width:3.4em}button.ui-button-icons-only{width:3.7em}.ui-button .ui-button-text{display:block;line-height:normal}.ui-button-text-only .ui-button-text{padding:.4em 1em}.ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{padding:.4em;text-indent:-9999999px}.ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 1em .4em 2.1em}.ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 2.1em .4em 1em}.ui-button-text-icons .ui-button-text{padding-left:2.1em;padding-right:2.1em}input.ui-button{padding:.4em 1em}.ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{position:absolute;top:50%;margin-top:-8px}.ui-button-icon-only .ui-icon{left:50%;margin-left:-8px}.ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{left:.5em}.ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-buttonset{margin-right:7px}.ui-buttonset .ui-button{margin-left:0;margin-right:-.3em}input.ui-button::-moz-focus-inner,button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:49%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-dialog{overflow:hidden;position:absolute;top:0;left:0;padding:.2em;outline:0}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 0;white-space:nowrap;width:90%;overflow:hidden;text-overflow:ellipsis}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:20px;margin:-10px 0 0 0;padding:1px;height:20px}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin-top:.5em;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-se{width:12px;height:12px;right:-5px;bottom:-5px;background-position:16px 16px}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-menu{list-style:none;padding:2px;margin:0;display:block;outline:none}.ui-menu .ui-menu{margin-top:-3px;position:absolute}.ui-menu .ui-menu-item{margin:0;padding:0;width:100%;list-style-image:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)}.ui-menu .ui-menu-divider{margin:5px -2px 5px -2px;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-menu-item a{text-decoration:none;display:block;padding:2px .4em;line-height:1.5;min-height:0;font-weight:normal}.ui-menu .ui-menu-item a.ui-state-focus,.ui-menu .ui-menu-item a.ui-state-active{font-weight:normal;margin:-1px}.ui-menu .ui-state-disabled{font-weight:normal;margin:.4em 0 .2em;line-height:1.5}.ui-menu .ui-state-disabled a{cursor:default}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item a{position:relative;padding-left:2em}.ui-menu .ui-icon{position:absolute;top:.2em;left:.2em}.ui-menu .ui-menu-icon{position:static;float:right}.ui-progressbar{height:2em;text-align:left;overflow:hidden}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-progressbar .ui-progressbar-overlay{background:url("images/animated-overlay.gif");height:100%;filter:alpha(opacity=25);opacity:0.25}.ui-progressbar-indeterminate .ui-progressbar-value{background-image:none}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider.ui-state-disabled .ui-slider-handle,.ui-slider.ui-state-disabled .ui-slider-range{filter:inherit}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-spinner{position:relative;display:inline-block;overflow:hidden;padding:0;vertical-align:middle}.ui-spinner-input{border:none;background:none;color:inherit;padding:0;margin:.2em 0;vertical-align:middle;margin-left:.4em;margin-right:22px}.ui-spinner-button{width:16px;height:50%;font-size:.5em;padding:0;margin:0;text-align:center;position:absolute;cursor:default;display:block;overflow:hidden;right:0}.ui-spinner a.ui-spinner-button{border-top:none;border-bottom:none;border-right:none}.ui-spinner .ui-icon{position:absolute;margin-top:-8px;top:50%;left:0}.ui-spinner-up{top:0}.ui-spinner-down{bottom:0}.ui-spinner .ui-icon-triangle-1-s{background-position:-65px -16px}.ui-tabs{position:relative;padding:.2em}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:0;margin:1px .2em 0 0;border-bottom-width:0;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav .ui-tabs-anchor{float:left;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-active{margin-bottom:-1px;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor{cursor:text}.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:none}.ui-tooltip{padding:8px;position:absolute;z-index:9999;max-width:300px;-webkit-box-shadow:0 0 5px #aaa;box-shadow:0 0 5px #aaa}body .ui-tooltip{border-width:2px}.ui-widget{font-family:Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #ddd;background:#eee url("images/ui-bg_highlight-soft_100_eeeeee_1x100.png") 50% top repeat-x;color:#333}.ui-widget-content a{color:#333}.ui-widget-header{border:1px solid #e78f08;background:#f6a828 url("images/ui-bg_gloss-wave_35_f6a828_500x100.png") 50% 50% repeat-x;color:#fff;font-weight:bold}.ui-widget-header a{color:#fff}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #ccc;background:#f6f6f6 url("images/ui-bg_glass_100_f6f6f6_1x400.png") 50% 50% repeat-x;font-weight:bold;color:#1c94c4}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#1c94c4;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #fbcb09;background:#fdf5ce url("images/ui-bg_glass_100_fdf5ce_1x400.png") 50% 50% repeat-x;font-weight:bold;color:#c77405}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited{color:#c77405;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #fbd850;background:#fff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x;font-weight:bold;color:#eb8f00}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#eb8f00;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #fed22f;background:#ffe45c url("images/ui-bg_highlight-soft_75_ffe45c_1x100.png") 50% top repeat-x;color:#363636}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#b81900 url("images/ui-bg_diagonals-thick_18_b81900_40x40.png") 50% 50% repeat;color:#fff}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#fff}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#fff}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_222222_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_ffffff_256x240.png")}.ui-state-default .ui-icon{background-image:url("images/ui-icons_ef8c08_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url("images/ui-icons_ef8c08_256x240.png")}.ui-state-active .ui-icon{background-image:url("images/ui-icons_ef8c08_256x240.png")}.ui-state-highlight .ui-icon{background-image:url("images/ui-icons_228ef1_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_ffd27a_256x240.png")}.ui-icon-blank{background-position:16px 16px}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:4px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:4px}.ui-widget-overlay{background:#666 url("images/ui-bg_diagonals-thick_20_666666_40x40.png") 50% 50% repeat;opacity:.5;filter:Alpha(Opacity=50)}.ui-widget-shadow{margin:-5px 0 0 -5px;padding:5px;background:#000 url("images/ui-bg_flat_10_000000_40x100.png") 50% 50% repeat-x;opacity:.2;filter:Alpha(Opacity=20);border-radius:5px} \ No newline at end of file +.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{min-height:0}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin-top:2px;padding:.5em .5em .5em .7em;min-height:0}.ui-accordion .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-noicons{padding-left:.7em}.ui-accordion .ui-accordion-icons .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-header .ui-accordion-header-icon{position:absolute;left:.5em;top:50%;margin-top:-8px}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;overflow:auto}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-button{display:inline-block;position:relative;padding:0;line-height:normal;margin-right:.1em;cursor:pointer;vertical-align:middle;text-align:center;overflow:visible}.ui-button,.ui-button:link,.ui-button:visited,.ui-button:hover,.ui-button:active{text-decoration:none}.ui-button-icon-only{width:2.2em}button.ui-button-icon-only{width:2.4em}.ui-button-icons-only{width:3.4em}button.ui-button-icons-only{width:3.7em}.ui-button .ui-button-text{display:block;line-height:normal}.ui-button-text-only .ui-button-text{padding:.4em 1em}.ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{padding:.4em;text-indent:-9999999px}.ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 1em .4em 2.1em}.ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 2.1em .4em 1em}.ui-button-text-icons .ui-button-text{padding-left:2.1em;padding-right:2.1em}input.ui-button{padding:.4em 1em}.ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{position:absolute;top:50%;margin-top:-8px}.ui-button-icon-only .ui-icon{left:50%;margin-left:-8px}.ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{left:.5em}.ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-buttonset{margin-right:7px}.ui-buttonset .ui-button{margin-left:0;margin-right:-.3em}input.ui-button::-moz-focus-inner,button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:49%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-dialog{overflow:hidden;position:absolute;top:0;left:0;padding:.2em;outline:0}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 0;white-space:nowrap;width:90%;overflow:hidden;text-overflow:ellipsis}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:20px;margin:-10px 0 0 0;padding:1px;height:20px}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin-top:.5em;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-se{width:12px;height:12px;right:-5px;bottom:-5px;background-position:16px 16px}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-menu{list-style:none;padding:2px;margin:0;display:block;outline:none}.ui-menu .ui-menu{margin-top:-3px;position:absolute}.ui-menu .ui-menu-item{margin:0;padding:0;width:100%;list-style-image:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)}.ui-menu .ui-menu-divider{margin:5px -2px 5px -2px;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-menu-item a{text-decoration:none;display:block;padding:2px .4em;line-height:1.5;min-height:0;font-weight:normal}.ui-menu .ui-menu-item a.ui-state-focus,.ui-menu .ui-menu-item a.ui-state-active{font-weight:normal;margin:-1px}.ui-menu .ui-state-disabled{font-weight:normal;margin:.4em 0 .2em;line-height:1.5}.ui-menu .ui-state-disabled a{cursor:default}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item a{position:relative;padding-left:2em}.ui-menu .ui-icon{position:absolute;top:.2em;left:.2em}.ui-menu .ui-menu-icon{position:static;float:right}.ui-progressbar{height:2em;text-align:left;overflow:hidden}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-progressbar .ui-progressbar-overlay{background:url("images/animated-overlay.gif");height:100%;filter:alpha(opacity=25);opacity:0.25}.ui-progressbar-indeterminate .ui-progressbar-value{background-image:none}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider.ui-state-disabled .ui-slider-handle,.ui-slider.ui-state-disabled .ui-slider-range{filter:inherit}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-spinner{position:relative;display:inline-block;overflow:hidden;padding:0;vertical-align:middle}.ui-spinner-input{border:none;background:none;color:inherit;padding:0;margin:.2em 0;vertical-align:middle;margin-left:.4em;margin-right:22px}.ui-spinner-button{width:16px;height:50%;font-size:.5em;padding:0;margin:0;text-align:center;position:absolute;cursor:default;display:block;overflow:hidden;right:0}.ui-spinner a.ui-spinner-button{border-top:none;border-bottom:none;border-right:none}.ui-spinner .ui-icon{position:absolute;margin-top:-8px;top:50%;left:0}.ui-spinner-up{top:0}.ui-spinner-down{bottom:0}.ui-spinner .ui-icon-triangle-1-s{background-position:-65px -16px}.ui-tabs{position:relative;padding:.2em}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:0;margin:1px .2em 0 0;border-bottom-width:0;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav .ui-tabs-anchor{float:left;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-active{margin-bottom:-1px;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor{cursor:text}.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:none}.ui-tooltip{padding:8px;position:absolute;z-index:9999;max-width:300px;-webkit-box-shadow:0 0 5px #aaa;box-shadow:0 0 5px #aaa}body .ui-tooltip{border-width:2px}.ui-widget{font-family:Verdana,Arial,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Verdana,Arial,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #aaa;background:#fff;color:#222}.ui-widget-content a{color:#222}.ui-widget-header{border:1px solid #aaa;background:#ccc url("images/ui-bg_highlight-soft_75_cccccc_1x100.png") 50% 50% repeat-x;color:#222;font-weight:bold}.ui-widget-header a{color:#222}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #d3d3d3;background:#e6e6e6 url("images/ui-bg_glass_75_e6e6e6_1x400.png") 50% 50% repeat-x;font-weight:normal;color:#555}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#555;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #999;background:#dadada url("images/ui-bg_glass_75_dadada_1x400.png") 50% 50% repeat-x;font-weight:normal;color:#212121}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited{color:#212121;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #aaa;background:#fff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x;font-weight:normal;color:#212121}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#212121;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #fcefa1;background:#fbf9ee url("images/ui-bg_glass_55_fbf9ee_1x400.png") 50% 50% repeat-x;color:#363636}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#fef1ec url("images/ui-bg_glass_95_fef1ec_1x400.png") 50% 50% repeat-x;color:#cd0a0a}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#cd0a0a}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#cd0a0a}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_222222_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_222222_256x240.png")}.ui-state-default .ui-icon{background-image:url("images/ui-icons_888888_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url("images/ui-icons_454545_256x240.png")}.ui-state-active .ui-icon{background-image:url("images/ui-icons_454545_256x240.png")}.ui-state-highlight .ui-icon{background-image:url("images/ui-icons_2e83ff_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_cd0a0a_256x240.png")}.ui-icon-blank{background-position:16px 16px}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:4px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:4px}.ui-widget-overlay{background:#aaa;opacity:.3;filter:Alpha(Opacity=30)}.ui-widget-shadow{margin:-8px 0 0 -8px;padding:8px;background:#aaa;opacity:.3;filter:Alpha(Opacity=30);border-radius:8px} \ No newline at end of file From f1d91dfef88166df46298d1b4c48ccc89ae1b148 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Thu, 21 Jun 2018 08:21:27 +0200 Subject: [PATCH 197/746] interface adjustments for apikey interaction Signed-off-by: Michael Kaufmann --- api_keys.php | 33 ++++++++--------- lng/english.lng.php | 6 ++++ lng/german.lng.php | 4 +++ templates/Sparkle/api_keys/keys_key.tpl | 25 +++++++++++-- templates/Sparkle/api_keys/keys_list.tpl | 8 ++--- templates/Sparkle/assets/js/apikey.js | 45 ++++++++++++++++++++++++ templates/Sparkle/assets/js/domains.js | 15 -------- templates/Sparkle/assets/js/main.js | 16 +++++++++ templates/Sparkle/config.json | 2 +- 9 files changed, 115 insertions(+), 39 deletions(-) create mode 100644 templates/Sparkle/assets/js/apikey.js diff --git a/api_keys.php b/api_keys.php index c60480b1..0af7142c 100644 --- a/api_keys.php +++ b/api_keys.php @@ -27,7 +27,7 @@ $del_stmt = Database::prepare("DELETE FROM `" . TABLE_API_KEYS . "` WHERE id = : $success_message = ""; $id = isset($_GET['id']) ? (int) $_GET['id'] : 0; -// do the delete and then just show a success-message and the certificates list again +// do the delete and then just show a success-message and the apikeys list again if ($action == 'delete') { if ($id > 0) { $chk = (AREA == 'admin' && $userinfo['customers_see_all'] == '1') ? true : false; @@ -65,20 +65,21 @@ if ($action == 'delete') { `apikey` = :key, `secret` = :secret, `adminid` = :aid, `customerid` = :cid, `valid_until` = '-1', `allowed_from` = '' "); // customer generates for himself, admins will see a customer-select-box - if (AREA == 'customer') { - $key = hash('sha256', openssl_random_pseudo_bytes(64 * 64)); - $secret = hash('sha512', openssl_random_pseudo_bytes(64 * 64 * 4)); - Database::pexecute($ins_stmt, array( - 'key' => $key, - 'secret' => $secret, - 'aid' => $userinfo['adminid'], - 'cid' => $userinfo['customerid'] - )); - redirectTo($filename, array( - 'page' => $page, - 's' => $s - )); + if (AREA == 'admin') { + $cid = 0; } + elseif (AREA == 'customer') { + $cid = $userinfo['customerid']; + } + $key = hash('sha256', openssl_random_pseudo_bytes(64 * 64)); + $secret = hash('sha512', openssl_random_pseudo_bytes(64 * 64 * 4)); + Database::pexecute($ins_stmt, array( + 'key' => $key, + 'secret' => $secret, + 'aid' => $userinfo['adminid'], + 'cid' => $cid + )); + $success_message = $lng['apikeys']['apikey_added']; } $log->logAction(USR_ACTION, LOG_NOTICE, "viewed api::api_keys"); @@ -167,8 +168,8 @@ if (count($all_keys) == 0) { $row = htmlentities_array($key); // shorten keys - $row['apikey'] = substr($row['apikey'], 0, 20) . '...'; - $row['secret'] = substr($row['secret'], 0, 20) . '...'; + $row['_apikey'] = substr($row['apikey'], 0, 20) . '...'; + $row['_secret'] = substr($row['secret'], 0, 20) . '...'; // check whether the api key is not valid anymore $isValid = true; diff --git a/lng/english.lng.php b/lng/english.lng.php index eccc3693..a409f5da 100644 --- a/lng/english.lng.php +++ b/lng/english.lng.php @@ -2135,3 +2135,9 @@ $lng['menue']['main']['apikeys'] = 'API keys'; $lng['apikeys']['no_api_keys'] = 'No API keys found'; $lng['apikeys']['key_add'] = 'Add new key'; $lng['apikeys']['apikey_removed'] = 'The api key with the id #%s has been removed successfully'; +$lng['apikeys']['apikey_added'] = 'A new api key has been generated successfully'; +$lng['apikeys']['clicktoview'] = 'Click to view'; +$lng['apikeys']['allowed_from'] = 'Allowed from'; +$lng['apikeys']['allowed_from_help'] = 'Comma separated list of ip addresses. Default empty.'; +$lng['apikeys']['valid_until'] = 'Valid until'; +$lng['apikeys']['valid_until_help'] = 'Date until valid, format YYYY-MM-DD'; diff --git a/lng/german.lng.php b/lng/german.lng.php index 3e98de24..e45d5c0b 100644 --- a/lng/german.lng.php +++ b/lng/german.lng.php @@ -1785,3 +1785,7 @@ $lng['menue']['main']['apikeys'] = 'API Keys'; $lng['apikeys']['no_api_keys'] = 'Keine API Keys gefunden'; $lng['apikeys']['key_add'] = 'API Key hinzufügen'; $lng['apikeys']['apikey_removed'] = 'Der API Key mit der ID #%s wurde erfolgreich gelöscht.'; +$lng['apikeys']['allowed_from'] = 'Erlaube Zugriff von'; +$lng['apikeys']['allowed_from_help'] = 'Komma getrennte Liste von IPs. Standard ist leer.'; +$lng['apikeys']['valid_until'] = 'Gültig bis'; +$lng['apikeys']['valid_until_help'] = 'Datum Gültigkeitsende, Format JJJJ-MM-TT'; diff --git a/templates/Sparkle/api_keys/keys_key.tpl b/templates/Sparkle/api_keys/keys_key.tpl index 12eb43a2..263d0807 100644 --- a/templates/Sparkle/api_keys/keys_key.tpl +++ b/templates/Sparkle/api_keys/keys_key.tpl @@ -3,10 +3,10 @@ {$adminCustomerLink} - {$row['apikey']} + {$row['_apikey']} - {$row['secret']} + {$row['_secret']} {$row['allowed_from']} @@ -23,5 +23,24 @@ {$lng['panel']['delete']} + - + \ No newline at end of file diff --git a/templates/Sparkle/api_keys/keys_list.tpl b/templates/Sparkle/api_keys/keys_list.tpl index 11671ad7..43cdf15d 100644 --- a/templates/Sparkle/api_keys/keys_list.tpl +++ b/templates/Sparkle/api_keys/keys_list.tpl @@ -34,11 +34,11 @@ - - + + - - + + diff --git a/templates/Sparkle/assets/js/apikey.js b/templates/Sparkle/assets/js/apikey.js new file mode 100644 index 00000000..b6efb43a --- /dev/null +++ b/templates/Sparkle/assets/js/apikey.js @@ -0,0 +1,45 @@ +/** + * + */ +$(document).ready(function() { + + function editApikey(id) { + var sid = getUrlParameter('s'); + var page = getUrlParameter('page'); + + var apikey_id = $('#dialog-' + id + ' input[name="id"]').val(); + var allowed_from = $('#dialog-' + id + ' input[name="allowed_from"]').val(); + var valid_until = $('#dialog-' + id + ' input[name="valid_until"]').val(); + + $.ajax({ + url: "admin_index.php?s="+sid+"&page="+page+"&action=jqEditApiKey", + type: "POST", + data: { + id: apikey_id, allowed_from: allowed_from, valid_until: valid_until + }, + dataType: "json", + success: function(json) { + $('#dialog-' + id).dialog("close"); + location.reload(); + }, + error: function(a, b) { + console.log(a, b); + } + }); + } + + $("span[id|='apikey'], span[id|='secret']").click(function() { + var id = $(this).attr('data-id'); + $('#dialog-' + id).dialog({ + modal : true, + buttons : { + Ok : function() { + editApikey(id); + $(this).dialog("close"); + } + }, + width : 800 + }); + }); + +}); diff --git a/templates/Sparkle/assets/js/domains.js b/templates/Sparkle/assets/js/domains.js index b95e04de..4b43c0fb 100644 --- a/templates/Sparkle/assets/js/domains.js +++ b/templates/Sparkle/assets/js/domains.js @@ -1,20 +1,5 @@ $(document).ready(function() { - var getUrlParameter = function getUrlParameter(sParam) { - var sPageURL = decodeURIComponent(window.location.search.substring(1)), - sURLVariables = sPageURL.split('&'), - sParameterName, - i; - - for (i = 0; i < sURLVariables.length; i++) { - sParameterName = sURLVariables[i].split('='); - - if (sParameterName[0] === sParam) { - return sParameterName[1] === undefined ? true : sParameterName[1]; - } - } - }; - /** * disable unusable php-configuration by customer settings */ diff --git a/templates/Sparkle/assets/js/main.js b/templates/Sparkle/assets/js/main.js index 3ea7cc4f..71a0a035 100644 --- a/templates/Sparkle/assets/js/main.js +++ b/templates/Sparkle/assets/js/main.js @@ -5,6 +5,22 @@ function twoDigits(value) { return value; } $(document).ready(function() { + + var getUrlParameter = function getUrlParameter(sParam) { + var sPageURL = decodeURIComponent(window.location.search.substring(1)), + sURLVariables = sPageURL.split('&'), + sParameterName, + i; + + for (i = 0; i < sURLVariables.length; i++) { + sParameterName = sURLVariables[i].split('='); + + if (sParameterName[0] === sParam) { + return sParameterName[1] === undefined ? true : sParameterName[1]; + } + } + }; + // Scroll to top $(window).scroll(function() { if ($(this).scrollTop() > 100) { diff --git a/templates/Sparkle/config.json b/templates/Sparkle/config.json index 53bd1088..b6d2f779 100644 --- a/templates/Sparkle/config.json +++ b/templates/Sparkle/config.json @@ -1 +1 @@ -{"variants":{"default":{"css":["main.css"],"js":["main.js"],"description":"Default"},"froxlor":{"css":["main.css","froxlor.css"],"js":["main.js"],"description":"Froxlor"}},"author":"Roman Schmerold"} +{"variants":{"default":{"css":["main.css"],"js":["main.js", "apikey.js"],"description":"Default"},"froxlor":{"css":["main.css","froxlor.css"],"js":["main.js", "apikey.js"],"description":"Froxlor"}},"author":"Roman Schmerold"} From d4312fc481dfe580fd0f09aa86052c2f04f6530a Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Fri, 22 Jun 2018 10:05:04 +0200 Subject: [PATCH 198/746] update jquery/jquery-ui; fininshed api_key editing (needs a bit more validating); added PhpSettings-Unit-test Signed-off-by: Michael Kaufmann --- api_keys.php | 36 +++++++++++- .../ui-bg_diagonals-thick_18_b81900_40x40.png | Bin 369 -> 0 bytes .../ui-bg_diagonals-thick_20_666666_40x40.png | Bin 387 -> 0 bytes css/images/ui-bg_flat_10_000000_40x100.png | Bin 278 -> 0 bytes css/images/ui-bg_glass_100_f6f6f6_1x400.png | Bin 232 -> 0 bytes css/images/ui-bg_glass_100_fdf5ce_1x400.png | Bin 321 -> 0 bytes css/images/ui-bg_glass_55_fbf9ee_1x400.png | Bin 335 -> 0 bytes css/images/ui-bg_glass_65_ffffff_1x400.png | Bin 207 -> 0 bytes css/images/ui-bg_glass_75_dadada_1x400.png | Bin 262 -> 0 bytes css/images/ui-bg_glass_75_e6e6e6_1x400.png | Bin 262 -> 0 bytes css/images/ui-bg_glass_95_fef1ec_1x400.png | Bin 332 -> 0 bytes .../ui-bg_gloss-wave_35_f6a828_500x100.png | Bin 5227 -> 0 bytes .../ui-bg_highlight-soft_100_eeeeee_1x100.png | Bin 246 -> 0 bytes .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin 280 -> 0 bytes .../ui-bg_highlight-soft_75_ffe45c_1x100.png | Bin 287 -> 0 bytes css/images/ui-icons_222222_256x240.png | Bin 6922 -> 0 bytes css/images/ui-icons_228ef1_256x240.png | Bin 4406 -> 0 bytes css/images/ui-icons_444444_256x240.png | Bin 0 -> 6992 bytes css/images/ui-icons_454545_256x240.png | Bin 6992 -> 0 bytes css/images/ui-icons_555555_256x240.png | Bin 0 -> 6988 bytes ...56x240.png => ui-icons_777620_256x240.png} | Bin 4549 -> 4549 bytes css/images/ui-icons_777777_256x240.png | Bin 0 -> 6999 bytes css/images/ui-icons_888888_256x240.png | Bin 6999 -> 0 bytes ...56x240.png => ui-icons_cc0000_256x240.png} | Bin 4549 -> 4549 bytes css/images/ui-icons_ef8c08_256x240.png | Bin 4406 -> 0 bytes css/images/ui-icons_ffd27a_256x240.png | Bin 4406 -> 0 bytes css/images/ui-icons_ffffff_256x240.png | Bin 4848 -> 6299 bytes css/jquery-ui.min.css | 8 +-- js/jquery-ui.min.js | 15 +++-- js/jquery.min.js | 6 +- lib/init.php | 2 +- templates/Sparkle/api_keys/keys_key.tpl | 9 +-- templates/Sparkle/assets/js/apikey.js | 52 ++++++++++++++---- templates/Sparkle/assets/js/domains.js | 15 +++++ templates/Sparkle/assets/js/main.js | 15 ----- tests/PhpAndFpm/PhpSettingsTest.php | 29 ++++++++++ 36 files changed, 139 insertions(+), 48 deletions(-) delete mode 100644 css/images/ui-bg_diagonals-thick_18_b81900_40x40.png delete mode 100644 css/images/ui-bg_diagonals-thick_20_666666_40x40.png delete mode 100644 css/images/ui-bg_flat_10_000000_40x100.png delete mode 100644 css/images/ui-bg_glass_100_f6f6f6_1x400.png delete mode 100644 css/images/ui-bg_glass_100_fdf5ce_1x400.png delete mode 100644 css/images/ui-bg_glass_55_fbf9ee_1x400.png delete mode 100644 css/images/ui-bg_glass_65_ffffff_1x400.png delete mode 100644 css/images/ui-bg_glass_75_dadada_1x400.png delete mode 100644 css/images/ui-bg_glass_75_e6e6e6_1x400.png delete mode 100644 css/images/ui-bg_glass_95_fef1ec_1x400.png delete mode 100644 css/images/ui-bg_gloss-wave_35_f6a828_500x100.png delete mode 100644 css/images/ui-bg_highlight-soft_100_eeeeee_1x100.png delete mode 100644 css/images/ui-bg_highlight-soft_75_cccccc_1x100.png delete mode 100644 css/images/ui-bg_highlight-soft_75_ffe45c_1x100.png delete mode 100644 css/images/ui-icons_222222_256x240.png delete mode 100644 css/images/ui-icons_228ef1_256x240.png create mode 100644 css/images/ui-icons_444444_256x240.png delete mode 100644 css/images/ui-icons_454545_256x240.png create mode 100644 css/images/ui-icons_555555_256x240.png rename css/images/{ui-icons_2e83ff_256x240.png => ui-icons_777620_256x240.png} (90%) create mode 100644 css/images/ui-icons_777777_256x240.png delete mode 100644 css/images/ui-icons_888888_256x240.png rename css/images/{ui-icons_cd0a0a_256x240.png => ui-icons_cc0000_256x240.png} (88%) delete mode 100644 css/images/ui-icons_ef8c08_256x240.png delete mode 100644 css/images/ui-icons_ffd27a_256x240.png create mode 100644 tests/PhpAndFpm/PhpSettingsTest.php diff --git a/api_keys.php b/api_keys.php index 0af7142c..ec536571 100644 --- a/api_keys.php +++ b/api_keys.php @@ -80,6 +80,37 @@ if ($action == 'delete') { 'cid' => $cid )); $success_message = $lng['apikeys']['apikey_added']; +} elseif ($action == 'jqEditApiKey') { + $keyid = isset($_POST['id']) ? (int)$_POST['id'] : 0; + $allowed_from = isset($_POST['allowed_from']) ? $_POST['allowed_from'] : ""; + $valid_until = isset($_POST['valid_until']) ? (int)$_POST['valid_until'] : -1; + + // @todo validate allowed_from + + if ($valid_until <= 0 || !is_numeric($valid_until)) { + $valid_until = -1; + } + + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_API_KEYS . "` SET + `valid_until` = :vu, `allowed_from` = :af + WHERE `id` = :keyid AND `adminid` = :aid AND `customerid` = :cid + "); + if (AREA == 'admin') { + $cid = 0; + } + elseif (AREA == 'customer') { + $cid = $userinfo['customerid']; + } + Database::pexecute($upd_stmt, array( + 'keyid' => $keyid, + 'af' => $allowed_from, + 'vu' => $valid_until, + 'aid' => $userinfo['adminid'], + 'cid' => $cid + )); + echo json_encode(true); + exit; } $log->logAction(USR_ACTION, LOG_NOTICE, "viewed api::api_keys"); @@ -178,9 +209,10 @@ if (count($all_keys) == 0) { $isValid = false; } // format - $row['valid_until'] = date('d.m.Y H:i', $row['valid_until']); + $row['valid_until'] = date('Y-m-d', $row['valid_until']); } else { - $row['valid_until'] = "∞"; + // infinity + $row['valid_until'] = ""; } eval("\$apikeys.=\"" . getTemplate("api_keys/keys_key", true) . "\";"); } else { diff --git a/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png b/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png deleted file mode 100644 index cc1e486a5a8fa064acbc9d761c4f8bca99f9e532..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 369 zcmeAS@N?(olHy`uVBq!ia0vp^8Xzpd1SErbK34)MmUKs7M+SzC{oH>NS%G}U;vjb? zhIQv;UIIA^$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG z?e6j)3^*gc-vNqn7I;J!18EO1b~~AE2V|V^ba4#HxcBykAy>15fLr4BQ>-lsl52Eq zV*Dn(zVTG$VAKY&H#$Db7CV$Z4fp5&D*d_PX64JfTQ2_%lrY|Ztm*~#^E>mN9{-ni z?$7&sDUDxOs6|ZO#e8j9-huE1bq8a^L;ocUu2er*xbpR0zJiifkNL9$HXrc1!n{H5 zD$DAi@&mW7@c!Vs()`r1{}*$?5%!m!1$VY@mzJqLThL%?wn}HirB5?5qD*vWiTQRf mbG^JM^Wqjv?%IU)TUhseSI&*pJl_TM6N9I#pUXO@geCx&l9MC= diff --git a/css/images/ui-bg_diagonals-thick_20_666666_40x40.png b/css/images/ui-bg_diagonals-thick_20_666666_40x40.png deleted file mode 100644 index 7bc8a92833b6f725e11f2f259b505f0a9f4f2a59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 387 zcmeAS@N?(olHy`uVBq!ia0vp^8XznHBp80OT7LpkEa{HEjtmSN`?>!lvI6;x#X;^) z4C~IxyaaL-l0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR z+uh~=f05OOXMsG<0*}aIAngIhZYQ(tfQ%YX7sn8f<8QBS++H8mU)r~CX z4LsK!yRsFIhfa9Hb)rc6!cMu3cbH1@qjk(1T|Rd_O$zRK*6I6kwUXIUAiL(>^w~v> zvvgjZ`w{;28qgi8C9V-ADTyViR>?)FK#IZ0z|ch30Emo249%9nO2EggXnga2|x`Dp00i_>zopr0J+|PcmMzZ diff --git a/css/images/ui-bg_flat_10_000000_40x100.png b/css/images/ui-bg_flat_10_000000_40x100.png deleted file mode 100644 index c5d10e6570cbfc4ade4bcdc227937aa3e7ac452e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 278 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F2qYNp$opRhQY`6?zK#qG8~eHcB(ehejKx9j zP7LeL$-D$|6p}rHd>I(3)EF2VS{N990fib~Fff!FFfhDIU|_JC!N4G1FlSew4N!t9 z$=lt9f$?sa@Dd=8v%n*=7)X17vD?XPJ0OGK)5S4F<9u?01nc4kpkYiA42-2_ZRJ2I z)e_f;l9a@fRIB8oR3OD*WMF8bYXC$>A%BP diff --git a/css/images/ui-bg_glass_100_f6f6f6_1x400.png b/css/images/ui-bg_glass_100_f6f6f6_1x400.png deleted file mode 100644 index cc405328ce69191a6bfbfd580d49cf4629c14206..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 232 zcmeAS@N?(olHy`uVBq!ia0vp^j6gI&0LWmFTHNUZq*&4&eH|GXHuiJ>Nn`~nC=POW zVpw-h<|UA$kn9oU%fL{j#=y|f!octgDAe$RfuYoZf#FpG1B2BJ1_tqhIlBUFfD%ke z-tI2{|BI|PJPYJ;7I;J!18EO1b~~AE2V^*Vx;TbdoKF5x)7#mYDAKE>wOBrhCx4B^ zy*-IZHph-sG98;=xMv5p~S|>aIb}5OaIwM QE1+=zopr01W~|uK)l5 diff --git a/css/images/ui-bg_glass_100_fdf5ce_1x400.png b/css/images/ui-bg_glass_100_fdf5ce_1x400.png deleted file mode 100644 index c1662ceca30f394ac08947f50a4b799878bcad63..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 321 zcmeAS@N?(olHy`uVBq!ia0vp^j6gI&fCw!Bv|Lw@jjIrX5XnoFeIKG^1(3g3KNAWnVRY?v?y%eD~QVQhkC$0GU?JN{g~pt#URncV#Y_$k|XK-R<-i}h8L%X=5pRm`FK+?Cepa!-KTJ)wz#gJ zUm0|}kDpz-nc6a#?2AmP!?*K(O3p^r= zfwTu0yPeFo12TF&T^vI^j=w#x$i?I+((tf;UXnmgbH|3oY>pC!)f}(GR!16S-u+#{ ze6YEqRkW=8vGl=5qArKM<9}TC-}iEvB{zdaTcX5$wyRTK&ALYGq)eZD3$! kV9>=^7Y^7?@ibn_3wdYa19?85p?Km9In5kei>9nO2EggQ@a)A)p2ZPgg&e IbxsLQ0P3kSL;wH) diff --git a/css/images/ui-bg_glass_75_dadada_1x400.png b/css/images/ui-bg_glass_75_dadada_1x400.png deleted file mode 100644 index a2c6083f55700e107ec990d7155a4e85c6fc93c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 262 zcmeAS@N?(olHy`uVBq!ia0vp^j6gI&0LWmFTHNUZq?nSt-Ch3w7g=q17Rci)@Q5r1 z(jH*!b~4)z#PD=+46!(!T=8puqDZgOs>RXUCGx5b?-VBQkUm|IuXOmYJrBRJgj{Vx zMbNnqUkncy+qa2-mWYc>swkcIuvGK#>(0d)B7)5f`@$Ei28nH~0h*~=;u=wsl30>z zm0Xkxq!^403@vmG%ybQmLk!HVj7_Z!OtcLQtPBhqZ+a@AXvob^$xN%nt>Ht<$2mX^ N44$rjF6*2UngCW^PcZ-h diff --git a/css/images/ui-bg_glass_75_e6e6e6_1x400.png b/css/images/ui-bg_glass_75_e6e6e6_1x400.png deleted file mode 100644 index 5c738d545a82220d1cbf6bc45be9d2194806f696..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 262 zcmeAS@N?(olHy`uVBq!ia0vp^j6gI&0LWmFTHNUZq?nSt-Ch3w7g=q17Rci)@Q5r1 z(jH*!b~4)z#PD=+46!(!TrvH)L6@80)r*_cdCvDr%)6ghVL16=s@mbz7H!uRdGeDa z?kzLg)16i!f8fKx84s0>4afpGrm9eRnfr++(ft7(l<4sQm6b-rgDVb@NxHWue`8Wrt Ofx*+&&t;ucLK6U?tWQ4x diff --git a/css/images/ui-bg_glass_95_fef1ec_1x400.png b/css/images/ui-bg_glass_95_fef1ec_1x400.png deleted file mode 100644 index 1d828735a5b5a2d0e88aac81b90f0dbb5dff162c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 332 zcmeAS@N?(olHy`uVBq!ia0vp^j6gI&fCnc6a#?2AmP!?*K(O3p^r= zfwTu0yPeFo12VciT^vI^j=w#>k(V)1qW$CZ|6)SVV-&*#dav<$DMuV&n0Dbpw@a>=^7Y^7?@ibn_3x|Xd4(<85lI) h^i)96kei>9nO2Eg!-tlSbATEcJYD@<);T3K0RXc|bS3}* diff --git a/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png b/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png deleted file mode 100644 index 50e4fd032f3f1c4e18d1433c058bdb5b4ec93492..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5227 zcmb_gdsLEXw|APauWu@?rq6Dso@yFRGfPJ;6`4`f#FD(0to_#oSL)NS?ABQ*MgVl`MvvHd%w@#zu#V5 zKN;k2x^~A}6B85DfMdQPCMI7j8h@YOxXSoVb-8}lc=-qHhZ8@Tn9#86mQSrVuD{3# z@%J{V>)RldV79YYO%> z0{=`1^!GKa*jG{N*T$W%emWMNVZ0t&`S^7qyi80sKMnBxA?$)s_8M6L=?w3DA8_+> zSSV!m?r@NW-Qa^~0n(vci8kp{;n}Y{vz`)MbBkPmzt;bcU1utHpS1k`KbIrCze!yU z?_YKQ_p|Tn*dm5Nw6P|bcarF6o0=bn3p-|h{kM$|9$r32SXi}dDeVC)GrNQ144alq zHQMPae|2NFytm95mds|homLH}az1?P`_WQ{zyH_9O-4KNc+c*SwkCUJ-%Nh(qdk9O z`4Rk#p8LuF|3Hf_d91j8LMPwcTJhcadd2lK`su;{3w2XH(ajUsi*jn*n;Ut6EU-p~ zzcV06bZGYjwAk={h`N?DlWc%v0{`%X*mu3HnR$Rik@$W=zKfcigLq*I=*!vE6AK0H zBqQpXIkkTZ8yI$Wh=WM;!9T|F3@UXUm`r}ZNM6kr&N7;$1tsx&@F)nJH=EgI{RP-l z;qpLJoTKL2Bw59S|1g1u&x zc7Uey-+89Q07n6 z_WlYxkNQW+^9M6e%boEw+oHkFS}Xu?Kr(l7I#Ar0DT|6MwmL*y4WM>2$un5kAPDfN z?<}Nm@@Q;VR<1*d@DfP22gOhehFZeQ+~A1Oy5M5m@t4{}N+Eu2jTFJh-6ufE9%K?| zhIg?Ag^Miplngd)@x(bhmR=T`J7I6H6zt*qrxx1#ZRA4i%F?0duj2n)z;}*3Hca)i z{oPkuAZ7UAlC&Sc$9d)6p@vV@E_n7t_nyeIYy>x8j&m@YeN)3Nsvc(O!wre8NllFN zJua=5-$vA&nWYf-9L$64%e@v)Q)L}iHPBDmMJSyH1~!8;AC@Zc$7_QFKre2Il4Xo& zY9F?702hBLk$fLl-FY=!XO)KIH9hZdt?)G;~Sy zHHQyE1c1R6I1tB;DlZHz!)=PLrQyx_EWvC5 zyFf|y2j^4S(5O8Gfp-l(4#HTxHwF(o2QWLA()I^&%}O`XKehkk17>n8UMn0=-1-jO zryCu2=$Re`=I-T}i}dgE*jL4@&X{A^W0*vDGros%Ry=V6`hL;D5k*tmE&Z5kalRdCO2$F2(@6aH{w%|=9MqX-k!31`n#hzyH==DaGmB6$B(R#r|a3fJ3Z7E zSG`zI7Zck&a9a|<(ZJ5u7wAB8ew1Ng{sdOI;}qDsL|u)(@7wP#(7cYWda&pZ-6p;^ zmGs!_l17RaM)I$Ba6a4yaiSroN228u#bf){2oAd8Fk_-)fXFk+`?rWcYzBq*i( zP(WQ7+5b-zNuLFdh?+PnBeiZJ3niV(!aXpVo6IBd%;X%5T;+4s zbe&W3*scEZJXoPshp<2iYP>DLfOlTu#`u@YahY36kT{kW#xMSoUl5-B(%qk!knm!z zA>H1^9mD5AdKi1eH8gxoNhEj?7vgHbv1WLt2!iq$DtU}k4w88g;pZS}w$Av-=Kk$+ z>envQPm3)W>Pp6_ZUFF}b#j92loYjh-%2gf4t?Umx|^4@uQDp+arc(xjoDUBr)=^ADf~I>>xyYhv6~ZvhW;BRnL(Lz$Fp$`5LG@aT zc!bkW9_@6kAuPySOviZEpoSV1X_}kwv zcGFz}n}V>lepCCx4|Pa=WN_&)&NETX5)VU{wZ|Zqea-jh1v$L$36@;<2K?}{NAR%X z0m(8KC$74+?csih73SIxzq`yI#xyC;wahNR8}8iT<#eRw!=x;-j^@E_d4~_QnRn0V zyjWVHXc!=fFiVp!NZBoD^)cp_id!9Re7{NZ}Rc=UEKEVhI^GBA7rKo zOx)}S>l?9so+t^e3kMTrzWIu-dm=eO!eourMl>7l8PtGcIdB>}~E4_z6s?k_0Iq-8Rz@aSxIk4osrLmemS)Sh9rvEAEYvW8uyh9yzW+6Xqd!42rAUT0hnYJ)F7NVdR_&IyIQKRs~$gdcDwZ zhpVR+w`m$>y`^cn>A1D84MV@xO%p6QLp#SL_?Av};D?Cgtqs_UK+h!z+`ZPy@ zLcsu>haO=hsj-V57**-~1@rW8w44ao z3!Z*VPgG!Lmw><^DtQW1@$c9QgOefDjqPn|v7^a3V{AkTA0#$*$wybqqhob|oy>!| zdsV4YHgR`^>q27RghrOrqkA!I;{QZ(=}?ySrumMEg7JEiTt{60cgz<8urz2kU5C~{?!IY+XUx@ z{^chX*s>WKu(z;`t66KuSUX3l&P0cpjxN8>XvTYgtmF1OrlVbrj?$*TlqJ^nymQ8e zdEw>1wA-S|IQlk^=X}!Ym$_%t=1u+o;*BI9wIBoedP%qL11kfV-G-};D{J*?@jvU2 zX;u!mWJmA$&ub{t22Ybz)!$-4CKaN$Vh%6@q(bqZ3Ofb}U*UP*wdOE_u*Ylo9dX(C zlvn7Hm>1hsn8W=Ijjy~t@P`XH2zwX*=vVmR#Yp<%2w|5!;6|qz9JYA zkKEPm#wtXJ$pPQ}1p?%;}B#YbA0=3FSvDE4Iwr;{PKctg)XMy-?2(?t!+CMS~K zHTG_ELs5v%_&A~ZXE`?0Ej|I)jD|MGB;9VbsNa(pD*U{Cf?7u5?zH*e6x%ngZ zF~YpFDp@T4?-GcoBVR9U{0y4CXla%1Y{UKj>u$&j6%Melp7F^&r9)1+(|xO$1Z ziwLIzVf|IANB>bjln!$<@V&JLe%_8wtB5$1x|dW3sDGQHUaDE_h?+W#BCSSzSF$-D$O1UOt+|Nj zgJh%1tc`|Sbf(9-21Y#yE^<=5aVb6%efC8w3q&Bz7mOk;(ITBDw9SL!Fw3sgM{ov& z<;rMD=2SdrJwP`&6THM6oO?NzP)QBpqGFy`NJ92diikPLec?c50na&_n%qVznWLyV zVU-1iPc;V&cJ|?t5)Vqocyv1IO397Xn)Nl2Uv-kZ^uZ-@e6Jjx@KZB;-{ zG6@sZ5~bb`-eY?ut+N*lRX7K0W0jJKtFq(ShjOtDo9<9?_S7@@;R6VRqC--)jvd)X zN|4#Ue}B5%Dcd;t-!D%CR*M>}4jFE|g9SAK#fK-SuH(_&vE(C!z9?{tBwP>@e)M`O z#I%M*irRDXA!rbS=zhG6R7WnfbV~{TT%lvLf?td7mI|V<`hil|SfmQdwS^PTS;C$R zHpm5*hH>6l)D&MV(antG##x81I!M%=nvIfFse||xaSYcAtp1vI-kOZWGVY}Tks{n- z`|P>k5Mfwy=yodJ9Pi0RyCUmwMuLn{Ey(7`IWEAkLoR7Sf_Gpe5gheFA-8a zX9A8Ge6nTlk@32NpBv288u}PjJKTD`1qPUAv%-)9_#nc9t+XhMqE?swuRsMTeeVFwXl9_+6kGgMy zFuL5`pc4!b1|qMRad?Cq>|l(YiVTaRZHnd;vY!oXe%8V-X}oq6a-HtAWp5haOFNeI z;(MJei5gWE|IlobaxMKWIns(~iNKW5q6Fm(phnpOqlCXIFVmM-bG;!Y{jylKKSHI+ zJ|O8uC4Vy?%hlzW`*iK(!WXDTA+`G*J3XtO_nc0Gb$JjcpEVBV*$v)*$)5P&Q$biE zm5+2fg5@9;MN@$~SUfkB22%+4kNhf{Ud+_7Y5!VJ)s75ynoGciQM|jAEsu%lKW5-H zZ3a9^vnYngIA-gM~b=)|{G-#gaQaiKL@-B_9=4%0|D#6vUjyih>Ft9@Ks|aie z9T+p4#}@XvhoX*?(*s#4mDx#gw0}L=Q`1h*s>XG@8`dhZO%Tl76s!;zVu$WgQNmn$ zEE3td55kyQ*b}HsIUI~aR2H?YJqb^}Gn&)WH-FcKsj^c^FLl$Es#Q~`XQ_{JwBDi= xyWGQuBfi*(zjU}@oFTyHOh2ssB#{Q|fqZzJ%3hmd{9Df?;AoINn{1`8HkKv5aIvl{>{H(Np_kp}l}CqhT%0UhzQs}1a|PYn hXM30ZUH`V7z3{5)w)U+I6M=>^c)I$ztaD0e0sv+>P3Qms diff --git a/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png deleted file mode 100644 index 4d4a7e9280a7021f5d3a6c5004637a588f481f24..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 280 zcmeAS@N?(olHy`uVBq!ia0vp^j6j?s03;ZUuHXC*q?nSt-Ch3w7g=q17Rci)@Q5r1 z(jH*!b~4)z$cXZEaSV~ToLo`U+vu0Ue0cG9p8hWqa?gxxGLm=1A1u)Cewe3oSeCaf zI$k30UHXoTXA5lSJe(zTcE%W-S*bfB&J`pw9sa4-R?IGW?p~6`>jMSP&M+u3 zY@9al)zrvpHlQu4C9V-ADTyViR>?)FK#IZ0z|cb1z)aV`IK;r*%GlJ(z(m`?z{NsW*1m6Iq|G@bT8aQwD$j|9|&`M^9k=hx1MoVjkXiRfu~dZz`bwtlz4m=|e)( z#`>#@MG21Q@2?j8m7vA_zlk^duQvC?#-|F~n%A%fzW&$Qz{3Cky>#V9Cm~jbXHV@r UrBB+00bRi0>FVdQ&MBb@0M;*QUjP6A diff --git a/css/images/ui-icons_222222_256x240.png b/css/images/ui-icons_222222_256x240.png deleted file mode 100644 index e9c8e16ac5e7f61c843fbac290ce30c5de7e40b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6922 zcmZ`;WmH_vmTkHN1c$~6PLSY|#sb0J-5r7l4-l+z2m}f4?tFL%PU9Z51HqaEx5g#t z@MPA!_h#lt-E(W*b5`Bjwd&M9yQ0-q|9pEC2TIxHV+jYr=H%}!7RG)%@|?KBm>pyGpu&ojtW_wyym4g`c9 zco$2c8`p{vTVxgdal_?TR2q}t~bOfL?S*Jz>-hj zej%U-ol64Z=H=<4vts*B%Lz$=z-A~15GXb3wI}HLd?o-|{AomRiY`p@EGlXSgLObn zHiI+BXw_n(p}o|pYMok=31u8OMO?~bm-n`CS;fRowPbbSwTgu0=*7l(0v$^yp&hkM zDNj1Y0bXyV)+53{VIwdGW4Fo5{;9<>33wL?Z3XX8uv#Z=U!_NVfTGUbDr4v}Tu zR`?wX3hn^0$(t{SX1-GeGS~e&#-4Kh2MVt#b5SU8CjmH-w$O;aCD6t?ZrfdZEbk@Y zJqZfG&3O7WH7q-{({FD9v;hC5@$-r}DY+5dbrzbDrH+tT&pfKJUur|7-n>I<<=u4E zqp>W4q^FJ|mzJ;N(7*VMG zU2BTU!JR%!p+}0CBhHUm_PbWLt8EWDyGtR zVU)%zz+8FYJEe|QI~>NS9o>Rn3~1g%$0eQ`!lR@ZF2O92__?mx1rtva8{14&Kj1L0 zDaZ(xRC21rv8=fJO|)hhkn=UJGcknv`SnzJbkjtkmm+>?J%~|f+$2Sbx8vOAIgY2t zqQT*KpE$b>z=kxqpl_!>-2K;g2dm}3JV23iDpVU^-or__aU{~34xQEW(|mEprL&b- zAQSAsLrN#rJ|@c*=_coAKi$0zq;K9IiTC{?&;K{{PJd1H8|KNh0y%+oVJU9m3t9~>S^EF8ZniI_zTt3H+C~_J}%``4jjf5yho+NMLCrcAJN*P z^^L0M2Nk|oV{v_&cv{>_czfr@d)!?(MX2RGno!%Jx$h65%CqQ#A0d5;| zSSLyd*lk78{Jmt_-J$L6#h)e1B{JA5+7@mTA2cKQHMTmEgVqts${HgsFO~C=B_Q*( zHl%u4XCA+FdC1CvH?&IhdHSiEW#v4eIhhZEQ+hV>M>Dkvof`t}fb!@dj0LrbRqUg5 z8nXf_+F;+Ir4&w^l=4m~sy#TR$Q8xfPB%72n=*iaN9EKWeXj`CkLbf69eW>JCVDPZ z7L|9kyXOYG(A0Zx(eZiSb@JFFdT6i-rdyVPce<|Y|A~7p%E!+Vm5pq)E_mjl3TB&{ z7xkGp$iSk&UEtWes|0wb9iDlJf)%nsDc$a1kP0__Pvgop<&Te#x#)w#k-3S#)*eW* znUIBK40-siM_X970Y(XHD$9MH=f zxxGx(wa4Z;q^W3V0>lRag@tvPcuViWb!iG;t2 z5-KFMfg<4J#MU$lBUwKHsdM*=Xc&102XwKxclrG1%i=4}FJahwrX;H zNwp>$UiNsQ3MaH`j_r!Cf!2nGp?oQYWGDyw*oRPD)Jji};0ay%lW)Y}e_~utKE<_L zAW#===qs>H+NFwJaOrgI)RmNr*QaH#W&q37EwXhR=bE;rxBx~wP_0#}8%G*VMyw0t zOXoQ8i8jP(H)=AY#xJECZlA{!*{lM`Wp((OZTS&YORwOY5MEdRRM@+^ANnn3WENTU zA3OQOy5)T|{ zY;G!nWz5+Wt(#<-Sr@5cgAv1gCuY^U)Ic+>!QAozYINV2tObk6EFYy2AmNW0n)r&g zKC+K(98CT!SCiRNi%Qcy2?^*e&zkhXK-*gD+1)epREUD@_8y6#qo9GdvS=?m7pBQ` zkL)xGZw}JHFBf3X*09PMs%@LyD-6@bK$YKo{x5a39#&fGyXO3w6XU>6jJg zQxb0!eyd)r8P}79d{lXm@XwyGF;M~{Y|HRvIVJl6MGu643K(3krt#!mUF&&tM z-{qM2sSo2s1@j!}h>Z+rA6Pc(peiB|1BSb{g@P{1VOv-kb2MJ5|L!vR}v>GSI@0<7h7Ey`Iq`4cv)8v39ONQF*(#v*Db zNB_~;W^ZHpEvX>TpNcjEwqgc~S;?zigBbTgVqHJ$%9~EWjd99l7Ya*mEn>GbEF!ic zTiJw8JQJ^ti0A(>+*#ha6svdftjY|(XtMW!CU}#pC^AEGg0!z^e{u6KNctmD_L=|1h_F85eW|d& z+iZ@#_$CIQXazAg$AB$+7?7Q|`Sca-nC$sw5+|$(DDuQc{)a!^hpV0Kx)WHV*$GjO z|LEJ?QV~#Qf$SxPaJFBkim9;NXMLaE_V9$4CY5uDt)dIV@{AVCq{`geO@^4SpUoRc zPVkO7ol5!RI8EDt^|cnZYF*_zKo{IflvWA_^?@N?-+@@l++5RRzEjGcx9-;pT`$Bw zYqm3fyCB@{Bh2RL_l6IB3$|M?yE=cV3>a6u#0604l9gb^&P;UI&K6>^A3rei z_AsGQ4Ph7qJMYk+^0a=4`12DAaYopombf^7U%29p-_H&Hu0FqO+@n`^e)LD62&Z84ykM?K4rpE-FOj2zS#H`pWk3AGV5%vZY0F1 z&VL&c0mopw@;XD4Qf8}=H2Iu{Vc9X=d9#KP5u$xsT}<}l7uBv2d>vWdXQF`>_6zd>Qkqko0@L$$)M-(3;=I!1JeN+rI>;xQSPDrF{CFaxPI^sok@u+J#M za|fi2jTJ73_5xC67LB)FzWAh)!m=%c+%oP!)FAN02CIm?G2@F7h~Gn>I;UiEEu;C< z);%{mKeh{tOdolre6l`Ubu6F4{op|tGN=Ad<=3cywRTiuWg{O(T@R}k`(MIXtG*44-UH&+4)(f(HhC1 zXsTq@i1&P?`-bqFAKa=oz$VRWlUTm*f{!U8!!N~$vB$ElIJZ0-MPoSq2y8Z>$Os?- zGitN8Lb*rCC$GeItn-ey9Ac6UJ+857Sgm5CLje5}kRry*T%TnPML@s<9Yg&9Jc>75 z9_UG@JJPA!V$7TfAcPr|@Es-aqeJt6yVLvzFUY(eR}|GGERaSJoA{bPCayxFF9gc6 z^N|sr=T+@yO}@iNU&Th$wC-VJBDztJZk!3#RCDb&k;!k4YXrXpK0LoRkco;%sq4p9 zqSP9OCLHKHxQVL(9&zrHL3fl~$FAr4$$+J%-=#(cT0I+BR?h`OnYD2FF^**(Xzz2s z#U!p1!yAQ8q zF_PD>c=N0{AM_3a7t9mUp^?$L6f#bY6OSkSHJZ6=c};=~Evb&aDUPk24+w1dPxB>; zHCbF&kbo=5)dNhQ1*Cr@omSHHWAO^|I5C1T@)P0Udp&wAPqepw9%h%6p1ZG#IOnP# z{uXSe?kNT%HP|H3^^N89$WH*`M;j3TK;=ta4?i2?JNb2U@R(s7rOV3j1>qa@j@KcH zJlr;{VG$cF0|Rt*46bb~aV|-q z73E4Wdq=&cryrwo%JgSG+kUsHH$wBn&on#@&z-V!vo&}R{c2AiEHqO?WQ-+-$LQ;| zSzsy5l3p8|_9NdxMK)PB!T>0P7k z&0CLgF@ySTJ=Bov9-*8aasg4`HBXV7GNBwSFWbfYpSUZwDVpY=r`DKG;QAq+)te2G z3jx&_i{~qs7fS+B*mZzdUd0prA#xjI_$&supo1tyP>sGKT!YLB=Cpj>xaiZ2&;OCd z7@~3TTVdi#^s^JD;Do?PT*OL`OohekdMverH^CeOJu2iQJPN++A9PQ@_?i+-l31q7 zsGebe^Uz2N$&&Im!nq>i#lZi3quVjmk&ofh?(5xyFQC{RbF-nue6Q`*?X*_#){G{@ zK6VgEs(n5yGLa=RVXuM-b3K?mqz0qy^q&O@{4h|_%MO1OeV8r4a$+ILh@P7F>Sjy* z`NPx!G`aDXuVsN(6|i63h#SzB>Hv^01!&-CYDW(j*2dAsem{P{o%?R3<|we8Z3{o$ zD#M;c1M7HJ1P6B8TCQRtdbsTW{Digg;=Tbh>ZLgI{R((4_UG(Sikptwn^Z`kcz&IY z0+NRJ^MlZY_n%$*l{qZ6#K6QWkMt{#=`}~c-spzOmr&=#tG8Vx`41nE*dJ5BnDXwx zM?U;=@oq53vM7qIZp5zz{&;QGv8O^S^(hKv?&=`g5=s3wuV{6Ql*moEnrz!8Ni>)P z>1uAq3&nbGE6lGHHl(l1UYr>`$O|3n3Y$mc{};v-(f4;jf;K*GAnTFZ17Hz43@FgV ziYF@6)04!;wB=B(dn0QU9tZzoF)5My#Ad3M^JND`IGi!5bS+h+B2$1^BL@Pt`IAbo zD)j5t9t+h&e7A5eJdOfMt4c+KJ8IXSSBX?Fe}VfE;+FW({Smz{KLO_Vg}SY0Xr^yF z1yh>@=f`l(!w&jrxu=|7G0z1wFqE5uJoW2WH~)%B^K257mHJg-(EIY$j=GMdObJV1 zRn|;yWG0Gk2CwO6bswvia-uxcDePIVR%08pf&{wJ-AYdv*)72*>^j5x#3UaM-E{il zk~&2g5nB`XZu_S3SN{e)AjE5wOs_bC_`Ncj;;$v=fvJIad(>fnQ|6AI{V&`SZ2_}CjpF}SlN#1n&An*VSa%egX%sCk_;{f35__sJ zOE^+UR`2JCbE!`(`mV4sOQK%d_;w9jI>M$c5^Y3wN@(kv{fh7uACHiAg-*_{UV3Ox zxp}8j@X_c)@`B5g4*{rYr=jQ7nnbQeic%Bgn-hWX^W$9FWS`dwVo76vy%ts0>Zbh2UvGm^{MpxM@{%>Y143I($OJfe zEv*E>MJ(+Tv`bAP4K~lx5gs3mEVZ?7tZYrBH;-V9J5Bx-)`^MLk$l_PRpp6?^F_wo zlV%W&v?Ydr?*)k05&BBKTxMXpTaA6|Qmo%`o%)m6vov2j7OvRi9Z-kplQ`yu@^3=f zFd6Ivjkw9pp-AQo&E};^*;RXBXsMp`DT#SIUOwl_*1H*_!HuuEZ?> z5!DSZS)x?A%vwFV!O1iQO%o;fK9I(% z$+BLxXIs^+|8ROTUlMBHcFokLkLgvLEWN5zy-40Pk>a#Pdr7^9HIuYjas)=?c8yXs zJKg~ZJkTHb(x7#`Et7pBoDc1#3##xyM z3}}lJ#$VdHnRhOTxUsU!Ziv*)`uE55lnXMS3zQr&NjQNO(;olsf)1uNe71-LxfUTn z>)M(OJ_&>rg74}GVOS@^kx`pSzJb@RY4ShyR zgpl6Y%~N}Ed?s`=A9k)Ga-YfSiz8Z%{}{sCRT*RI%FPz!@xcRbf2MJM9o#p_NqdNim~b;E?;dRhDlGrPjCy_vl2 z_70gh4;SVyo?cOaCAh=6SOzQ}en{jzFI_0%XEfx8j zNWFIdL-nht-6wHKB=T~D9QGsl%>mhNv>zIk6IJ=yW)5w2`To@u?IeJ~`~XHE8+$%1 z6-iC^E)-)noXcFG{Fo*BHxaic9kEceQ2597`9X1NMmjE_^I4Ed+t8GRH6Y85>O-3x z>hnmv8;jPzLWJ)VXN+#P0)|6EGy1ZcLFp{NzV|oH)>N!1@Uy-{QrJ#+3_B zTly^uFUV;yC^X#J^A>qTXUr%*tpBT)vbR?ZQiy~%TFl%B;!F_^kLIfY`61qpq>rN( zGq1E~?$>2+Qq4f;m`^g zi@XO$y03_pVr*+*)_~0g%)f9}BCi~w)cNz^d^6ql=&8w|k8@#HcvG$iCMHlhwd2Rn z%KB1{`F!7>?v5BeW%NC*Ej(?6t=w&%41kxL2g1S4$HBv+>9qA|%Yi%f`(u%+1YR zVPExc0nRSg_V0ZE{{V^eY0{?v^1qFso#H| Q^5g_ikWrPcku(qaFX2Wbe*gdg diff --git a/css/images/ui-icons_228ef1_256x240.png b/css/images/ui-icons_228ef1_256x240.png deleted file mode 100644 index fe17e2f0701489344ab4c340e1ceb1c816995fe2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4406 zcmeHK`8(8K^ncIBM3zZJ!f0&Sw$%T8=iKKx_dd@#=bk0i%EFk7<0JH0JGWpP3tK>l zR}Bic>WoxGP|D4gO&bF7) zRn4XMDxTFeo?(K>))H$RY+y)+N>QJ?;3Lz5df(}MV-K|dgB#Lz!p4X1fZWZD4S^Jx zh{>UFB*4Tu=#c)yn*bLEz4ZWqhy9|VzFqjYUza&OCRz||gC<@JPqMr#%$_>=b)Lmm+Rie*MssDZ?#en4n!a(58AXl4b93tImrwyu*5Af9*T%)6E6aRa3w;s zM8?Eg^5e48a)`JCs9J$)m>K*z#;O3izL6Q6{gj3R%I=bc>o)kAx_ye#kG{N{UHtj3_0EPYqB-Ypy{jW&9oPaD zyiyV_S~wP8BXm!LLWrMg1?8>6!ugk8o=CZGW%wWZ`gMA^_VrT{HJ~smUj`P?p;)@4 zvkOA78%uxLxSt{F6CcD8%4gi&Cy9KS;R$an62yxc+s7ia6i~9MKZ6m1;KVFop*B7t z1U_(i)-(4vv2TcQV;8?U699HRFPfQHNo%>&tJ!rT<$p0=P~P0G*8Ta<)#n^f3NsuF z&7h{kt1PKP-nNsw8BdlPJe8jhkMv|;?~ZE)9@`x-eI3^ko+ZL35_scEFqA;zeEjv> zlnUhd>d%gDAw`1}V7-3=31)n=*zqjnPz+3Xh~({ubuqrCj}x#|4$o%Z{Rhl63t_KJPs4q7lQKwoA=lUOWH5h_cDz+B{ zDSRIjUb9V>3qu7ZmD7wzZu4{U;gyJ+2*vE9v~)7lJhA@$aHK+t9mv5b-kx%0h>vi; z&uzn9mR5lhfOy!u394;@z*&@V%&b3zLap5+JwSh*&T(1_Xq$#PwdW<&i1C;pYcv0I z1h2yB;pWg@)_tLB#FopS1P8ud@0XG86Tj{kqnCw_?CQSJK*{*ja>#QeC`E_t`R@OW zTbP{?;Vv}x%Hz`9g+wTZ)1BoYcva-?K3C#TCeZTxs1{D)@sk*S$_LokqPKJbx8`N2 z1TUOiGcraEo>);or|{~j(BCHd?q>>LprDhMI_lFt+$kwm!gyXvRo}N>?Wt!KJy4WZ z@&3NT;;O8l%8@mJBFU4ls}=+oQ!HkMdEmx%g+`dd-|Ppw)>}dU`KpCu@$A}wydV2+ zSyh{6ln}G^rYuG4J+`Y({hTno;>O}H$q6rzd1vY3MRB7Z-t;w_XYzn@97}<{-u}5g z9``bRq?9G01vyD^3?U{(#+xTYA}&vcA^={Q`!-U|eG&M3p65IaqU3K@It50{QwA&@ zee9s;<%xBU&lahbbgNilT4ZNPFv&}9#uso>@au@s_gBLEZro%CQLnjEb8OxC(8%z( zV0z~u*FDz6g4kgmKs2}xJR)Z*kPjRzXB`|%mxo?)?Y%rD2G!AQyZpj5*D^LgpH6R+ z3?#Zux}U%hRxaMMJm*Q0S(T?)JhP8y7A>F%b&L-GwswOZzok#uUc{%?Gb%ahxiwuz z5to;)aCeD**ZolzryZeIu2|ur*)-K~hwcsNjnm&9lA!c9YhF~_p-*h;X?E9r+BQhYfujM%VJ z$efWd@Qhf}_i$q?Rf^Wz)rI2)uTs0b3JGNF}y`DL~ z-cKU*=K%=3kM(UQ@YMSE@6e{ylLDC{Tl*WywcyefDO$;^Sxi>%6_Oa1#0ysa!og}} z;G-k+iK3{(nsBt4B?^8z#H@s>y}0u68tAKj>d{lG&oo+iN-Tcd9^w4{hZ$53djd_Y z1T5;lQLutg&#jnuJVYLB3?*v{Z%+~zNf-_ECfSl9RQ@K7Ji=rzY?Kczecj9bV76F`(!JiWMNIb}xvR@?8f>zXMx5f?aCT zIo*(44YEg#_O-6WD9J7w*-})WBqM6Agzj7PLUnZaz85ma|KP%xo)C|}wJvvI$weum zV-4h_OhlCC4Wx><8d;_~!Ms++VP|Sie~Hnl(b?K_H=Q-Qfaw>!zNaa7S_Wi7iU|!1 z*jdBLe}@z#6p7z9B$J$B0;&RJollL8%O2Z@o)MlE0!d_kR~rGE_kQ5fELP zM3r_&K2G3LtQygB@M$vv_x6YX?)>v4!1=)TBR#3epcrw@HEI`$-h}MdYd-Y#C;lEp z)uPwfjGCXBN$046!#HLdkx2}#_6VaifV_+OCs!bZF=9NIiqZ~J7BtTuU;JX?_(xjyQMi{-)<%&y2W%?8liVw92cby{Y4Q7)~Q>rc1=Y7-Lu zV;9L}XR_p8VXDTMc_KBP4)=@9GmpM&)6^R!jiHaTDlUI=#$CaCLNz6{nyU?9$2ymE zK8L*XtS^>kuY!uWop}?^R)_b6$~bcVRT!+)T>-iQeo5b+AM)@1G`~1>X6Zrb*d_2l z7D9%-csM@U&dz|pV!K)GtY`u}y!OY^Iv3$?$O%x~pRFHu4%|%^?@e#_C*+i2N^926 zmUla(sbzL>=(ZpeNVpFPMMn$3mO>wo_<(xFuhfw~{*>+f0ySY8P3wX^g>E(6+5ghl z&~e^lkKh@x5j6ggI5tox`LnmrlG4D~bc!AwET8|reB2pGyFx~z!}XF!e#F<=EwU}6 zh^~~dQby8J9sfImWZ71*xnPNyr4%0)sMs7?v{Q7-EI##!oco;X$wzw_C7o6k6Inb;P!%sFgLCskAFjM^e7QgR)yYKf^-Vo30U`hV#DA-e0cH^r(&@J5;N}` zPkl9_M4`Irv15hT8Qk9$2N8SzdoCxeTTXITFfMw@?I;z`XuNxLV0Z0E`vznn@d=Oo_%G{$H=#D_YvGptW zOEccI4EsgPee9P!m{a7!XgzH{(m4X{3_6e1Ko<=^613%E06ABy_qG~#_8h%|CDP7q zpSj4Xu(v$NzsX#nNa5&lh|aaH^5@SQwcrM94@ZoODtx0oACHrg4=p{^6{N9v~T0l-&|?`{wM83aMWyeSbG9i zFy=#cdr_;u@M@Fs$}8crE8Hv89Za~d}9lq!Oyyi$c zBjWDl2BrY8$St9=uiSq%>Uuf zY}PUgpGegwh*P#Z(y=}JuJU~ZP>}T^&vF%b>6feTB^^5>q+vm;>5)968OEmR!91L{ z50OuInSztFh0o9$JFJ^EvKXw+2c?T=(TdKap!VsW#KZ`DqN8Vu_)PBbvwnXf^_2#fsF(cn1gT3)tqV zrrZEEaJ5-LS0a@DnI{j?;3qNmh7VpFpIFVrjwLc<3u&OIZ4!AZT jE)BU0YbhxTZw^3m9Aj?j1)HvivoUbd$ilD|i;w>w0<$sw diff --git a/css/images/ui-icons_444444_256x240.png b/css/images/ui-icons_444444_256x240.png new file mode 100644 index 0000000000000000000000000000000000000000..19f664d970194372c3228494e34ac01d611a4d45 GIT binary patch literal 6992 zcmZvhcTiK`*7uVTieMljO?vMobcpmK9qA}Nbd@ehhkzixNhgR%Z_+_J2uKSZY0^I{Q28>^X z-Kd=7A$Mm$)*32G0Hp<~qSm|BJvY_oukWntzn>?AuVerKYG*Yic>|vx`yc$B>{J5# zWgY4?X69Mj-=z&u7@jg^$r$V}%@ODo4tWucu&Y|RDwlE_)+P_|D?p&7ddl2vpA@cR zSaFs2pV+o5gSen;OOf60WSq#pD^%B66c*~F;Oo^Grk=KJGvKkUog^G*Njg|;X8Q+L zVKcEq{#fZw-`k*Ll;kn$T zR?3|P!T#O|%RiIEP{5+J!S7S>E&J&eC;)Y!`%}T}a~BCRK~$SEpCC5q#|Y~NYNfrE zc;&8?BZ6%LU4Y*w0LQC>#9plngzxt#A|P;~OAHvov;p^`g@d=@a(dqi7`C7apL~^+ zw!x|E9POIvoqX78`+0Ny#%Xs3rYV3u?iQgwLp=$D?r(OAEdA11aV^Ef3=e5(VOs+A zDsYy6QYndjoJj+AP9Jur^?H5;4%3(~ayTU)>cj<8oNlAoRc{T$rNfrGL4cWo%rz(($6n3OuelM00Y6V7L0-3jrdy;k7{ZTnCG5fU)T?X*izfb?#hq?b4+*FlYCds|v zw^>jlD(RWKEJgi$8XyB(6M~Zdu4Vp zjz`5Ze;(9Qp8lYvC2n(PpDU}#p>eqXyIs=ffW?OHt;^5YuR%;DhX2UlC1bcl2_YCugibe70$6C{yYXhk`KC#Ib-!N_V*A#WK5 z!4uqj(UlQxE>YI^raVNWgw&lx73ehWEje49z#t)#s6npqwt2QNAS8r6I1SD6Y0k9E zOtB6So;GK-+pBl}jj5~QqGbtw`h?IgcU3XJVMQ!@hD$!9k2u9Z(A$t-?QAlgNo0KF zg+qg|H%?6oFy#aI9dUa<#gH<*{Exdh29LNDMfK~w%QOYdOFq4R-&rR&-4`-!kgdTf zI#L1JCw0>9DY9NNsQ-*V@8O-PWE5`RyYE>Fwi7|3b}Gct5ZVUK&LJu)@(RI{GRX;VEiM!DWZYGK}S z)=Zu0A@bL+oS4?Lmyq$t1{!E1k1rIk=B$wl_lJcx=!oMJuZ)sK5TY^#dEr)ae*a^P zvcJM%g*S7q26{v^tmDdsFBfgY?A`Vz&tR_tPYhy!b@W?9RtAWTfBVp~PtZ7;S-YT3 z?SId6d(OORLE|?j(ZX4fvsP?i!Wkbej42uAc&9gH|F&k-ijJ)3PPUzL#stf6GGqmZ zKjihG)EKl@K$pYU1Q*SEH1NNI1mG^(M@@NW-qyvuwlC7<|(F zwC}qu*jV^$N^qjdLmbF-ipQIPr690HhC|8%iPcMhFUY2>quH&9J8KeJZohI`XdPk> z&=Q>+l}w!D{}4)}xwoWU)$Y#2wgK*H-(0AQg^`cGv5B;qR-f%rzH=NPPRCDs^{R|T zZC;81h1a$Cz$e%e8F_CvPK^aW7^W2F4PBcM`PT>d&ziNcNyO|C4q)-CzuH!LlwK%l z`FnTjc6x@`)%XwWSS*rQg$`Q(_TU$&nbU&P!lv_ouW=tA9(4I#O^;fc2k7tMC^V9C zQlRa)zBm$(#0wa;UN7t~Rt_>Fo-)O{-a>e5yf7A1YZK#9I~G&YA%4WU3!YYu#&(K| zsaiz3V>Pa*$x9f4d&3`Gww$sf!wpy2d8w2Eu$&lK+80$7N`c)ooH=OX{81TQ?r8US zFL_-)%P&x`fPfQVCLnUAo?FPNu5Ui>vzJQ0+&JS)2vcyG!buTjxkq9{|M=EDcY*;; zCT?7MuD_10>C_8-*8u)LqgHu}migjxv6wnBO^V(U2b`-Q&60 ztv=%V!#;6Go@!wQcK3~^?E>FDXaTkn+X7)#dMM-8WQ(P|aEz#} za>B2fHgg2t20T!x2{V0BQjih{2bHPVL?SgYQk-SFK8Qn(*02!Wtaq6(e1INvxr z8TuT6ql4jNd}Y0qBejsK$$sIZz7LX@Cjl$`E(;lG`>6VcVJ#_d*c9ojpE@#_IpW=~ zt?uO;cc1nno0~Vo1gy!D3PHo{hjUDuOHA!^x`Nw-VeA%uksuh*2uP3)|F7$XiVc{d zZcJTt@%~1hxc~N@^Ntx#5@5Iw@@2?rBSi*LbFu*SF#5r#lNP04QM@vnT2uTos$bq> zll{&72D^oEbXQ8lNx#jGyB57)3m z!{$z!MEP?~iX;-jF(MEVBIH=_XXR%1(>mQy{&XF~mY;2$*D_xz3et!{LUR? zPwV+|M|&_@X2cIA^n0Pt(F;2n{2w^p2RuTGj&LtVE6B}c$Vaeg0sAIhTXBB~CFJ+R zlWd|0Ov5TPBrAmZqPjfeUHW4bw)03aKV60T9$0ZxfXyWlCi3pG#?w_jb$OA+Tx_7~ zN5RnxuwG_MypFL-_smnbD_V#zzn1kFDTj- zx0(VRm~n%cUP~1dBt^<4T|Y2s9<$zdfu+@tBsAmvrFOQvf~YF1nul&b)7PTJs|aF2 z++qdUW01Q6M&E9%aYCQ#DhF=bv5gb~Ngv4d1g$SlXdh|+k~x+bCi&?q&RwF?q=3@O z9U))V%fR`?S86B?m?LlJ>t#3y@2-oy)}RG;fD-So{;mp9KHcQ;fy}x=Y|Qcv-y6Rc zHO48UZ*)S)Y97$O9zM*rf4oVyFf4?A^Ue2$s+0DXh9|`}^vi=IZs#AgjK7@w_zI_} zF%0B4o)WQ;M(9UlshF@EUf1;)z;HFigR;&EigF42xMOKWRTN%^70r4_=V!+ouBb}V z5X<2}42pAy%{lK;QU%BI5IX@D7gY~=9KFN&cPWeMyFc6_dY5u!Oz#v0mdhTbNYzfd z4jv_%tYRMKo{+-NFlxi3e!^)JJa}07%{L{^2C8kF4@mKMSMm2gVwkz_QuG)ik z69Q=qb!9WFTbMbOAnsMm!6oj9QduW~{MQ?cB6> ziWQ4JTFv|W&n0G`D~t$_1so{`K%6G*sGk|{jaY8P`{3>{8A_d^?3wgHTlMY$MZ9U`Nv@l z7(tm~o5lW~EW9l1x6h3T#iS3nin;OAcNaRpwKffN-9)X7|CMlj9iE6x4o~ffC8pXE zIHZn{s4U+hmJADmnNy%DJzOepE&{<&i{DWOMF8{ce*kUAR)mGB$!?t?7NpCyo zC3=m|=@928j^QY@k6q#${I%J5mgnv&)|VsQ@pY8xUlr#PN4|FF0`#jiAq)LIa`nWM z4m&n4vzMYvqeOpPVrb}c00CgCgou8M$MdwS74=yiqxA^W2;#?kQ@2^!JxfHH0J;l{;nF@f)IP@D=!-)Erp^b#=%H?@X+e=aS#bi#q(6^eoqx!< z9^4*uDQ)h$4p6_i=K3uVCOM=F@9N>L>pG9rSf^zS^C8aFTRmlBWTaVBV7oSVa_3pW zd)4r9?H(9%x_Yj-iEO(q9KcD(W^%etUp*~qdT4ZK63b}kIe;J=wlju2vlFDIUK}` zD3Q&tfL2jmpL=(t(}P7$s|F{IsO9#)oj(z0A)JUtbzTK!l&t%ma<~HT8Mp#}md0dR z(+wy0+S=Yy+HCKP<0#-<8nPKkPDU%q{lPn+=Rahk$EP^w(LVMj^~$mPosagAaVY}! z@jU>M;pwDg%Ck%X5ifKcjxu z8_&4z+t!CYW&_X{qhCcsNpOhHn<_D@$%nfMDxDJ{ZCStXVSv%hTW2^Tw96F#Xbs{q z_vCEFesgku_@3gQtO$pjuE-p#@)C9p)&sSTw5wWzku8o7Jos;dv|G#9zRuy*m_S2T ztW$-v*Y?{!U=Ml1Gjw+YcyHL2J!#j-ux!65T$p~fXLI+8w^j{c?FX<9oRiK>_C&q| zCk4#guU6H>p8pgGu`?;1ugi#x;TgQy^ZV$KDP}A&SY)lsHsqVxDnVKGWp}nG;ZW#X zYB$UF+3b0#HJ@i@@$*k3sSo3o93#D5@Tvd5|2eV-oP#eVFRCse}zYX9D)A^U9pVet>L>xV%@{H<3hv#BS= z&4AcqvFnCOQ3-kRcGCP3{P%%6aJrTr1*cbr3#L61sa)eP*S-i2vL;^{A2Ch=m|mN$ z(h}7apw8;HYAJP}kZiedLf!SC8maH#92w8jo6F_K7N(A*S$FUE`_v>}vILMJMd7*M z<<_J6%?t6yHyMN|uuatZMbH;W{$BQGW5V=ZR%$CkiE>X2-6RHZAj~NJgWVK|<#eU+ zgp3T+VJLjMm9>IvJXz;XGz8q?SM~4Ax?azTxH7p+zT#~lX&cB7|I6@`d2ms_y)0O@ z8H9~y+1^YD`Dh&WmlMDUBcAYN9pbMx%m;GVJI0V~6UpukY$`_1KG-kp3`z(JM~&Q=>iIBjxH zM4R3?Anzv9$$tX_5XZT_*OgE^GxFU6Me$f_gty$2%rvj|QPq#lFMkclnFp?g^$jJR z`i9XRF{*P6+mWWeMtSUe9}(Gd^TAaIk0{oIl}~%v(Tn&}N+MI7))90!3F7XlN8sD~ zZ(sWw%+P2vwAbOSeaR@^!d*5GBu!HY4HA_MorgltJ0mUL|4@8!X&Si#;;$=pL`Gh( z{Ni68{j9XWtfaJODO?boY1e^!^*;0(@X;nN;`vYA7G9(Rkz9ej;T*KFhE^b3HYbK; z*5P(+L!n-eF6nAiagjZLSt$P?R%eDu*AB(E0ft{* zhp!Bte}C_}yhS=~!yumnu7nYpakjnfW;otnO!hw?&LzU?GUVY&(4E~p*WOfE%B+<& z{d8HcM^y2UdXcq*_ElnhgV`z6Kf&v3R>y2d&?TNOAPwo>$3?#@ksn*`2gk)z`K
    `A6pHt6IR6lNF6^&(ovlbP|R$VC3 z#Kj`X&Lyxos$n5C#2uMfZZsvbj)i z+$1(n=6q63)^IOf+^1x0R%Jn?&*qGX`{G5;8i!QR3oXJfw~DcSI#S?P@6TZI#^)nO zwBfhXH&dU8oVeLDqb6r-R>&#mCM;g!tPBL&yUn8|aGk2Z;+<@plmQtZfs}+*Cs>)w zJK)hUcu#Axe^!n8SWqhu2KsCjRL7fdv|8+ohg@hb&_b}-xyXe1kWcrO41<^<<7&ZQ zz`{1CCxUin(^K(w@eEMT(^}Rul1#tbP)zHUXQs;T&4gaG6=ckgoPc(7n z*nP2UyM#N~U!%DWT-%~=m$RLWZ{BbaHA2Pw2Jkcvd9BQ+`pcKWLIsiFyNfBO1oInH zC%Gdn^(k@+F)a~?>K~};Kc#WB z^{nk2D~t~niv&TrKOd+v`FXMR=DN+SgIjGD%g1Ye+s=6>ipoV8mP1RD?mg8&DMU=q zO^zcP7!_*&@^Np)=&5r*;QialnPQcsta!aRkF70#VabT)k?5u-;YzptvmB<#WuY3R zCHt}suyr7}=u<>SA!(YcV$8U;juwU8^~d)`Oo#4yDH(yMUn7ajXIk654oRQH1d5R5 zWQl6N4<$AfH86-?|I)&>?_Yvz75C_;;V8U9u;+dh-hiJfqm(Gh|2>&!^|uw8Sf!N& z9WrD?bX<1ttD2$v^8DeVAp2*9cB(6Un`IlxfzXmt;l3tj(F!-4ZXJajoUOha-mse| zA!`x7DG7-qhC}*iY;B^j1mbc$3RLMO^k4L=lXULiXz#zsf{Fz|)4^5x2{aQ>Z*5#E z?vdX&8)WO?ucP>9E?pIzt1RwC{5rPs{h+hGPYV7P+>A3xJe?O)$@6T{rX>PzP+?q> z;;#q;(=T~?W`h>R_;Ks-_~}fi2}AdrFMFIdE1o0}W}xGQU9x@`PQY7H{;8Qo?e<{~ zpKwEX7LjJY8@J`OP0iy!ifw(LLS^?8cYRZLaU*tLUX+^zUp#OYztQ*IJJVtGpa=vt z5kUQuI#p2*!|A_c$k4oQv63yk2dbsn%h^HtD>s48SH&Fx^p!EU%dtP`gZW_eQ~lD% z=1H@F2j8g~?=|<`-+VUSqGzqu`pWji0Wn_*0&TTAnpzFDp}&mJKU@JgOf_ zb;2;7x~$&YR>MwuN4Y+=+#p^M4MJWZpFdmo<}A=FxP1T!{_NcOYnv;lm?Pr55-@Rp z_Zw(#Yp@9w*S~`|Bbq3EEti?agR{4d!p#l31aBX|JVE(dSOq@?uUcKRg4>v`Ea^WP zvScvPt5m&LU<@#dVu1HHWDd&M<}MA}MO{nFS4ALwSEZ&SkQdw*mL1lF(*{jkf#MMB zDV>%pS4?g14ZS1Mk<_u3q$@5+yn5KrVLW`$+I=o&gd}Nxq`=Yx|=X%|V^yMSA#&uGDocw=YDx0(zS#)KM1H zcoDN9KqHH7E90e0OzF(xVRN5$N2dQJSx(Pi?9tyoYdK*!kX=m5UqNr4#V%tnAXyf7 zh@#7GW^T>=a7yJ|->AUNYkTaF&Z3y^_JZa&v|Chg2WGnNI7g+Br+G{N_$LYQcblb3(QrR$@sbar5n*kBIjSafWl~O) z!}!-exeo$ZFhbe1GXl>SNQJ|K(@!Nl$wETs7nUKenZX|bynIKP@Sc+xXhW`-WP{Q# znGJRZ{n8~pVjUN|nJx6cPp1T|E}cOX`x_uF310qRZ&nkGYt=14@CQ^2_-^Syl*w#c zhTX`qiW%(KoCFMjV2^Gg)Q!t@HNpPl-)M0MK-J{85Q@g-{rMHerkBXXsBTNiu<+oQ zN_*2Wi0&X+{H=SoHscgP)2!BpnBWcAJ{dRSwjilve-qFi$_rqJz%pyu&v}Z&W{I9g zD~K=^RFMj1UI#(9fbZ0Bd`?LKmDIU1dz2C>Hn(By*(zLjIogqGttE5VO@Opqm8e|j zn|*e5+1qX1hqVBw{@|Vnx#PR}5cHP1IXLDTH7(IIU^kG^z0C}zV3r|Qg==OHDzt3N(HOX{{aG= BEO!6^ literal 0 HcmV?d00001 diff --git a/css/images/ui-icons_454545_256x240.png b/css/images/ui-icons_454545_256x240.png deleted file mode 100644 index d6169e8bf9389ab9b5b7d2c6f0c5fe3e4d363105..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6992 zcmZ{Jbx<76vhMDpfgr(y1`QHmaR~%lf_s1jw?%>l3$g?j_W;3NgS%T`aR}}Z9Kzy3 zmf&*v-FxcQJNKRU$4vKBbyv@PGu>13O_;i>93BoO4gdhaQ;>&f0sz2A6>yA={@BZA zI!itZkeQMk1Rz`XI+62n^yHKL2bV`F|KIKHpzRv~fYM$8BK6MW*I@?C4l4JhU&5+Z zX?B6>M{wc*nf5t->YI0m%k!c6iV6D-HiA+N=Q{Bvn#`}m4j85ZvFzIFY|%J>xxbN+ z)KhhOMWAack{rDyA-k^9P{-{Y^-4&T)}WQ|)zSX7U*Jm-kJzQ#7H}DD8x^^@Hd^Id zO}JQr`JnFakU}mtj z1A#B^`J~>9Aj!aCs_5V1v5fm^rO5!bpr=#5Ec4gVZ+s}W-{jz#54|=utEYqV2QDVqeWLoK&SDCg)CL8cuq(~CMMZgT%6AWTd@*7 z6&71eU>T)Xe>ATJzEE*l7!d_4EfiRtlm6_$KqoujO=FRN&<+w0vNs>&-_Ed+(5C5$ zmkYkr=0{kY`cMqEqv<( z_g<1p@{QK8lz=aO1*Gv*be8bxc48H*~?C5sKsk6Y+G94lgYv>T~L|y)zUs?t_{Tkesms}ypzUDGt zRKP9jp1&y^@&RZ)xNxKyc}wp*1n`O+;#zYQ0#N>+ zwL0RCF;cJTN#50JeIN3zGCFJL?|Pv`sHdd9Okxo&0#6;1k;Hzh-x1*puL6C4Rf06- z@a?%qB!?Rcm-Kxu^;0S$aw3oi+ReC&%oRq{3G>G&kxB}sam-E4#4k>x`2Edch79Z`l~i|Vub!Rci8uT%U5Qm- zqzrUOWT)C)V7C0Ot{iK@**yiK6J*$P=vs^p<%g&3l?e^uQP(SX{G^~DEz@uDlSwWC z6HFn2>d!B-S;p7EJ46TDw;VlxIsE?PQTPYF)AjM54|{7S7g#H5O?B`H**ZP3#Rcm* z14Vjb`0wAD5v>;=@y1`jQyS87ZX^RYr;e045q~!y3PGK@B^ND$Oe^By*Pn{6o@PbJ z!lX~i+!?Z!hDHPe+ix8>vxm$X+}l3qzB?$x6vTUXWbL&MukaQe@6BjA#BLZ(saaH| zggtTHoi}V+RQiLAF|y}jtq~lUu*VwWLl%v`4A%T<8BjfHLPOI12-{xii)iECHzfHd zr62uYRj@yB%Cen7i_&0M+83`9EAR~5s_^5&9d+V~;i7mZj`xA~1xnBw*ODz`UG%eN zwZ3T!bX~sAc;6U3Vl=#&c<0aF%RcBv$yV`;1STH=o*?s*_U2Co?5UH4rIxGb-_86C z0V)FXqarc$Tp9jU$_LA;l|P&~n77aoKeiVuBf+GjpUp$fXB6kUWgiU(@RP6-ZEZ^! z6&A#>(=ZWB&pdpM;h|6VqZAmie8G@Fcf$1v{(lni-_NX(c?|LZXW$5{s?(yKDNjFt#Y{0GhcICF7V&#Q=1gJ<&oth1v|juBqBGovPk-dcNT(hWqc zWJ6H4Mr-_$uiQGVcZ-Kh6@v_>YV?tg4|uPt-H=98>l5Q)dqz{@eqQ+5i>@XOx=Lw0Xjqh0_w6?9r}i zHz^HH<3>U^Z|^f;3Ltd0j-A)8wr?S-+)b`ua{R?dJbK>}>9YdzQs=FH5)}5q~n^@mpnbkDf>;vFH7gTfo1GuAqIgTs<^LXm9fV(}{jL}_UeSnA>mB(5 z*^HS7)v)lv+8RF85S{DN^}DAJ3$UL@K!=hf7KRfi<8`F6N!2;flN(lV=4S+jIOp$ z&wiGBqp2e~U)Y*0XNoN*D6?F9C?0~OqzKTZ|CIO*v}MVErdyBA9oC0?Y9)*eri{4v zt17yAMm?p@V*cb#Hvw)krZA;q@IpgTXX8_PoUdYZBVTm!*+lbrHvpo=gZ{7Ug@PHF ztf)&_aQ*FG3cvp$*nZCdEfz4`hu8SiZY!R){{6K*wg|@|8S79F+w9cQ{?B=>`ku`f zj?Tob2hPNf?6M|5^{zL0{x-1?h%b{7uO!`AjnZ#vx{8VYmf}L=)Coi^a4gtedw<1E z&98NFQ%awC#S$?PlDy6A7gUAtEOwf6Sub2*l}b`7VqbPLO7zKEBi+3)buf$LF-a0! z-IDu7roL^8V&MvTlFnupTDE2HrkpfNtkC`xk!nfmAWQ$kb~wBHTLHj)w{aO{pHet( zo-t4j3FMTX{Z2ibk_a&om22FD=D2D+L7fOa-^2!!;`&#lgUhpn)<0_`P zPAvs$6X-Wo(Xn{=i6A|sYBOBI?JZNFts7##k;ah^QKUbGI$vXZW(eBj6Q1I4CQjPm zp`vkoMXCER^+U(lGG(QG6C00(0K-*e&q_Kb$V!DRqfj(5@@bgKCR7{k9p%>dY+)5d zepzE*kZG)Y%=Wp@W6NHyjIv-KVWidJy$;`Fyyp-ZL@k|n6d z-ud<^O;$1tvrmX&CF`^y6LQen#4l1DbFF=xV*Yko(y@L=4Q9N#dHKuS8Wr8@783TREimPeayuWEgh(Glh^D46x(av1+! z=ktQ1Q76vNEP&P`)WfsgTSyflWj4;eDSyt^%R~t!*1NQk333xEZ3xBQBShRxrz252+a?C9G|#oO^9xDr1KUz&nUjD8&f#!VBPS`fm^ zw>P4kEeImD>VXgHnOPp0i||U+e(-EYGW5Y+WvPV0E7E7u3QmP97?d z?zh65fjF@N^Hj`Kvw#meID+CQJB936N&AZ(oty?pl82s38*l2mW@&lEV3(rtTdb#N zx*h>C)Vh;#NMk^vQiF-Kq2kCP!@}(Q%!oys4KG^=Y|om ze(PAL;ga^D@4{2ToH*SqLxjs(rN!;|qAg9SuDPfDHc#+fUE+%1a5gSHvD!d&2l8zOK;zr_$Z+%6C|;Y@#iL$H0Y z^@`<)>wP)#c`2-;>1#!*76jk2r7JjI><+gry3kWX4ds)$71awl@jgwo~@Ilplz0QPW( zX;U;YX`3iK@B7RgQG8K};*vsN(XM*PT7$M&OpOuawA{WXNSjI)J;UI*F=e@+eBbl+ z#fE2F=g|oUATxJ}Dx5GHh${qqC~3}@K6K;x%gA^W$d!B^xH zNejV0tgIQ+@vt}JZ7~Xq2H=W z0$=)iMqAv`u~Ja7#=bYxJ@bky^*YHB{cjxy=HKG!>^(xQl>}3JV2B9yOS!*jkZBg-Z^~jGb zq+rflL_Oy^Hqtv_Rqc98JA`0C+2!&UvDh9yxzT2s%R4FjMRLdJGtAX`n=F)Yw%r7X zEEK$}pA-<568b@uSA_NLi@GRHOSiP0t@fgRw{Qa6_{Vj3@E}v%t?ucIDFD5T!5TGg zb$;4K?M@B3##@3N2UbESO+uxFV9}S!?~|GvCC3)0PQ{t_pZ0q+$J}gsQ@{m8v!^9D z!uk!rV~%gr@sb@iQEC+s+Q7MbSeoZ>lJ=P>O>l%uU6C}CNWihy3-Mnp`e=;jtEFco zB*=DczVn^bRSez9S|{9}z&#^*SV!vJMrO#Z-p$l4e?39lFmcc&-8#eIlGKk9U-@Rx zQ8?r7Hi2K3cHku#z%g9i#S?^&J^1xI+3RVN<5h&32x9DnAsP}bi&rJX;n1QjO4{IU z`gFZveK0;i1m*<#?j>t#+r)}_(Hi+!+L>AyUSO;yV4LgWBE;FP} zXB?1wA8H5NLIQ-)9-bfwYGy~Kjnc?oi}k-MbtTcyt$AMgYrFCC9Vx@W9iNuAh+W_C zP@Jl$^SqK8o~%R!cDy5xdyma=sL{7yGB!8*p{pRS+G1;X=$-Ml z(aNAbWRU>^IWXqS_f3Je18@EP?*LiQsQ&kxPg-8VrEw+mEw$$f8>*?f(Y2Bjw97)DTN2-B>D7QwR#`K8n?=j6Q6}{j2P>-q6?B=Q=7lc zRHYqg4)nXZ6kN5^UsR-*@-liMF(NixSw-tOn1(cV(O0q}g|R}DzFZN?hWIExV60HT z+OQV`iRJJ1q9SXlBeWwPwyba%SfrT&xkBAO2e1mjBRe67vIEi*{p&gOwGA-*@h)g} z&gz%j5(cDCVB-J*MPMxpo{Kga48P!aylFLNP{jHQ<&!B{)6ha^m0axgnrIV zz9w!NG363dJEd*R*Ls6LrSSDOWDHIi{^t zEE3l>G=eji-}6FFEho;T-3N+*lWM%swipv`flWB_LN+e6?0S#2 zYv<{a>K~iF&*C?S70EItez?Omk$QevNDjc#AhSIV6`Ko+zNfTE@GX?=F)dZETbYxe z*A}X=Z~pxMmCc8OCcyi4YuY z4X9)<>d1HDCn!(%kK7oGN5RgUXWyzvWMO;64A20#9;hV zjA!3^9W|tk<25zIOgbNAbI>DRQSNPz`Xm{u+<-idJD<&ZK$@9KFIcD*Poz(6uQfI? zBtt?r`uM(A4J?4fTR>9e#ua`GAyOvU3!17$j5Q2JnP{%>>sPkQBa*;+2YETi(`BaL zNL-6ba&k3EI%ZUCR%JK=w!;*a&a%|L8arxN+ocAjxOxHevxqvcl zyWdPk5!#m6&TM7Rz}@VeGS?0wx|~@D0g@^iJbk3ara@H1!9=Ijl0xTJ^k+giPbG%) zyaVkjG&5Vc*USApUEd2-%_}d5{KJG@CLsPFn(q{3A`08*@gG3XCT&&x5lbb|e$M!M zv754_za7E=;A|;&Nj7&)PdhHaTsN(1uq9+Y-VKxfyQ3mnx| z?5!Q$`)8Wq1TzHZzD}lM+pU6}x}tQq()Mmwu5S5KZL4}!#&6)m*^@N}$phJY276T! zFZDF7SG5QpJw0)3pph-X(h;_ zUU*UTZEidO=VXj^vvkj@wB!B#>4{K&fKAIKOUrv40bNCgBX*s`r@xSjf-ZzGy5r}& z3P+RfVUB9XTlk9tKJaV!hxZ%qtofSx5A*=v@Ae(PciD{cUxrLWfH8;ry`cG>!KSpR z{yofDff(^S$&^$Mw1eF=jBKA9^j*})vouc)6JIs-HIq9g^jpYfhnRHfTd<7SS0m;i z$;&b#3D;0tzPK+4$<~x1q8pgM5djc|@GKz!{?%@4_wFSrDgLB&?LgU6kmGu_It&fF z6CVdmFpYnvgHzqTF-h6FOp_-!9+2*x8pD1^MZ-wLY{=#N-s3tSQaw@Hpr+YT#d{|k zcDHAs#6Sd(&;$}0y!!_rUr~F6+V;*E&TgK^bs4)So_HDILoRQ7iBH3I#1=P|#8F$x zE1H=2j#Lh2rxq}U#J5j4 zG@Dw^$S=tpC%^jM3D6PG*OLEh`;MukDe)cg?EV*d_upAZPZ_YptwyKXB8M?Bi>N+Z ztl)JuZ0R4*5k{XjU#<`c@)8#+%J2^fYy-?wG!0e6W;R)kclJqUa37(Y%IbH7V0r>6 z&=}GBDflKC323|mlZBL%xx9fl!8p<1Sn+}8q?)njX<9{dzV@Ks8bqf| zutHPq3mfmH#uxxqkv`y&HLM&itjaWfgvW$+8H)r4`F?~P%tV-W`AA^xJh3nzCj;uI zw$?}ZZh;R;*i9euW7`k60YCg-0a)2KUU)DbeAX+ z(lK~>?>YDPJLlZ{$9~p+KA*Mrex9}0`tH5HJ6cCenV0}d00027s)~Xx0D$hgz#$&? z-BU69h0L7*TdON800z}@5vq4@_#P@IUUyREznfCOvlIY89aR-%^?m1my$^P=Rrc)}nev#FJE^KgXEw7+V@d`y^D~bgP-i_Wna_cf}GegPJ!E=3$vK1!@mU6ht2U zuzbkL;UpS*N{L?pd})D%hu?mBszca0;{P^PZhSjZ-^Ji#klR({E0N8!glp&R}8On1Zu%Vlfmq>R|!%`>K&;<;^%`;tgP#y z3VSP2iXADxc_$$Gz=<5_^^6L0bk<4N@8bJZV6X^^7z~yl@c!`_fj8leDu_{)9%BxZ zJC2H)z~}1L_RruAHeI$Fu4yTBDcc~CD+CP@j6Eona$uCoU)1ClD$=cT&x~N=-}3jj z>my6Cz!y4a(PJEf);q0K7s2+!Hq7!~gET%0 z?zknmD^VA{58Nl{y9Vj6*t$G+1bI^j{6Wl-!hZz2*-pl4J1n{CuouyXXaL~d9EO*W zG2!Z1|K+Gg+l=bUc=LJ}i0187QG4uA=ay(#I)tRvv&XoqJiC{uXVshl&6sQC>BDg& zr_=S1v!^oP3)Z=+5__#d=*=*Ryu#7fg)Ti;It&z2s%sMKbZ_HbZI;|jy|s#3oMV`<)U zrzxj1(AUp|W8#jcsCPaQibx_xqrZ==txmeG_hT+NK|H`kn_X0v;mu4{Xujk-ghs?gpz)|O1n_bKTP%PxX@D0t-XuS7`#^4C`Th>#}x1zyGK9B zYfZyI=p(b?aQiRyZg2~b>GUbdi>dmlIF4xgY)g_wz=omP06r>Uw9%5ritF949jySn9 z)CFQK;NRCE7uZ~xS*euaCy~WKf=oN=h%i8(=9kcA?q)jpX=pU0zb>oUU7O?lva8Z1P(U zgUClISn&?tu=XLtHovoEHu)X;9ibq~Yx`Yze><1aEM-IA6JtO$1P>%Hgt= zSVxu8I$eS5g*}#ZWT~E)aA_cfThs%ye280;g`0IsHmtt3mW?7a;;v)A& zn=GhJ&2@|S^S>x!|44P~TQvgP?#WPod-Xq9_b+iCp$$Cis!^FI+QjJ?PIVQS3)9j! zkD^?1^?N{D#~79;E9P_LR+v5x-P%pYQyrwPEFHWeYWa?PD4vMZ(W>TZ@k`6!JGN36 z`wJpZKjutn(YWkGDXdyONIu^=UJ!G{l#m0 zF3Bz8!G6lJQV&GWA@TCogngMqdq?wj{c5w8L3Aq!ftli2zEmE|TN^=Br4>b^ju}}N zKy@kC*vveIH92ocwvhMSV?};NqbUXSQRrG zf#X81tT%#L2d{@ye*A{gJeE>l-IvO@@<7ABh$x)kZi~s#3mxvE2_uGO!|W$So?p0t z7#0ZrWlyQj!iKY$JEnhEzY3UR{6pJN;zg_ghS7^Il25+X^B=O_ZDigouQSc%zW&= zuzi)yz6VQo4$3!`-^6%OSWx}A?b=KbM0Huc!%ym_SvuzHO*fpeR{CRj^wL+M;oM8c-VS0)JTtK;Tqg3Z}aoFXBbDRfRdK8YVkyg6C&f=)S z>CeM*j&(=1XrR11XI+G-x-Tt+vBuV{aEUQEdpcs)K1#AT_MOO>J$4>JMiSydM=2Ay zbU8;l$JC}z%0eBzQo!9M>&CK*%1b$?cCaIM5b3=aqd$P}`{j0Jo$+h$obk8%hllbF z1Xi0YcTROu(VTJbCSN2<`C33&AR#Q*taUGPx+j)gxE-@{JVXz@=Lasz5@QlS zW51~`%;Y9A#wgv5_FRDyG;>)VgXzY_uFa3R8W~i})Wmwof{MU=ZT|3nlYTg8yE?~c z!@ZHK_+GPImT>OygJr{XtWBO014&@^K^xCk#>4P0wNaR3I_!3WM55LGsu0DqokzYX zEIN1?)-yIg$r&%n+#gnilxJPkqk>&Tqs|RpTAw>N@u{I>U}Xx&ba36HOrr>r!?Q*E_UQb}ZVN+#D%9LVlfdMh!Z~)o zL)*2gC^5>ggK4?%N4i|=;OofUFH!ibGhr)yPVBlEMLdGDbAeO5SNs8tYo$ZCR^&iJ z2wZdyEWy?B_aW>50ne=Y1$GVxl`JJHwX4%b(#&lc(G@n4FU2`q6YG9C88(65rNbaz zS3X0KS8OR?$=tgjRKG@i*LQ2Ay`*N`P*N{8vCGgzMpcFIeAWOzifOx4U!<>+d2f}} z#m&Y2O}lo$1fGqrrGE0l=a#)8NbKg}N^Fi&$x@Q(RrKB({s`rI%lyWIOx@vRfIm4q zVD}M20??;YeMZUsz?YcS&@2NmM%K;u~hzRl_ImCm>5-cr>?Tp0s8S((UA2c*iHa z5;4NVe{5*HJNO+Ha!Wol+Y4cnbi5UV=h+alPhCze@=j<|#T_0Gyq$KW%-8@g*>uG_ zkZ7gq8KswR7Ar13%59)q?iZI{U-5lQB`ng3ZDz`D?8*c-!Zay*BXe<(CK&m05J3f@Nw*a2B6ea+PN?ABU*hB5wMKGW!V%^|S6I zXm7?PFyk$Cj^*IN*F9}5?U(AqI1FoVCJwZJt1u5SlZ=BH>_fyxl+DO@0Oqe=z*}%5 zDF`%&N5!>!VE6uhV4Ba7`Le;MXMgP(og+Qz&4r7i=!OvC#GHOtX&1ogp=|crtH5wQ zC>6P@jBteg$4ZyHh9rH!cZDMig{l5JIMfto+D)d=tJpu7-FDS0WKqasVLKb9|P+69?5R?+1n zt=~T6-l;eYg@bZZZiS$)JkbW!kNFJX7BoFC*SHM04ZHfgb&7*k`Wuub=hwJrJ0FhtjtV#Mdi@xgeO~;rYIug6Pg?7FlG_~`1K-kZiQmc!n>cIFRtYpS z#7IvUexJGIG<7-BTh^J#*#g5d=TFkqXI43uPSoP^8C1?>cM;#&y{an=T-}K`^x0*~ z!riUjdG=gZZORscVWj7%JhB2qv#jw8 z%gC>W?9T_YhZ#?bjZV)wjaJP)VVE2I!{Zn-Mi4xShElA248%NFJ!Ku43Ea~9zLC5E z^LeoQb336lpLtI5jF)`cB7Bb5NUU8*=K@{zy0N;FMDuCcp<_aNeN`?_sjH}87~vaF zDF@?r2v^{a`9TQ8xFt#bKIVcHFp#;kfnku_z1#aJM*E7ak~R?@5x-Q&)bAV(3buk4 z^`q`bC0q)L*Y<^_FN|X|(t@axK1okN{_@qzXWz@QPLY`lt~zwwUJq70>w*$W5WQp0 zbfl>6XaCb{+N9+d8a%NM-@tWSwRKzVxd;~2w}0j8g)*J%8eSY4?)aq|<$@P=W2wNW z`YW#vGu-VCN;A3Whv;Ln#{S~QAr1-FlzLo}-@;>YluPRb?1tM#sIeWh1h4VKt zk!eqOn7I1SEZxL`Kl{md!!S~mT5znCR?P4O@ApHGB*#^`HF}EMbnUHdiN2zH=DrD6 zWWpW&P3PKP0#rY;Q!sM`OGPQq;NfGd>_=P1uMp*1HIcK(a=dRRzs^nSJ7)|9th(5f-@HB6ahY`Tq5e(8^X3AwrK&Lh*`ic@Vig$o8u0 z7ye%ZmE%Bvg%#B4=8~bkB==TppU6Koxt(u!xc_X~-KPdlX!RkoT!3*DY$zm%DHRPN z;$#8otkgDg33Xxu*_DG(qg?98jW7VMpmlDX5 zyT9Q~B_N(+wu$BxA>M7-GBa1X|NKp6Kl9yI=I8Xu6cOT2GG^89pVr8`Og`>}o>i7# zE6MOvB@oxQM~-*NdhbnvbU^h0x1eq7fJvlnVLFRbE~nM71X*_Gu;iWE?7BPY%Ov4k z4q)&BtyuJob#%#>hD2L%p>e*g$_md;>DHSdvf>iGo+cLIa*yXK6{mnFW-e=>Wld_ZAsBSQ;W22f{hRMPmO ztY-#kV;`U|e>$6_f}N`_P(WZ^+aS+Qb>g|bcDaR z$TuocSxmkd5lNTV%|c%$IG10xe&as()Ed3Z0962whW7JLP9eRQkmw|B0?^nx;oq6v>I zAPKFK^&y;LYWp$akY_s0=byeGT}E5QS78oewH_MMp?c^2BUMoT&tgtF--tj+>=%!QbLmIS+>LR6d*zF7g6@a zf9)Bh?_4*plr5;;DP>?Qom-WACIvWtXy<2IAco-()E3WP`^?eIFA*NiHz{vWbrMR5 zr%);Ro`8_jcc*w)(Eq1R@35*5yW=IJjQN$hMAAgg9ct`}wym7f3bnB$Hv$VmfcLJ5 zs28U2yaoBH&`9YaaDLcGFn@jV?52WuY1^S@gtfPug_DcC#@;=4I$7|Og$HY6!`(3tv2Qdk&=PWG zN-%ke*A{HyG8g6K(ugQ$!8bvDGA-{1$*%)KEl5GN9e$;xpi_g^_P}n6(h?87D>vxZ z!aT&~%7|?=Msf@oe>nD%5k-qZ!b8F@vQ(%n`{cSOM+x+4=~?O7%z1;2eXmj!s>jP3 zv~|&+1TZ+!m>*{9Eaa$IT@bmMe&GG2qF{E{j?vBUmXHbMqmxIao5ClbhGlRUD>$Ix)$+j=ai#?gQeLHNts>;#*8hJX zO5u6p_112e>Ul9R2YsB3T+ofER6nN{r-(%PmoL;5>QwFE*6ST6CQ<>kSdFs+6Zg-F z#Am4;T6!eQ9e&~W`-BYalLTyA~?OO z(nLw9$z@~D_x$;cNXUD|0fy_+aF~$0c2yaP-VEqq}MnkTeH%I_&ZMtDxvTv@Cjb7vuiOK(lKY^u`2VH z=!wx_|EVx;fc9wRka;Z7f9mTpJa9s6$vuI@IDc7FeA0231z5D^y^`iH^s za4y-M0nOht=(*b=e5_vB0fwjI&1l{+cdhVT@tr__Qyv=kC%ykWoLktb9j0~+zEwv2{ ztPBjK89Tcf7#LJbTq8Rc?MtW?ChKk%1w04US(ficg**Xb1q>v9tmJ diff --git a/css/images/ui-icons_777777_256x240.png b/css/images/ui-icons_777777_256x240.png new file mode 100644 index 0000000000000000000000000000000000000000..323c4564a74caa26eca81548d184610bcaedfb1d GIT binary patch literal 6999 zcmZu$WmFv7vTg?V;Q%wiB?L=w4T0dU0R|tO!GjIKVQ`lq!7ag^;1VD}2pR|kG9gHS z0KwhuaqfNZt$W^jKf0@Tch%}$U+=12^>wtCh9WTmH30ws#L7x?+5muYe+lg4VcnnQ z^W3HG1;|oWQ4a93-u_&DKf-raGW58YO8+_(h8&**05wusPFmM{b|=@*$wo2Yv!rbU z;g(afsjRAXEaw$iouT5CRaiyaIc=*sRXwR^Ax0ZkVa@in9ZyKAC?-aC_?^s6EDN^$ zjzZl6b8u?N{!2NFLQ)#+Ch&HZzP*liqjFMAbw%Tu@J*kbPtL8IslfW^R*pyQUemaL z^_Iup#n@_iEs9YqflJtv8bLT_gwx2T7HZ=j-Ij*kg9L{=}O3x zT^8dsZP(~6Z32&B&tXybL9CuC0B+$Bdb<12w>>kwM0c}ET?fiiVxl=7_aJ0aAeiZa zkycb*uwn5Vm*5%>xlw$ggOhQM2bD7q4Tnqkcg0eJ2_vK$>B$Kk@z+LDcKDKHJ>21-0*nG7DPkB0}w|-Q4xKF zQhiI<1xx#&30H3nJJD-jl>eN?dQjvGgaqnm43N!-=xwF%xyE|tw^@Z-m)-ZSbB}|G zqlLa_41}nWkqBiKT7*wE*Dt=gMC?{*28>tXu-D-7LS04-vA*#Nu&d5nJMqD~9$!tD z|5Q`Y&f4psKJ*3r4(8;f=mr*^!)GuvQ*nf4M=@O!6Yph(?l7hxRRM~gbAGTrnAW^P^_*K%6_ z@aNy(p88+znnCguMf=-Omwu%x>p?*u{$Q4eS*$TOt}$9_r-{#6kre)Rhk`@C&g?BS zFjh%%#8!;gtxCp~`l^?s!{0eLwfxl@#!2RtPtl|2pB@l!c^)i_-A2JA=yZ!5GBYn2 z`tClCH&t!bOwF4<@kFZdkuaLNJ~gPG(3Pv}WmCe33@Vqc(sIZg8ROR(-hp>n{$@bMbA#vYV_pZAHdSIBMz>kDUGJ zDE`=(VhT{M|D~cXP$|`cl|5BgZXiPG>?qw_+Cjuz*~`8Bsy#K7pSnlgbB1OWC&37z zFA069dl$l^! zaHpeuJ<5DY^JC@xWZY_lm9-?SjyA-1qApvQIqt=Q`*@Uj za~Wf-so{KabDpTKbUPt7H`@Ul#7lEtBGfdIYJ4KiV!V!wI>Ux|2Ng2U(~EI~FA0*a zcytabW27P92LL$bH!|6ajaA$E27`7n*XfU9YMfn$FEoHnUDSumoxe;1B=U0hHkk{8 zF0Iwnk0Z12OV+c6lDJl#uG#|#J9)pb->+_6j6+~R*WY7JOcabo#XXLd3VvTS-P)}(XBk+ysp&&1prikDdO@&5#5CvZ&D zCuplL6)1rsX7x)`{mSMr+kqQ@*+%#zW&WYwtF#6?3AIzs^Z=isgUS>V8nu(Mj<(@W z3Gup@K}`Y%(8bitq$D#^Fg4d?4a7s|8ki4@{n-Uik@#xbRQUSi58an#1&A52KxlpH z%OW``2^?NJMjq26`JB3b5vB_BtM?oqNJGK;#%5Z}HJfb?81*|&^vc#CMjC<<CUl`W4u8ppJ`z&TZ_&1g0fSS7O{0! zuQwSqWsyP$DK67F71>_ABH&D5aCaegaC=ECeFH;!^fvF%5HU?(eoMa+@u6X8YI;0f zpW?TUn+L;JQI=*@)$k~Nt@E3zu$$?tgS?bBg-?NeE*IBqu|N1P>BT@@B!6KjRp?#U z$|%8u7Md4~OC^7ot{*WnglUZ~A*eZrR{y2M?^s}9(=O!vqnAD13aaaRfRNNktm92k zBKqi1d+i>0xvJNN!F~iWH=ePROdgVB&@6{Dvi}K|@@}tq$Y0BrJg?|o^!Ia9T^kQ~ z#L@g$)pG9I*TE|vuy%JJQsbOqK6HZ11)0-hev`&LyX`RlMl0vdy2Gn54*6C8?tk`^ zzv1>%21+I~Bg%t|y7NO17R!ip9U1>Z{b*g*%c#zo?1QLw6EoQsO@nMQML!u`2B=f{ zRlM=uUiW-yo$dVcw`Vb)nxRLKUDed*0CVHp!K3*_(*s@;sX}^OChJ1VjJ_#^MTmOz0kQq z9IGB-XVYJ$v(#FW@6b=waOH5nQ1Hp=9X0cp=&VPxn1Re;0)#D9;3ODg!s10raQ*Sm zSHaEwR^w&IH!qo!^?=lmQL5q(9@bbgAqF% zRk1JIt3(4%{rYZH5gDthJ%mm)rS_x)-vwsDxJ30(?}RCw%N#~sCXeJAkO5RqYWni% z$|IKOY0u-0v#nGg(SA=X6X(IzT%0UJ%&f74VCFP;P+r}g$OZ1ftC@69F8t1(S4?p^ z;Yz>&arwq9Fh^zh3r_i@8AR0WUh4M6=<_6Dc;8wWjmckMLz_km_c@E&1+<1Xs1=1BR%fLi|ZX6 z>=SUheFH{g+vrb1D^%yBrV3o3<84d$c|ezLE3`i7wQSFKAem(DMyPJ^)*qvG&7lSn zSJ|X;SlyY{0;+&CTU$t~?Gt&79>gJ@u2UF~@{j0!a=U%d{opR1@=)aCGkl+LVdqn_ zLyeW>NqgcWQbGng63bAoekkP`bD?CP@i`5EFo?&SUbKsqr)?Ox2xcl$$5gAscKf-g zt$UX$ryt!jzFu>|8DpfJZ70XF7421r(d+jS$GI=&tr$UI2LkUTD*?K4D8JH>dc%qV zR(vmCtYdwbe~@!riPxYan21qAS?go-2iO~gAHgW%c}^Sjys<}dhL&@$dB=nYLu>K! znIQ&$b?FlpevU1VBHY$o!%)D`>8T~?&YvPG!ifb_Z134_l0{gZJ|Cvcym`k0(93=_ zeUI&}i~2{|RoAl5@f^k-@&w?DjQ`==n$x)v!BH$9U{q%VT|BhKFYf+9dxK1<$wK~B zy{d>Sg?sLydV~C}Q(dWB9+GUAGs$T3;C|!q)RKq(DX8ycViv2U%?Y zftdv~xAoddE-8BFB<_oz9YmFclo;5)Sq)LwoVzUd4T=BAgt54)$L&Ub*I#SzPutk0 zrqpxLt7fKvOLPqNiN~acO{Q#`0dG`h%w^zeV>`?@$d7p4WjThqHksI8rqm&GQER`f z#XRFTR1%E(n?MdDOU_P*vbXUJ2RQ?*qMxWea=vFFw|?!lg~v9w4LV=H6V??>Ul5*L zeX2@T%P~XnNXck1Ia!Q*I_u?XxNp-ZViGUlIYHd#d8=4cVI0wiV zhJ71TOaDObZt@1n9}+*fgdnZ?nyTHBV>jWvkx$idy~7?sM4@iVw3uPwL=0Vmxdm)2 zYYCIGqQzlWwsZQHeTkPG2G!)JmB!toi8a(ZawLfM-jG!Bn-TvcwhOT4Ayd~dM?7J) zaKUDuz$L8HqNsAkHozsU)WZ?{tg5IuW|%DzWc0M!h!!&nuJj^I;6NaUKA~{cGjl~e z(rUWejI^u|S(uoD-7^~4R?z&2Ok#Cjat(auE^+2A6g12cEj*OC;zDz9VqmsGbv15( z*h%e$p*(a*w5tr4dws(NbYbQ&>g~0yD+b(E&~nvc^y}r+A_d-pt6MKeY9pfwYHWiJ zAxTmv5Z=7@GLAPN#g){2PlNc@YDgp)1?$YxWYZp2D`Dxn0DDW!3~Ttm1cQc@Z#CY2 zY3L6?8V-LR6GXML)DvJri$PB~`_Kv2d`-7P3tUk>9fYTMRwjk2!q?&@1tZ-@bx-5V zWzFi|Ga%e+gXNF0$i`QM0uy;K_p#unI$_>e27{kHg>V@X&4R>n;z@o_Bh7e8wJ4wxgPWrCMp4Ne4c+9|AD6E(6)j%?38j>-o$kKVH<;h#W0M*V&KhQ6 z#H0hlQE4G~*Eo-JmMQj@;nyfL&i*)u^_2sjgQ68Yk%Q+lFIR+R7Cx(opCisFFQ$nu zPKe~&x%!=!sQ4u~lrE{gbBOgW2+gDQ98RCGE-$ON^Hwgb8@?U0NoXj{(2dUP?1$&X zum3zbx(}o1WwoWkUWcV0;ArnGPi^;*MKBkG)G#Js`zM^RF*ap z5BUJS4U{%!^fUUFG-|24>!f<`PtzIv;YyfDCgGtECZZ;6UjdySs$zk?B;hNRNHEed z+le?19h5$)qm^-`H->&!83;AeI2+(%e^%DP=zQaF8tX`Z=&_@Dh;U$N?X?O&XIKXf zp`|4%gQT8f?#0M%oUrwbbISM=p={O+)}mx^!>oU5#}YdboV)x|KRxgeACdj)`~vgX z?h~vi!HE$&`-cYTESUzx zcx0Fqi>{b>pn|9}N#>uXZG1grJwc%Tm8w?)P2Wr86H7B}!V@f&b%mEn9 z*B6%EXXV8u?uAeIZ*9n!nNMq}C%gOqTEq^N{uqejsT+P+b@%`dkt)Py)NbIcA{;SD zfSfXnf6Cr!xbtWOSL-UWbRU7!hsx4N*~K5GHsO#Q2~EU7=5-Gw$D(tl#f^3mW{zRP zcVEE`pUHd=T;0vSJR+JKJGzeIa^?!h+6WB3<~?HU?ltOUamflAKpxz#JQ7uQ{eN+kMxWE_fCwg4B>G>Q{8F*~mwTXpj3J5}98IF=8%b@YjA*FFhBP`|Ca*Y0XV-;=-bP0O`VY528i&O1t1Y}-UsOC$-|nTm zQZrYrchgaLudbcfnw#Efou|{^5T0Qho#LPUv#m_|<5*!wbyx>MCQg)5z~ekC({Eud zxt$$$Q8dMi@*j?hQ%qso=}_;ov+s%{f&*4(gq)YkT|=4|tZYIYz&k$477rz?0`jxF zc{D=ExE^|YcK%Kliq<0HCoitG>VEuXYKqN z%QZKnSy``QZZofD!6(~)nl`%~X=oG?TKjVR-XMoFFk%obhD|wV8^_L!@u#TUI3$y= z3ZyI-JTk z;&?vK1g=DGm6A2YR2Y2Y<4~uM4E80Ou=wQrriFWf;Hy(NT0wTHCeufmKup&*@N1v6 z=k^SV7Df|L#cw4(Y#0ecWH40}P+5Et2k&N$N!+WUm-mQHlY|P`flA89PouwEhG+J^ zPlACE28A|Ci#tQGBep9>Egjo9H+ctxrT(zjVg9B)oTOBhGsw3+fBO92Duijw8m5)* zs@%gvPp3!pL|Dg*>F?MHWUc#OKJV|bDL|dj31plu_-;#EgC=6&#S1;e)Mi(&b9bbP zr>*pv$xx7aq~<(Xy9#Sd<`N%$!Io=*+=7X_n3u`N4Z0FPyCgNNT$|wW{TDLyznNr; zpZ&(;db44WI|PAKt_O`I{DrH2HgVt5LEyrz_}(a&hT%uESyScd-WlnJh3}sB2ojVY z!XzLaI+8t(wCv|oBy9=$ts1VaH6`6|Pf8iSHm@WOTXjc*UN`))>cK{#W3RDiX&*Nh z2#eJBgA_B~jerv0_)h+ublIHhu8o%5>|0-&JwFouc6#oJI>>mwo4FU$i4#>Y11Jx| z5TYX?Gj#8bFt6VqUtNR{Fg%%;4OPNCHYo1%@3i;L6Ryl6=K$Sr3cQ+Buh5lsgGX(P zThCPP=TC*Dux{bx8caH&5MU|QOl^)sfp}4WvR5zB=m#ANCQG+JVr*y(J`yjZUBR6c z(wh8~_?a7JMtV=28vm0-y{CV3akbI|pCy(YD^k`_NlZFV=0Ok>U5`URQ^lKJTIrs#I-xnnf!ZFtJJ_`~l}JH{;PY&&zDF;&WDJ&2F|_ z=5{v4J5dVzd*t1GptG*id}btE6up(a&s0p_H2{%8tD zMMl@ZKMD5uy6!s{`!-{NWWBOjkkztDRDSYP?_ zVG8?B?jdgK+e17ltu-aJO4fz}ze@y0nVF=tZ#VS~B99Wi2*p5~N#bL;;N6WpY;?d9 z=->GR(*`@!!ImRQ+?)~bNonjvJd`bi%qI4@&FMuIPq+)ZV!p4}X2=LpzEoLM4@C>6 z?q2*$g+WkC5U(*8_m@I9je#Y{E$ieb=zA@2s zRcb+cmfe@-kM6O<=D@_~X77p~oRUn}`?c_*D$5n*0M8*qr=((AWNp#n)@0D&ovUI(AdlG`-(|swh?6g|90*7^ zDE?f1F#q|S{7tc|=f?PcfN3e+-KFC$6x~36ES>IxJ|5mDl%XbZA$Ah|??C%cUY&_L zbdFS$LA_lOdvK=bo;zsfN5b1b4;G@azgf*U3x)e~eRp}bC2$BbxB4agQ^9vRPa2m7 z)A@62B&G8WJ@s#FbD}sRTB}i|L~-OFGD}ll3$cCdZ9-0QmA7c@IGB=rg0n`U=~WYc zlA`ipsFd-YS^6%YI~cpC#7HrB_XS2EbJ_n*1b@%(^DZR{Uy|t?=^#ieD38lJCaFAl z-7iF)756q3M+$-(Iu~$48+rBLR$JJ5@Z8@RI~OoP8@1R5F)USJ$gmRRyWw6>aH#mLTTXxh32<-HkyaBfY{PSw%}0;;+9TsG&AzGoR$=lcpuyb<3>FAZ zkmbGIJ!jwU+aLE<-@aAd=d0V*UG?2~ElnjNd>VWJ06?UoEdLq+KznQgj&U&`?+SU| z(vJe&T1`nF08=?)XMB9Z^HetSd6dfkyc7prBmn>#Uln;7-N1#z+z?m0-mrcthj}6w zs$IVc?0MB*NqLmqw0W&IykMhE<715Sw-rtey|Ppy$7Oa-7Uhic!q-qv=X<ow+JFz5Rtu#ncw$ia8BfaFp!Yi96&?N2M}Pz&3@|7>i@(XAg^AF=PIm% zdBX$f@sJ{q&aiPuPurDeR0+DWJ9uu32I4}3?gYQhgrA-CT%pJpK+O(p>XrI$V0R{U zxLL1P<*w&<4n&}vYVOvojf#PYDL4(ItoaT&oAo$3+?MM4==HjngxIQx}zMlWZ ztE?V0R?Bd4DcZR=d`R8wyk(WBBHVPK@fsNZ>@O^w0O_toe=?tOEEq`?M-7ebA&~A_ z$AYgPqmO$AqB*|o00_~AOGrx|)t(}H*c*UIHwEa(Q$Z_J63g&6xe|Ue7;_`*+AEjo z$>}B}YxZb@2A}g}(mwvKc3PbCPzw_bm@esZ(8r*d#mGwhy3LL5ob@cd$X%d0i*q&O zZg0x1xL9N4(u^ob0@@kq&I~8E@yO@hM^k5B%M>x&YbG95kUJE!MJH8`2%b}rHt6N8 zvgb0-wCgJt_Mh%h5V|ghbguuhZ0H?s1j2bALH81fm46qZjcQ2!%@Ok1f@Hj=rJW+2 zx%6M~{O7m-Pq=&pW6$X}Or`HSb&5_A2=lW@-ENQ%sj&|!f0-)qkUvh#yOjXKf{j47){seq)+IN+TT6w#5e; z-*Rbnh(^uun-FEGIa0yLpU}F#GyH*=!)l?j_-fp5t|G%g(Gib!Vcw`o+%4n}FHC?K zNW5?|zY&kdZWBGM^!;Ve_)7pOQZ z#y=CNiOf#$!7D1x)`Oe98F8A^t;|ViZem=VG{a8Ov!eETW%fhn8PQs?*#0|j3?&}H zQgLiWto=;)HPT|v;pN6w4Bfuv8ayF!!dE!$*VmLAH`&O4yBhxCy&xaQ4Q{`{+P@eK zET+!LUS?ySJU5TVeI3=$EuvGs%OOqpyA0(;c0;X?z)eT&XIkPX>b6IMZUoeIt0O@l z)TBE_r#j-sdJ7~zx6DeBjTdeIqLIyy_)0S_VZ;2j)4xFXpb}G!L1awC|Nrz122ncmG|%UxLJPaPgtw`DmX2w=n6CF)bm{BeRb(ca>s$T~6+-_K81 zszu{Z^Wnbz13nfBz8xmAb9*uAEy!-v-x_A~Q}siS0zl)%fbfT$EG-YZ^g`F8XbpNx zcY@n+1yY~sR@-{#(9`?d;JQN&Hzx>z7hg#3LfPMRRj<1~QnD49Xr;-u{}qttfSY$# z9~-zB5kMq%TIY|VU2^c@a+KoT5?1Em5gAwe52HpPYe>dWSI5@Xv`QRNaq!f#soe?? zX!sSe@T@Y?Lp4l!pauu`9hH+oVk<{D)V;gcZMAwZ&#`7X}FO?s#hB}pS}_&1FNXwWA`#RH*JE>`3onp2%ses#ccCT{QNf5 ztUwTqY1`c4Ok3Ij@-x%Y4XHMPiAhRgL)8KDF2uGY^LwAkvXyGZFHG%d>i5BACL(hS=02&!5CF~lIP3>L$4Kn4Afe%0k&~ec-L>NT>Vr& z-+V-;CM(TpCyUC*ORpm^G9>w*8^V9>e@`XQ9N+z5aS}|X28ft6589H8Y5Or5`vz?3 zxz2@?Dq?Rh^|ohQ_qlA9s4=tf*tC@M?h77#CP(f|Qvpu7n}V8OIZgAlsgmJN#=kya z=~O;XDq(8-cs2OdS#x^iRTeEw9bg;qTfz3iGk53=%4i<}x#n(gPSTA?^6FWc-$L!} z3~}NADQjs>a-@4=BCT&{pEN71rr&+PcNaJ|X!^w(k@Yq7KCRdFSr`dDngvcgfJKk% z-hipFTFS%Zchw}rpUKmpWD;5SU!HBj&*<=tdDs|`0wF*cNBBTe`jGjD+uC<$#39vSjr268557Ekkk`o)3ZcuxDj z&yz=YzwcxL=%CblrlcoU$G%;8OvR3ZN%4@bre0jb`kE-F#G=*Mq~h>*k4m&ETb(L}gn!Hjoxm!x;q`X)b~mKK{0;ApRz~}m4)#0AKDCM8 zQKnWCeJ3zPUTbi&}+-HV@&jM2RH?HYfA@_ zhBt2Q(k2^TXNJ$cxyh{3lEElE7j-peQ0|w-lY-=S_PFaS+P7Y~SqWG&A0S!!T1%ID zM9!y`>>Ez1@c`HF242r%v$hkF1g^9tP9%M^lMAFcgmu(`?{%8y!exJrPwHnx_$Np%9@p0rNk~za0J+JoR>ov}a|I7zJ9=r~u zKhxV!G0K2GqB7J1(VP%KJS{U8$ar`e-1n*Z8t*H{YH`(D1aTQ| znGcBGr5q(22XB?mmYRh7S0PvGow4sL2i8;qdII_U4S@2 z;Cuh7H^DHq#(BVk=>~lazw}C~@iYe1?B}BFEt&=IQsNw9fN3oUpn`LBPh)5s(FHZ| z#4-T&Q5wGp68+#K3U{_rqq^w990T>!jX7reXx8;|H@NE2?IfKHova!%MUZzSCeJ!f z+A}cy7U1Q`odG@69{*ft6rPg}Rq^6`ax>ZSt_B^{<1Al#uV})(kp&(lA!2f)v3$b= zTfEOKxG7<2Q^gGD4;V4fo>EaNi4>!BI`()1uHjIW_Mo8%-f#@18wawG#rBu<^ z_$dxvm_U`V{zqs$rllbS`5QXh%me7--g8-1aYKVS?pd#mr%JF){7-lyGPQ1UMt(xJ zcgt1Sej11S0(_x}+9^n!=5}yJ@9?rgjb9n1M`_yAl%PT>(XWQfRN(!sRwM z;Uh0((ce?s-w23V!V%wd=+;V8^K>83Yjx|f$@i?C-KkL}_=;Bw+rxX@L)@P4-RsvB zg>cv=%UskAyr-C}DYZ<9J*?6gPCPn(_|W96QMe3yX}U~QMIm;UP2nS0?&}Oqp9@;g znp1ekjD7^_)akR!Ox$FqPkEKi_;T@0Rq#~%wP1KMfPeu>Ji+r%*BXU#y80TejN*tT zf_+gFlkP`14&fhu3yg^vadYoNqwj{)iEL@EBtmn^LlenEjaof7gOQC=0-td(O^b^d z*5LN*Lml6*ZcW44>?7(!>P?)vkMSg*-`x}Dvqy^#TZtiixa*MfFtv@RjM0IF@Yib> zyo`+d>#{HI?1fu1exJH`jkOc#??>P5&G3zWspz9fiOR07Wqbj7pkdhJ3We1iGj)Aa zc}Ea04BzV!ys^8TQa0!xyIH}7Y|k+I8>*;LWgjo!vhFR@A6lM8SiHXL3`(8&$$ z3-pT;_m`U>fLd`VSq3%&X!f1AjGG#B@Q?P0Zl$)Zc zFY>3ig1|@Usj1t~meSf_%zsS);zUcTl`B4DNjLX;qQ>SJu&S39D0_J(W3TfM79`}9 zrepV8JdBZhJeJ~q+B)1(LCINX2M^Z6X7s_*j+O|{I8G(w6x`Akz{(Jo zHdj8^A^tC_K0PVN3E-t-O^MB`ie;!9TSq@=?R@95!~#QVVEujIH!oBZz7;Qq4$5=zew@8Ib>j46f0WWke5 z!38}clJCN>j5oNqR9v67SZ2NGrM3+4`M)e&8{Vpkqh59sb>t_Tw8SJ|`p7YL&4($3 zgXO{{IAai-BWq6|C0R>_{MI*G+{DjXYcUW@%OG-J(|=JpPJ9zVsj%g zxDzn~F8BsrOq;TAr#|I6L5cLU*XbDG3XnQ-`zu3q;VcJ?3>k2?3uLZFpcVMO##!fV zzvG+mV#hZV)f@6p7=1c9_s*y%bYF6S@!C@ugJSHt#D{gHi^kIzGayYn{6^y|Gj)MM zmWPa`0inl3W#?s&EH~=cId@xsBKsE{Up5NZOI}!!s$4FadZpTu9(?D}>6$7P9Q(Y( zF!1JiCqX6R!<6JPa}w1? z#M@IA8=UjXd$euB4ld%N*?fRh4ep6HAT!%0`#C;6X{wtVk8ml+8X_{kF3Doe2>8}L zkp`~BRozA@MwuCEMY!I)Hh6}agMGyukrh5xlbAMNW4-2eL>?~joh!Da0+ZYMixk$H zMiusW5E;HG^D;?L5gbkaj%@zo$2&B4%zJVugF{nJS*I{hu=qD_E?)|@f_k!=g`KZ~ z29(SLciCm-Y_8vjhq@A#=NM2hlOWXs)`NVjPtx<@d?%t*8wlpVR#7jGaDXf{P82`C z^go;k^XeR}EM5x_x;x@Yx}V2|e9X&#ikm>= zF!=!gtlXdT0UC@W)&o3+n^V`r$y>Do3J=EYcO@&P&|RXjMdJDTX$oydK1h5Ywb7_8{@qx!8Yx2oRj~Zzm9QwL z$+=m6q#5~5dw>0^&3aHzsm>DP*XED>*}jID+MjakrSZ&(CE(Qna6RuD<|^wVjsGj^ z!i1mSd6(+OFqS+?UBg9g=p87YXHz=ASgeoI^G!_vTaCth}Yv84AHI8%Ej zCv>L;tR072%L;N!?039-xqP{2<<^J}q+BOL(zuu(*lwZq6LL&%zVqtScp>0z-|f;U z6B$%<=^?--XWF&b{AzVGiP+3ij(REP3vgZ)+Yu;6}f}G%WjtRbFN#Z|}yiY^jAfnnA zZ?Tm%i6*r+_d~9$-`u=vmDn{>y=g8KVOumoEDyfniGByHAGH?8H=KNXR*{D(PyoY} z4jWx*)4~cky{0<{cEMqjR=uT2jeR^n9zt-QphV)A$0I>I5=Kp;8NXMPROtW;R#I@5 z$_?NPo)MzjC0g5(S^HSQX>zg{e@~|@Yj)AkpTc_*&bd9zIw%X~!|UF~cAE*Frc(Hp zj@}l;MeWU1(ZkW5(G2bS0@E9aD~3UGx-x>@pMn^dz&Rn0YPG$q&kmSeq_qM1T>9Qm zYI^YnW9*nJFo$bphmyM%(<**JE@a@vqfa*zrfR$6@t)&dcR=-kLi$xG`N*Gf*YfXh za~m5tA`E&hxpb0Zce$yJ}~q3%Kjz`Kv_? zn)3c?d1e4l8!E5uxo?KLyI{|k(A4Ul$R|NQgJnxzSS9{hN1tT`$b4h5B!|MH#gS3b zssRIJ#@v&2Vre{WoTCq!^e>1%KI*VMj^gTxajcSOn~~TEF45sTA+4#ERq}y4QD|;g z1ateBghNF{wT?^Zy}>nJO>3TJ8W9@%5UB#Ueuj+xa1tzLhoAk>Q$QW{^=YH1zN3tr zyJbAqdyvuxEFi~u;n$MYsgekbXAx6zV?BB&JBOu*CkE)7{9#LitCT@Rq%u`#smT?? zp0QX}uWXyJuk=utr&yGdhHY`!DcHMps&UtWlV{>?`e3%#n8S6$R(YU9y0;=Ud2nOhSNm{1I}mTso8#HO&u-C(#%}Js3&$!SNK(&=~0>4yW71nE-1| z^d|N5LLYfY&p?xZB~k^@YIAY4K7`XI6`myqf^3fSz*oUi&vtClWO`KAZte6+!ak88WHwv zGfoaz^Lx${xwsZ#!TC6%M%q^PIt1Tt05yo@Q&BLrmPPOWse$_EkN6@*zl&vDX;d$7 z|EWSk!$^aN2E|xQhNTQPTp+lgnJhb^w;x!+w z)bEaRZu;bO#G`w>=0gDH8&X%&@4ZPZAc)zb2iU{<1>K!_SEJ3D%mdm_6Q_8l=xeDcUR=6MQ(!Dh+(WM1t;i z7h%@cKOjQ^$Z@9kYg59yiiiJOTDRQ*zC4 zD5_utUXY+w)4(MUa9*a{JVdj8QsQWFhUP+5+Dqqt_XEPD5nv=-|BzKxD&o8p&j`@G zF}BMzZYtqyeYt=xe)Hx5Cw<2;!K0P%x)K!QB{ z{JQ)?Vqj4*egQ5JNDKr5l{r=Xi-DVmt&@Gw|8F33_LKV2;MqTB(DAVM4Y2aI1IXBV hSlcnGxLG;ay|%Nm4e}ba6Ms|!s3>U4SIb(y`47Y|2vz_9 diff --git a/css/images/ui-icons_cd0a0a_256x240.png b/css/images/ui-icons_cc0000_256x240.png similarity index 88% rename from css/images/ui-icons_cd0a0a_256x240.png rename to css/images/ui-icons_cc0000_256x240.png index 49370189231d006600b0f0c2967cad1583eba634..45ac7787cd2bb4d6c3eea7e6e4a893034ddf75d8 100644 GIT binary patch delta 368 zcmX@Ad{lXYX8jokhJi%&18Y5l2)ggjUoASn*_x40z}rmMz+BhRIKd*#!cVrwAGX E0LPeQGXMYp delta 368 zcmX@Ad{lXYX8l<%u7O1L18Y5(2)gf0&X&F2Y|Y3g;BBgFV61Cs7-DE*Wol_Rc?MtW?ChKk%1w04US(ficg**Xb1p54Qp-y diff --git a/css/images/ui-icons_ef8c08_256x240.png b/css/images/ui-icons_ef8c08_256x240.png deleted file mode 100644 index 60eb071e0259da5c0599989e72f8adcf50baf81f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4406 zcmeHK`8(8K^ncIBM8ixX5=LXozD3z)46KF>Mlo+ZWHR3E{1nhgK|grR}1B>;eq zSYRCvIoihzjrNWR)I-}u8vyFl+3D9{M=~hTQXdc0d^odsB%s|4EKL9)Tnqq+2>`Hv zbVOVLfM9t5SRw#`+EV~H<)7DXp>d>l%KWmm-r9Zm|F8dR0g7s4-;wQ{gDg$006wG= z6mH%Xmm_!9<~HsI;q(c`cL)v*9z3VPWg#?|BSzdx+U2ft60yg#|6|_;y)%@ymr<3? zCHKppS2vtvfXEgiYiulFNV-B{uPg6kqx?Fb={$Wmtlz_%;@157NAG}KjrDbbWSD@# zk#Nl4z#;I6{=*vpmj=AF0DzO#P*>YJ?Axy^>~7;ND3$>O&xNO%UggHm?0qTca~kg$ zZZ9?X`h2gV z(a$8Y4=ucTo>0v(Lt3qu#{)xBc+Vy%F;X3~7K@oPD@7S*98x@R%cMY2~TfAIU@V?K%&)9|8 zaRH73ea~Ek>MkT)Hf+y93PMms?;ao$elmcT-^bNlWS%^Y=B9ptjV^kL=X0oDfr=pE zVgD`lWqYcS|eV zR3rHqt+ynpYVYx#waORx;pI0Me~FHJf=nnSi-y8_-N>{x%jeR7d@NIjwa%u|hT!rt zZMcLfq6RrlwF@RDM#PyULBg+0grESV!~;vQ=H76^efJA)I+4=1D(nNIq^bR8cHY*| zi_*keyXT8j6?^6}{Im#4a1hB;YQ_h!m+|cg*Y=Ym_-x!_1x;OdoyxXy=E9=GVuSW5 z1Bm;~@dcryJbVHhmkRc!1n$6nUl`IdwLhQdhBLvk@ZM*WqD90=&FK=(JO*DY$ zJmGo@=dogV+w6inNn%x+YWmzJj#0RP@u;PD__ej`?D#Hy!uOy)wVqSRPRpt8)C<3| zbd{r1@Vn-Z(pdFyr83!aH`S)e`eUE(6&gs%<5?1bUOWI=BLe;}&B$xo{?I#mp~#v^ndjA;g;G1sNKy zoPz<;!+XH3dgRR9hVh-J)W+};S%TqQ1^1tM2;LTV?7@@k-@ikfQcm+^2y7i}B-MaRR>WwXMiGgA=!Y>-g_ReEvt<8{zi?UdtZ6rZcKa2A{XxHHWD{SPCs4E7Y7Pyv|M zexu@fq3&DJ?>LE^_-Jb8B*BIxED%2sG>RyF6?Tg&c^3rCll0VH`w)||@7zSJ}&C7P=Pllbb+ZW?`M4t#y% zqK5=FhhMp+>(DkYWqM{;B^z(*u05X~E+p)b6{8%$jpX^DWgf4?BhIdFbS;5yu5tC+ z3G*}#G?*l)8NsAIPbbH?xVt)lqd8+T$XHic*78U9Gm-scZAqW?n~3ZlTFujI7BYn) z1mFNOpe;I4qwnq80k>}g_k*`*%+}#`%$xnP-k=yBa!S{Na0%B9koY^W>0{XCCXKW8 zNmU>l%t&wRO0=BhqMj91@o5sO#+>hgX%AFGbN_n*ee91**wRzt$+s3|j!daYS!|4s zl$e2l+`Nuh(N+V~NFxYot{-}?`s|lzjcSdpeOIGdy-T=0-W&U>QfDPV2BheaQ2(7Z z7wPYi{P;rQJGx|&1B^$Jhph3bp@DIOGHgWr#QxT+m?ZiuGUMQPAvIkA)E5rXq={6j zcjUS7T#iv9nhiW__HcRo!*6%KF%fV$wEDQ0Sg2Eky6zOYi^gt3cI#9h`S=lk4`6Dr zYb<)r&yB^imB68FGY#lOx>|d3Zh~L#ZO;$-b$yy&96Y!5Fl6*HxIYsm z!CEvFmt<|N!(G1JtaM&59v)WnV`&}XaWD83DE80R4{KYFCe!yuclzS9OK~ODYv;?l zY*UpoI@mN@(D5Xf5AlV^^TB3(ACGy1dW5gml0N>Fq+c?X>8HPv5kU8W|{?|Gs?E0Z6?{#$v;?l7@f8)mksIEMtgH z)X)-o;!-X5I}g#)tsoQLVj(jzE<8}aIlO2sYoAec<}o>^5%Kh+4UC#btBj7R>${ZE z44RhtMpO-l)}Ut=3MAL8$y(z{R=vdbAqCIvrS4ou?8QKrCXo?_F_?#wa&7}MiK(~R}&za5N+&hIw;@E<(9>SxE{c+MtRFl3Y1`~ zrX%*|Gza9T&>%eqgcN=~;U7E~PP(x#MTv;tM)bfv?}+5+T; z77zaPInGo-Pc?@LYxoe0Se?h+z81S|Jv#m5?kLu$;rMSv>c9Uc_v@q`&ftzw{&${I=<&hXhOcKIn1R4;aK|p=$Vje+A)X;58AQi#mT_*D#RC zhg#n<$r*fu8JXjQgDy;pP+NvaPZl_V*IfywxQq093HLoO!n<@%I0&)^Pd9cuiywss z-2@qH5uF-w|rcYUNPcC5zzlM+WX?#dZ^aEAFiXY;5yUb0)gYla(mr zj z1}x(J`1bdM?Ju#8TINCwre>CGx(I9CYaaCNL~@(wsT;TvKlpAT-fdQ9zPcxW1)_iR zed_T`^NFcP7x0h5-%f%($~@Y>)+S=u@|UJ3;ODa|i)lWx|2AnMsK^+c!H?AE-`@JH zwN{(~PYWS4_Y~V(0>%%jC0WT`SH*W3ii$1 z?@c^;;PG*#EB3KIoxb@o?umZ#UgoA(m|3lkZSzhXw>?q3OaA1>PY)<`Fy2MT@6pdJ z<}!wmFs1DwOx^BC!*?GzN%wky0xcFfmn*?bznpw7Ygp@{b@N+|j^*mj&^L_^=i#&i zh;*9cB%Gwqe~#ABVbQFTNoP_XmMosf$~ugI+NZmdYC7s|IzF`o@Lo|@tW>k~nU`-5 zxo#I+GXD18a73XB`~lkA@4v)}FRNvwr*UAa8E#J))kFqBv*9RnW`s)mJ2+UI$0}Db z`6j3y(Pj!=377k4o;*UmugK^dE_h8`LKOo)n!t$3ua34o>yQI5DYcK7%XL7_TJvYJ zBkl#IlYRkS$LIoOk(XoK#)(xdBUptkvyeV;1%1@+&_;&#ZMn-+jy4oy#{jAi5gQQ4 i`kaNe&M0C4 diff --git a/css/images/ui-icons_ffd27a_256x240.png b/css/images/ui-icons_ffd27a_256x240.png deleted file mode 100644 index 4cece816e7da0e6dcce52535c81380ba18bfe2d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4406 zcmeHK`8(8K^ncIBM3_lL!f0&Sws!@tLv9zGTai5s9=Y z`A`^xlr4lX%#5$k=kxvT`!9T7&wcJW=RVK5_j%4a_bjOvW`!#TVj=+S z9Uc)E03bvO0G0>`WF2Po2)_@>F z6$-cLj?Yy*ZF>uMopAD)$~!J@3_qU7=#nUk*9k3UE$4biEt%NsHSn?joWZHiwwKXW z&87D$p4Bv-VS>n(;%gjiU`U2?QJ)yrJgYpz^wNeibgds)`+O#_LP0Bnt$qY@f_$^T$im*r%0*ORA6QNnc zW1=njaoK4(MArkTYJp0a3H&+Eq5!(Skr|x*l!gY%?vjM+H;1rVXOH;BNrH|j@?89B zK8zxg{iu|I{NMqiWlRSLDfpr8%vsdBZHmE z*WGuYo%lPXu7JVby}z#iaHkBpf)Of;6GX&%fIxuSasvu|szraU|1npCK4_(4dqIH0 z`!V4)+hn;AR6s&0&2Z#4A15zCk+_Le$UaI-Co@eG>)#JY%BR?X>ixQ5T^@mWXwR@xo*ss$$4od-T(=dnjyo4H29wSt3=6{Yr z$e$i=4((;#7pz8ZIsHknwYnIS@6iN_8WDyv`;OE97lp;bjY6X{?E9D z*$H9pLPM`SE{$DCghDvoQ5Hf_LGA8yCH`arEx(Uyy2?L(62nLN02^EMmMY-ZxC|9X zz{xcuW7Ob@6}5BnubvA2ZKUgVrtk$CI%$qkoA%*ONwE+jAShLR-+HyCo>lZf(VE5k z`|^vcGJeWO*7%DgPQI>M5Liqxn-$`L8`c#X;0k}UAM9Fg1^wr%7LLWUYXfqA?7L-E zZ5mO6%+{MS6wUYet~#}ILhy+GK`QY+~eu|l-S&X8b|m+Xu$;2`hU5uxj^Nbudb$qu4kbED>1yYOOB;c>zA z&OxqwtceBD!#sd&a2|L>&Xgw~*qP7T*_SR4z2e$?c}f(D(P+E;!Z_DFHb0+EZ<7cl zx=gyAz+N0KH+bQwfk zUb@2FCGuVSM_HU!glf4$g}X-6RKtsnk*^;mLyJ?qlERPpEhR`JZqSB7F1z4wNj<@%d zF#UM|lHg-`+W|ba{{1_&DfJ|OrtsGOMsh8+TTW$&&_}G-?|a)H(9fJ(`V^dyG32+TEOLvYyd5CM;~3u3 z+9cOk=XjgLzgx1n=7^Sfhi`T4H2WetBwL(9UFml~Lw)1Xa^7 z&j+(*+nQXoGcF|H&j;NcX)V7yAr@0+4v>_X*>mcnd$QA=sWH4A=%iP-cheawbKq+m z=RKvdxk73!-3NC0snavN>N$8b58e5U2vIS|>{zuxJ_P><9g74#eko2Zfz=Hj^T;TBxBuR*~=e2PDKrjwNHQUS;`lM z5`ca5psvJZt)Y)!2i&0vJOJLFF<*z*vu+G1_<&;h$*J87Vx_#-K~nF)W{+T(nlPsu zlB+?s=+VB`l^8{tMFVS!%9CVdt%cxyvtB4hd+&Q8WBiY6_|g;N@wb-cPAu6d1#GOI ztfY~!;=G<@@m3?t7!!=JFbq3WbNWjRrUtXM=Vm->Z~@mZaD7ih_Ovv}h!hhV7O=DC zD)${ykWeIcTc1pFgz>BJlQEwf8=2QTM~sP|INy4gkR*OZW$ynjqGTw8`XeCPH1R5} zj(k`COR=g%^TDUhp000y`0vg?PXZhdtUuC|iu8(+*PNqvQP@q$ZoS4sUw`87L3Ay4 zjm@C>nTb@6DmaW|rV*9I&}@$|NCU{bxPNj5QV1u?b4k{o+w2}jv7$Wi&cJWA#G$bl zjCt!PgJjjov*g)|)X4Qwd)HVl*MivY0Q*F$eYqJQk7 znCwiJ>?>T=7&A|}rqk|zk!j}9cWoLvqogtHaaP6UPmZov2p&)kan0sxJ=n3%CCulL zcOLb{QtVYwVV5&+!rAHwzEEj<&c6zS6}u}yH^49H+w(*I-Jj+cht4cL2pziw9>_vU zvlkD?C)?QQ@l|X$tDY4}fQQ%qSX$@uyc==?6!&NAhm9R~li7RY+x-bSWw_FswX@~j zc4?}a9UR&%s05PhhlHY|1z>Z*k4Jn!y<%7DNFRU7bbf&vF%71*!5)IQ8t&|W>1*gX z@4n~h5wQ_8{*X8}P$%)Tx6ho?z}R$%9vv*7|Gs?O5lFj2#$v;Dl1F~T*V!zxEu)Ff zl(14p(o!AYJ5PzStzc7u5>azWUOZ5-IkIS@;E-8->Jd5jIoFeqwlGRMttuwAzW+jI zGiX}=8&M+yT8o-lD3n>VA?r*eTlW#$hn2mymwNJ?u;+tZqm?1IT0l%69dhp}%oMFX zGEQTb>;A#*1$7~A*MdC04cXD7L}XbNc4rCFDP+TM+4G4F=L+H_kdHVNix!ZWdEa>I ztC1!0)lHA>E4S?B6x38Ct~$(WR@*J-VLu2=p6Uzf!*x z<4w!3U$pGUeu;xQ1umTC)8-?cBhb#E^LTY^(eNVyYaRxWbG3SJt6^u)-WymV?cDa6 zi<}C3%X9pj^aYBfD?JX`xz<(w{5gpW@mCWdlNe*-XEvnN$LpTWg1GI&u0;DNObM5w ztEVIBbJ~MSR20a7*~zH^f>mt*o@dNt2YebY;*2kqx!jbLmu7d7U8jczwC>^0q+5 z;l=$w{Z2EL&=bw!Vi;dyF}urz`_~fJt%oNc-xIU-P4GC6U{_PF%;0`onjN@b0tn^EJH%D-gpQ z@6(Q6m`_SOyg+CS{&ouNS?<~PwJr(GQLr>U2|t@tRYLPs__s+5MMlNqjDDm&`}Wp% zt+nzLcv=*ZMOSHW37pukkzpryUy<5ns;IaceYNw8?6#dp?Xrkrd*J4%!l>S~J5kh5 z*%~L~$l6V`=5kLfrhoprG4>ip66T)Q7)fVD z+?{-U-}B>2cibaG24nMM{A0rude)|QxOttPUGq*np94{O5297h*JHjx3)Yy{GR6{()_4i47kx6V^Z zxdCe6YBPhbL@53opldc%k@C*TJvYJ z6YfQ47vmhfp4kn`CNIajPY|owMzP9U=Ar%IO2(M|fvr64+j6&;B5gRQFF_{V1MIvK^OF~bls-Q9bK`Xv`yGy%b5otPGTUS6i;Q`USo z-*o35Lc(ij%hYFmE%@FKtCBJ~F14s=~XJ)gRXxQuvPKgU; z1CaJU_z=bq>K+AE`i>ZV5t^~_@{Yf9L(2nL1faCaUYX)w*-}4<(W?v2gi{iamuadO z^FC#0ceJZNuzG6R(|YFQ`vRG4bO?Ata97?^Hj8jPTu+7C3H_KU_J*2B`fWJaQ)n;C z^E5GNyH@Lzxy~Y;(X+*`{FyoBs-SNy~aMj+l(?IS|fG%juy8CQjB0>-hUiFd(sAyB|8}D9+L0t`RoCGDf#X z)XUd6G-jt1Hhb+gHsp=gHy5Qa3`DO|r6v5n&_ak$%izwpHuvC}I*~z_u+!i$ilfop z>**`*H3L@fLsp5jn$k@DdNP%pwwJuP@`fRcZ5uMX>m(vpsdFxlJD5Lb^iR zPkdd|rDGzlj!>QnEG*61Z!`+0?%u zmJ^;mK5?Zk4RC<0MI785WE-JC7SB8VrKdp<_D$giPTzwIkEG(V+N-8O&lh$Yqn zqm^y{-Nwc10q>!xK6DWQ(vECI$)d|gcteh7P(i1a)574!4Y*WZ4j>*bNbGn-Z*&jN znsP56w^)~U#8|?DCJSg@;4-!mM8*XnuHm z`11r22Lt~6LRF#cpcy6yur#QMmW`QJi|zL0BBZYsf^~K$c*YlJ0(TISvJG+QstBvc zXZJ)9RPD|l$9%4-ua89PkPAfBar>aV2d~`q%w_)iV`S*rE>Yt@d++arThC?H^zeHr zgMB^dc_{MheONoB zT3-(R=iWroo1wz!3T$JE{`X^1&Xd#AR8of~@1}zv0yz$}t7qUcewR~Hw|&~w+i@0J ze=^`LOzsT}Dk|ay<*~a{f?F_;!O}or!g!j;xl$V1nETGEDmngJRq~}VDF_Bc8 zg-X(ab|`N+L$;R%>wXX?fBwEM?fiZ#sd!lb1^`iZ=x$Vs+qS+YF(%2u^j8|$$f^Dd zkHmtAwHyX4>T#mb#kGM9OR1Hcbk5Yh;AnO+n=!dOVw^ECe34Y2Ftvi9hJ~EqdW*l$3c- zB0hrKu9Pupl-{#nu>SIA^2)-L&h!ckNvSV~u}lQs3V%pAX_dMT1{&^!E%YBYBj!J< zCfyivW5?1o*MHft1RYbUgm+#URh_m%BeBo37K45w5 z3oYt(sQiEy$CsArju3#=pNabC1x1tWmA6~srhb|`~qCDqSNCk=js6v7~YhtOv6X9Aqs{U@#PPoV+tuS!&+UuYmgJNub?7Wns; z0|;sd_z8(rbvPa8D_BXo+rwv}2KX8qsBoi7aH4GH^K(+Am>mERUNqowyil_cS^6OH zl{y+-$(|QW#OZzp(K|erl+_Gh5LFuD?>Fxw{UCq+BgUWk7(^%}gmAESKFb%rMlkN7 zAw+%0tY8PWhjsP~f+_T_r5JB(W@Sp^2PS(M&Ylb=L6t}uskJ9cgqT8BVd$VZFGZYW z2nA_UMbRR4)p~O ze0uUc7z7id`J3zg8?_$@wnms`tB*j@5sT(ou88;Df3QPv`K+v0YXs$ij=vwvSI9mye(oZ*@*j@D1RuQPb#f3=2R_xk`(6ql#fHB zQDft_ZLG4}3K4A9`!jmrwaY%P=4S)Fp%iSCQJ%(x?eJtf{FUIK+`F;Us1@}r7o(nZ zic)5@?$FQdIXSi2s8(;jf-_+l><5E$1GC=MQCYCEVIC&s&BO%V;nk!_q$kZN1{-Sj zj$ypS1+24>&yhiINy-EMBOksE!!5*N#FcAb>rRr$s^OoS=Z`T482{nEDp~mnJX;am z!WfjJAF8FstyBJ7ZQ`1x4Bv6}gi`^dPbIJ;8w@@>5krMxmK75Xt5Xy$J6Z2%x-k*x zP)rM-N!wz1cSR|s07SFp z&&?A(tDSq985W+pK@q8eJuBlm<2Q|5!&xf&NW`NIq5=<7A_2^sg~H)cJfSKcfA|n7 z1uj%eH_1kb=;Qt3aI^S@g^3k)yvx+C&UvHi*Pg1%kKd7PTp&F^EBOb=Vr#`W0G1SR3) z2rwUaP=eB+JE8uUYG49a$Gxi)%cnd$Ql%)4{)I@Lv<$ob9Qx@pZL6_nJ6V|Vu+1ys z-_OSgl|3B^;I}uAys*b>PVe6-qO0WnukD_>+S~c&Qvv9&swAZH5)Z5=D z%<_!Qs-G1gFJFVw+Ctc>c1;R1Y((*T4DxljzV54MP0|oO{5bPQm!BkDN~cv-E%rXQ zK^oBYfDxl%bF^Ml`7>K)G9W@p1$y%UCim&8trlm#@~^QqPhL9sVQiK_rntLSd&ROG zC~qjgl#X+(*($lulUPQz9RG>NI?HUCN`ARV9q_y#KHKO?@KNtRG?GtvB@xQM&a2b^ zBuV>EAiHxWhe?skF?wkcYHf(=XEAFfZ9Fu-rEiwK@)tX>pZyYZJ#L!C~~$dw2q)_B@iqps6;e(W1Yo`qB22)OVxO zd-?<<{?FNoqJ9Ry#OpADLosB=AH!CVMWH^?<(Mg2H4fLk7VggkQ(j}{?x@i z6VN$K(WHdH@wB-N&h*#s4)Y9bi_PNc*debeSA zO{|MWix^;SMA)AwjdKjAcZYP7`5FfW3VJ*@Zq!r@np{Dx%W@K>(Da0E@*OPX1iJCS z!)EWEyc$$ml*|CC#K^JXc{8F>69hKxxq)&^E=$k+kDiT=DKE~jp~=bX8xq;!he{nW z#l%3kRpUeH(2p3Ssmb4@-qFmk(Xm=JLkr217jDW>wAICxs+xHSjwG|pCdXx<^X~qR zc&akK55Z&Et?YJkn-|O*DtB>In`dLF^&}iB%1KuSB0xL*=9`Oyo@rLbIY_jNfJ{Cd zi``AGp89jhut|)g8)e@bWyhZ|tgeU(bP(p#fyx3=lOKea-(6QR(d^dY?%?tx3_ohE zp90|k-SyFUQx2)V=0yFa2Xn3%mJ{LS@1b7K^qcSgDSg4nJz3wk>wg z&NjY0p~JZYiHQEUl>d)3k;NQluDeFV4~qLPr3|sLtcuEFbR*l5f`oy5Ep*rDx^G<@ ztrWdl5=f+{4HKzzVjgkZCc8RPnFopr4oDMTn7mzwoT6fj93o1YR;Qe<8=UxLh8c>Y z1&#Y3IPguq{soYQuy^XSwP2G?{hE z4iDUvZnlU(O5qi-D;@6V0a|cGDmF!4X$*78og=XuR51Sfh{mr|==SttLa4%n8}9HV zY+^9=q8zvtVw9^KCGW58f=I7vKY1lmYA9Sk=WvYQD)frSQQj8osI-3kP%ic_;*Pu_ z`dfGEmP<&-8W6S* z%o78x#DG5?s|;ec-FLwk6_wf>4C13gA_7K76D2+;4rA4@bat%*dq%-7sr}C9#uVv2L8*@R z=LOl69w6(ve5VaWwPbB#V*j<)WM_QjB5NZff{}xKDfm+M3IY971v7?q94duX{^mt~ zOh@#j^$26U@x?;!iIycBA%GRO(sh+b=wGiwY&n?XRsZVjIi-=7mSNZ&JeJ`n^Fg-* z`$1JJ9o9N9D9unzh*ofmV4}d+B%nkcFxYX6745{6%5#&yYLz^%p_(6DA8*Kxv~v4!hru>w`L+G|d+WK(Fj$y{ zV<;3i-!;2$HZ@XtCG+6ykh7A#KjVD4vE*eag(BRS?u1_;=mnmq4+HU_liqhPBlz58 z6mvkB6$;C$$v$X$9a@$faxblM#dV|P7Dru1d%;VLVI5zDKSj>b6I>xSw+F-+%4t7C zWSo}a#{=NBn;-GF8yHST_`X0CS`-l&CE~=M;tL0)T>}QV4alHuqpZyTBs~8{i_))o z&11$r6$9Mn@MBWp%!TCX5i2ZABKGuVUe2%NS67?s<_c?K$S8S0mZS=u&u<~RpMRqs z|0$j)cQb*g?%+`KfiS&5$uEk{ZiO&2d4shSJDi?oFI7F{Z8eiOgTcBhjlbz0&cP?o+=rjt=PBoI#_w~hZ zg9XAR(Ujr7MCz35iMz ziAmfQ6O|JelarJc5EYdZ6=em*C;lG?S9jZ|_5uHN5S5h^my(mb{V#|8p*)&P2bRA^ tFm<>0_OtP{1C(vuAKP(iyV^L|nb_Ib1|a+F6fQdf+Uf>sl`7T|{{zsr!pr~w literal 4848 zcmY*d2{_bW_rLQs!wkugsL0r|B+O5XHO7*iL4``j(xPlr_Wer<$r6RKWGnl=uQMeQ zQDk4DGTE18XL$Yo@B4q3`#kq^&bjw_?sM-w_k8ZTH_XUTi%cQJfkT9hFMNTGr<6>F?#_uIJ|Z+^*cNQt|esUKo=cd+-z(3xtEXvMtV;Wsgp_iVUSew zVmgwt!ojSGSPQ973~}$&As900fp0*M`Dq@7gXIV5!3i$dth5k$%L-vJ6GS?`B1>K7 z-B5;|MahhQa7`3cEn>%*0YD@_D6|4dK-hQ7e;^@36#wEhf6JugLEzdJ2 z{nlcIzO10BJXSSN4V30XT$KqNgwJ0F8PQDF>aK(+;|SgGr%E>1m^!D006PGn0sKFJ z7yV*cWbpPWBeM|bxQd7&L{WkYMM60>z{i<@M);jmL{O)IF>Bdt5o4*!yjT~7c+n+k zgyWC^j^xcUaUF1K{}sk8Ow`iXy{|ugD{w<1T8S1NMNB`co`#J6w4TO)_`p3kiiHES z!=wQpGTlPuFBYw?^ZI5w)9h2+`%|hQ^9vD zuit-A<1u|F%=ObZ(Gy9QGQ%NJhnIDiF7p%Nhoe9!0Z5_7Etx{}3{ntd+R>DT-64n6 z6>-5u5q|4J-T`EF`Oh#dzA!r$?l5TQ82bx)Y1Q~I%jo)J!LHC|p$kVh6Mt4puCpYw z&T$#RrxkD254ehxe@wwm+SO_YI}Sla{{2wk|2rQ33DgScItkL|703Lvk9(cPU`pB; z?aHo+(ga}=y}u-4^Tzbg)&?nP7P4dqcUMYxO4BVdgrL!Pj-8quwLu{XGHT!CqX}=H zo*0@YVii8!ddF$t62KsP7CKx>Hk@)jFbv&o^z_R>jn2mW>19g+qvSUd2o7sGbCW55 z0dbCscQVbfA;Hm;`XTDjMbR&!HYs?6hYo*`0gy&IM!=YigGE8V2CtEs5ubU1h~QF} z3|+okJU;E(rvb-@dpyC9riJ;)hp?CL|q%zTOEYtg2r1A2!Q>Y z`Th;Hp_8(lio#H967NLA!^bXY((|p`>p|K_5fk>N4tzzP@M^#_+KGM=V533BRPlN>N?lRg|&~ed1Fl8L!l{ENjf=ITLw8_`MoD} zNx3WS%Zc*`SNi27UuQkCPoP<}h4Oh=QPgf1S*2%{w-uk;_~X7uVzH>XR@ATOtZ;I~ zyk}V2#*mM_3O`#O+jy;q<@Ij;?j)x`LYrjuxUWf8Ie{j8gv5vWK zEkNyC@(e%r{wf^N!tQ60zWDe}3W|K4I$EzddR(gK=;F=lEahnoIS0F}8{Vo7i$#Mh z1!!T%8MSgi5*>2{(|wA-Ly3~9&-1Ul3QQ)x2ITQok23MABVLAYWZzpAl0p6w8`fZhz+y!`Z0JHu_zYZ_i&ku|1zg}{3}-js^Nkb zV*7cz*@f~`vtB-&#dGKPx|<+vG%3bv1uK8?ikbDjG1%jQZKXiB=o)7da~M00der$70gyDYh3zO_ofe$Ex?6{&)! zmCYRN;U!;UES3ho$rZawPP{gCMS|Hu_p`>HWQKt%x7FOr?K1TKr{&u#dx?jKVSqA(0&wL{gBWhwt2qFKI1vtC1R<>zf$E$O+R1ZTwZ%RNOmpBtAb{+gp$u5szX-|KT5vF?W5$6KHg>r>R^A&B=5jYls( z!wzIug!S866oA#ZyX8EUVX8M`O1 z*eDT2IaV2sullaId0w-SNU`@}sbAEFovNiO&k3BHwdF+(c>nf7dlZ;w*2!ZKCXe2_ zudY|Y#q0!Wm5+D#IzFMlWm3ABj&I&uTrgyM?tEa?@*A?8CNjN#Qw!ghO|jdS_Qg=y zpzz_);K|v;4v~m&i=0b-LI}afNr|DkH><|3hbr0hNMmX~#2xS9`?A*-Id*P)g`f_n z&qo;`$FCVFRt70&(<78dp7_<_;KV-1co!eW;5OO(ts3j`NT@_$)k%{2)ADWxDyHxA ziFyaKhghxX0Xl;j{$Dp4yq~JzCGuG#^OL2eNP_Ps|HPf`^N_?8pONw=yG_phG+t(3 z_S)PWk-Mt=RWj13)$9{pu!(ru=$H5$+e01sizinXjdmt2xqhU78l)SqZGW9h96a>$ z*qu8mpNsqCO8X4|2ppK@7-I8Gi!*H~F@ne5_$fgSZQEDxCZBBn?3LqRr?R6lJF{gR zfxGLMHklh8@1`vAfm|(b(nfjmQbD3@d(-G>mqEL^)SW`2q~p3$Ah+ARm(U1)?JmY> zvvi+>i^PjFf@*eW(Mt^8*LtU_%7_ZV`8npI4P9G3!bojNiFo%vsjfVh+*}3n;eve% zWnaA&o}_@PnP!(`P=33OQh(>hcQm&0d;N{+-tJPFA{|8RmTk*m!|9o6y`jRCYnyaITT13C>}VTi7f|_b^uXnM6Lt7c=~SQPY5yCiuV!}n zmKxsf{yLRp9FR-m&sj=-%*L6jKk6-7u(`EqQICa2#P)nLc1I2T`pieXZHmE~-K^wI zgwsvo)~vNv^wJxt+VBU$`wZ3S*h;$6OX<>7?BtOvv7}q*!M^rSgixmupB#6>-pwyB zhCKp3wG8XZ1j}uSKh9IgM&o2^IKw=0Xd8^VZ-F9(J6}t`B~B`h<%O?n_^i70GQK)0 zW;EKxs;(tHFInT9CB@Y%8!$W{;)AnF)>%9mtx*3cFhrTTwRgAe$l~M=hsoTHMj55S z+@0h0;)I|j`QEhYg`*?*=)3J?n{xIwWsuW$ok{@lHG3h`Oxbtoc_}zTrAlhS_EqyZ z*^^^Ikn?Ky?#VyU&U+Kx%x9$4(?g{fR#lV?D>MJaR-ZiAur8kghX z>RpvpE~%$|%oexyC}%7eC~6`4XS#c)@D}H$0^~11_4&4j*LjxI^kDAJNd7ySp+TjasDOa5>d=^_o?iz4uy zDpDk<`^YyRG~!o>J%yzdW5)%pS|alvcbL3XK)ktwcqBy4l%zh}2HjUMW%CUBKAik7 zI{%tA26z`00RB50$7?}L(y|O9x^RMj9-91SE$ytLN#`jaN2osz^y*!3K@}Y+x@LYW zrYKnY*;K7wrGEAu2YD)%e~jr*)Cq)OlY6W2{OqJSVQzgzmj*uvC0EOn0~9WH!Tl{& z7rD3XNJmH?IgSRs&a}*J6br!JS!+@$b*i@f`0*sE=Y!2%so{H==!LZ?={u=;s_pmh z-7GPqU(|wl`?Y(zE?cEXW-34Mp3eJeo}5J_C4D8bp(P1{{Vm5VR}Xhe4hzg;mQ-2% z4k;J%T5SRD2*h|xahM)Ev*b6k5_e&s5XfW*->mU!f00YyGPHD&dEd5_k{4!>@G=%> zb;aq>b7yRDd~?&`1Ao)_KcAhS0>Q{OP@|2AT(oWf;NR%i&VZpdu<>8&bUZCv={q+$ zT%{BZUfHcNzFA}!e`+AOD+zOT4Yu#vMBtqmwRhMfY7aGrt8x)EAY466Y`0jQr z7EZf)4K9Dpl8q;7P)tMfX|a2g3r68Rf*tb-PSLSu^9zUGz(Op6bDLTaDtN+Kl;F9k z(Lixpu9IBF@gvT;o#_ez-HW#3Vmq5Rx!(6fBa)QS_?(c@6eT*IM!+NV!^=Wq z9)NxraB9G#)+Sc_BD)jO#uE&hqYU1<*CZ2qwkJ+jv7=!W_`(G6%wU}9KR*-y)I>C4 zWF!0?gKcaU%JB>NDq$P|t+E=xnM!L0pwL~N9$N{Lywi5io5Q2-b9GG-;8#VR!YW6e z{|ch;1xnkeI7fv&8)s^DpDXo+N{@;n3t0W^%Q^mLzyB7E=}QjIbR0-h&9i$(=`8$Q e`)31|-DTSp_6-(eIx-LTi_Qf@jY2i6pnm{5M6z`N diff --git a/css/jquery-ui.min.css b/css/jquery-ui.min.css index b0a15796..8a59b9db 100644 --- a/css/jquery-ui.min.css +++ b/css/jquery-ui.min.css @@ -1,7 +1,7 @@ -/*! jQuery UI - v1.10.4 - 2018-06-04 +/*! jQuery UI - v1.12.1 - 2018-06-17 * http://jqueryui.com -* Includes: jquery.ui.core.css, jquery.ui.resizable.css, jquery.ui.selectable.css, jquery.ui.accordion.css, jquery.ui.autocomplete.css, jquery.ui.button.css, jquery.ui.datepicker.css, jquery.ui.dialog.css, jquery.ui.menu.css, jquery.ui.progressbar.css, jquery.ui.slider.css, jquery.ui.spinner.css, jquery.ui.tabs.css, jquery.ui.tooltip.css, jquery.ui.theme.css -* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana%2CArial%2Csans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=highlight_soft&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=flat&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=glass&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=glass&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=glass&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=glass&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px +* Includes: draggable.css, core.css, resizable.css, selectable.css, sortable.css, accordion.css, autocomplete.css, menu.css, button.css, controlgroup.css, checkboxradio.css, datepicker.css, dialog.css, progressbar.css, selectmenu.css, slider.css, spinner.css, tabs.css, tooltip.css, theme.css +* To view and modify this theme, visit http://jqueryui.com/themeroller/?scope=&folderName=base&cornerRadiusShadow=8px&offsetLeftShadow=0px&offsetTopShadow=0px&thicknessShadow=5px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=666666&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=aaaaaa&iconColorError=cc0000&fcError=5f3f3f&borderColorError=f1a899&bgTextureError=flat&bgColorError=fddfdf&iconColorHighlight=777620&fcHighlight=777620&borderColorHighlight=dad55e&bgTextureHighlight=flat&bgColorHighlight=fffa90&iconColorActive=ffffff&fcActive=ffffff&borderColorActive=003eff&bgTextureActive=flat&bgColorActive=007fff&iconColorHover=555555&fcHover=2b2b2b&borderColorHover=cccccc&bgTextureHover=flat&bgColorHover=ededed&iconColorDefault=777777&fcDefault=454545&borderColorDefault=c5c5c5&bgTextureDefault=flat&bgColorDefault=f6f6f6&iconColorContent=444444&fcContent=333333&borderColorContent=dddddd&bgTextureContent=flat&bgColorContent=ffffff&iconColorHeader=444444&fcHeader=333333&borderColorHeader=dddddd&bgTextureHeader=flat&bgColorHeader=e9e9e9&cornerRadius=3px&fwDefault=normal&fsDefault=1em&ffDefault=Arial%2CHelvetica%2Csans-serif * Copyright jQuery Foundation and other contributors; Licensed MIT */ -.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{min-height:0}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin-top:2px;padding:.5em .5em .5em .7em;min-height:0}.ui-accordion .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-noicons{padding-left:.7em}.ui-accordion .ui-accordion-icons .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-header .ui-accordion-header-icon{position:absolute;left:.5em;top:50%;margin-top:-8px}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;overflow:auto}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-button{display:inline-block;position:relative;padding:0;line-height:normal;margin-right:.1em;cursor:pointer;vertical-align:middle;text-align:center;overflow:visible}.ui-button,.ui-button:link,.ui-button:visited,.ui-button:hover,.ui-button:active{text-decoration:none}.ui-button-icon-only{width:2.2em}button.ui-button-icon-only{width:2.4em}.ui-button-icons-only{width:3.4em}button.ui-button-icons-only{width:3.7em}.ui-button .ui-button-text{display:block;line-height:normal}.ui-button-text-only .ui-button-text{padding:.4em 1em}.ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{padding:.4em;text-indent:-9999999px}.ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 1em .4em 2.1em}.ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 2.1em .4em 1em}.ui-button-text-icons .ui-button-text{padding-left:2.1em;padding-right:2.1em}input.ui-button{padding:.4em 1em}.ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{position:absolute;top:50%;margin-top:-8px}.ui-button-icon-only .ui-icon{left:50%;margin-left:-8px}.ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{left:.5em}.ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-buttonset{margin-right:7px}.ui-buttonset .ui-button{margin-left:0;margin-right:-.3em}input.ui-button::-moz-focus-inner,button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:49%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-dialog{overflow:hidden;position:absolute;top:0;left:0;padding:.2em;outline:0}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 0;white-space:nowrap;width:90%;overflow:hidden;text-overflow:ellipsis}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:20px;margin:-10px 0 0 0;padding:1px;height:20px}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin-top:.5em;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-se{width:12px;height:12px;right:-5px;bottom:-5px;background-position:16px 16px}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-menu{list-style:none;padding:2px;margin:0;display:block;outline:none}.ui-menu .ui-menu{margin-top:-3px;position:absolute}.ui-menu .ui-menu-item{margin:0;padding:0;width:100%;list-style-image:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)}.ui-menu .ui-menu-divider{margin:5px -2px 5px -2px;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-menu-item a{text-decoration:none;display:block;padding:2px .4em;line-height:1.5;min-height:0;font-weight:normal}.ui-menu .ui-menu-item a.ui-state-focus,.ui-menu .ui-menu-item a.ui-state-active{font-weight:normal;margin:-1px}.ui-menu .ui-state-disabled{font-weight:normal;margin:.4em 0 .2em;line-height:1.5}.ui-menu .ui-state-disabled a{cursor:default}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item a{position:relative;padding-left:2em}.ui-menu .ui-icon{position:absolute;top:.2em;left:.2em}.ui-menu .ui-menu-icon{position:static;float:right}.ui-progressbar{height:2em;text-align:left;overflow:hidden}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-progressbar .ui-progressbar-overlay{background:url("images/animated-overlay.gif");height:100%;filter:alpha(opacity=25);opacity:0.25}.ui-progressbar-indeterminate .ui-progressbar-value{background-image:none}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider.ui-state-disabled .ui-slider-handle,.ui-slider.ui-state-disabled .ui-slider-range{filter:inherit}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-spinner{position:relative;display:inline-block;overflow:hidden;padding:0;vertical-align:middle}.ui-spinner-input{border:none;background:none;color:inherit;padding:0;margin:.2em 0;vertical-align:middle;margin-left:.4em;margin-right:22px}.ui-spinner-button{width:16px;height:50%;font-size:.5em;padding:0;margin:0;text-align:center;position:absolute;cursor:default;display:block;overflow:hidden;right:0}.ui-spinner a.ui-spinner-button{border-top:none;border-bottom:none;border-right:none}.ui-spinner .ui-icon{position:absolute;margin-top:-8px;top:50%;left:0}.ui-spinner-up{top:0}.ui-spinner-down{bottom:0}.ui-spinner .ui-icon-triangle-1-s{background-position:-65px -16px}.ui-tabs{position:relative;padding:.2em}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:0;margin:1px .2em 0 0;border-bottom-width:0;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav .ui-tabs-anchor{float:left;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-active{margin-bottom:-1px;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor{cursor:text}.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:none}.ui-tooltip{padding:8px;position:absolute;z-index:9999;max-width:300px;-webkit-box-shadow:0 0 5px #aaa;box-shadow:0 0 5px #aaa}body .ui-tooltip{border-width:2px}.ui-widget{font-family:Verdana,Arial,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Verdana,Arial,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #aaa;background:#fff;color:#222}.ui-widget-content a{color:#222}.ui-widget-header{border:1px solid #aaa;background:#ccc url("images/ui-bg_highlight-soft_75_cccccc_1x100.png") 50% 50% repeat-x;color:#222;font-weight:bold}.ui-widget-header a{color:#222}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #d3d3d3;background:#e6e6e6 url("images/ui-bg_glass_75_e6e6e6_1x400.png") 50% 50% repeat-x;font-weight:normal;color:#555}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#555;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #999;background:#dadada url("images/ui-bg_glass_75_dadada_1x400.png") 50% 50% repeat-x;font-weight:normal;color:#212121}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited{color:#212121;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #aaa;background:#fff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x;font-weight:normal;color:#212121}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#212121;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #fcefa1;background:#fbf9ee url("images/ui-bg_glass_55_fbf9ee_1x400.png") 50% 50% repeat-x;color:#363636}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#fef1ec url("images/ui-bg_glass_95_fef1ec_1x400.png") 50% 50% repeat-x;color:#cd0a0a}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#cd0a0a}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#cd0a0a}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_222222_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_222222_256x240.png")}.ui-state-default .ui-icon{background-image:url("images/ui-icons_888888_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url("images/ui-icons_454545_256x240.png")}.ui-state-active .ui-icon{background-image:url("images/ui-icons_454545_256x240.png")}.ui-state-highlight .ui-icon{background-image:url("images/ui-icons_2e83ff_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_cd0a0a_256x240.png")}.ui-icon-blank{background-position:16px 16px}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:4px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:4px}.ui-widget-overlay{background:#aaa;opacity:.3;filter:Alpha(Opacity=30)}.ui-widget-shadow{margin:-8px 0 0 -8px;padding:8px;background:#aaa;opacity:.3;filter:Alpha(Opacity=30);border-radius:8px} \ No newline at end of file +.ui-draggable-handle{-ms-touch-action:none;touch-action:none}.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important;pointer-events:none}.ui-icon{display:inline-block;vertical-align:middle;margin-top:-.25em;position:relative;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-icon-block{left:50%;margin-left:-8px;display:block}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block;-ms-touch-action:none;touch-action:none}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable{-ms-touch-action:none;touch-action:none}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-sortable-handle{-ms-touch-action:none;touch-action:none}.ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin:2px 0 0 0;padding:.5em .5em .5em .7em;font-size:100%}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;overflow:auto}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-menu{list-style:none;padding:0;margin:0;display:block;outline:0}.ui-menu .ui-menu{position:absolute}.ui-menu .ui-menu-item{margin:0;cursor:pointer;list-style-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")}.ui-menu .ui-menu-item-wrapper{position:relative;padding:3px 1em 3px .4em}.ui-menu .ui-menu-divider{margin:5px 0;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-state-focus,.ui-menu .ui-state-active{margin:-1px}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item-wrapper{padding-left:2em}.ui-menu .ui-icon{position:absolute;top:0;bottom:0;left:.2em;margin:auto 0}.ui-menu .ui-menu-icon{left:auto;right:0}.ui-button{padding:.4em 1em;display:inline-block;position:relative;line-height:normal;margin-right:.1em;cursor:pointer;vertical-align:middle;text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;overflow:visible}.ui-button,.ui-button:link,.ui-button:visited,.ui-button:hover,.ui-button:active{text-decoration:none}.ui-button-icon-only{width:2em;box-sizing:border-box;text-indent:-9999px;white-space:nowrap}input.ui-button.ui-button-icon-only{text-indent:0}.ui-button-icon-only .ui-icon{position:absolute;top:50%;left:50%;margin-top:-8px;margin-left:-8px}.ui-button.ui-icon-notext .ui-icon{padding:0;width:2.1em;height:2.1em;text-indent:-9999px;white-space:nowrap}input.ui-button.ui-icon-notext .ui-icon{width:auto;height:auto;text-indent:0;white-space:normal;padding:.4em 1em}input.ui-button::-moz-focus-inner,button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-controlgroup{vertical-align:middle;display:inline-block}.ui-controlgroup > .ui-controlgroup-item{float:left;margin-left:0;margin-right:0}.ui-controlgroup > .ui-controlgroup-item:focus,.ui-controlgroup > .ui-controlgroup-item.ui-visual-focus{z-index:9999}.ui-controlgroup-vertical > .ui-controlgroup-item{display:block;float:none;width:100%;margin-top:0;margin-bottom:0;text-align:left}.ui-controlgroup-vertical .ui-controlgroup-item{box-sizing:border-box}.ui-controlgroup .ui-controlgroup-label{padding:.4em 1em}.ui-controlgroup .ui-controlgroup-label span{font-size:80%}.ui-controlgroup-horizontal .ui-controlgroup-label + .ui-controlgroup-item{border-left:none}.ui-controlgroup-vertical .ui-controlgroup-label + .ui-controlgroup-item{border-top:none}.ui-controlgroup-horizontal .ui-controlgroup-label.ui-widget-content{border-right:none}.ui-controlgroup-vertical .ui-controlgroup-label.ui-widget-content{border-bottom:none}.ui-controlgroup-vertical .ui-spinner-input{width:75%;width:calc( 100% - 2.4em )}.ui-controlgroup-vertical .ui-spinner .ui-spinner-up{border-top-style:solid}.ui-checkboxradio-label .ui-icon-background{box-shadow:inset 1px 1px 1px #ccc;border-radius:.12em;border:none}.ui-checkboxradio-radio-label .ui-icon-background{width:16px;height:16px;border-radius:1em;overflow:visible;border:none}.ui-checkboxradio-radio-label.ui-checkboxradio-checked .ui-icon,.ui-checkboxradio-radio-label.ui-checkboxradio-checked:hover .ui-icon{background-image:none;width:8px;height:8px;border-width:4px;border-style:solid}.ui-checkboxradio-disabled{pointer-events:none}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:45%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-datepicker .ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat;left:.5em;top:.3em}.ui-dialog{position:absolute;top:0;left:0;padding:.2em;outline:0}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 0;white-space:nowrap;width:90%;overflow:hidden;text-overflow:ellipsis}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:20px;margin:-10px 0 0 0;padding:1px;height:20px}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin-top:.5em;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-n{height:2px;top:0}.ui-dialog .ui-resizable-e{width:2px;right:0}.ui-dialog .ui-resizable-s{height:2px;bottom:0}.ui-dialog .ui-resizable-w{width:2px;left:0}.ui-dialog .ui-resizable-se,.ui-dialog .ui-resizable-sw,.ui-dialog .ui-resizable-ne,.ui-dialog .ui-resizable-nw{width:7px;height:7px}.ui-dialog .ui-resizable-se{right:0;bottom:0}.ui-dialog .ui-resizable-sw{left:0;bottom:0}.ui-dialog .ui-resizable-ne{right:0;top:0}.ui-dialog .ui-resizable-nw{left:0;top:0}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-progressbar{height:2em;text-align:left;overflow:hidden}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-progressbar .ui-progressbar-overlay{background:url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw==");height:100%;filter:alpha(opacity=25);opacity:0.25}.ui-progressbar-indeterminate .ui-progressbar-value{background-image:none}.ui-selectmenu-menu{padding:0;margin:0;position:absolute;top:0;left:0;display:none}.ui-selectmenu-menu .ui-menu{overflow:auto;overflow-x:hidden;padding-bottom:1px}.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup{font-size:1em;font-weight:bold;line-height:1.5;padding:2px 0.4em;margin:0.5em 0 0 0;height:auto;border:0}.ui-selectmenu-open{display:block}.ui-selectmenu-text{display:block;margin-right:20px;overflow:hidden;text-overflow:ellipsis}.ui-selectmenu-button.ui-button{text-align:left;white-space:nowrap;width:14em}.ui-selectmenu-icon.ui-icon{float:right;margin-top:0}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default;-ms-touch-action:none;touch-action:none}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider.ui-state-disabled .ui-slider-handle,.ui-slider.ui-state-disabled .ui-slider-range{filter:inherit}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-spinner{position:relative;display:inline-block;overflow:hidden;padding:0;vertical-align:middle}.ui-spinner-input{border:none;background:none;color:inherit;padding:.222em 0;margin:.2em 0;vertical-align:middle;margin-left:.4em;margin-right:2em}.ui-spinner-button{width:1.6em;height:50%;font-size:.5em;padding:0;margin:0;text-align:center;position:absolute;cursor:default;display:block;overflow:hidden;right:0}.ui-spinner a.ui-spinner-button{border-top-style:none;border-bottom-style:none;border-right-style:none}.ui-spinner-up{top:0}.ui-spinner-down{bottom:0}.ui-tabs{position:relative;padding:.2em}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:0;margin:1px .2em 0 0;border-bottom-width:0;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav .ui-tabs-anchor{float:left;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-active{margin-bottom:-1px;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor{cursor:text}.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:none}.ui-tooltip{padding:8px;position:absolute;z-index:9999;max-width:300px}body .ui-tooltip{border-width:2px}.ui-widget{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget.ui-widget-content{border:1px solid #c5c5c5}.ui-widget-content{border:1px solid #ddd;background:#fff;color:#333}.ui-widget-content a{color:#333}.ui-widget-header{border:1px solid #ddd;background:#e9e9e9;color:#333;font-weight:bold}.ui-widget-header a{color:#333}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default,.ui-button,html .ui-button.ui-state-disabled:hover,html .ui-button.ui-state-disabled:active{border:1px solid #c5c5c5;background:#f6f6f6;font-weight:normal;color:#454545}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited,a.ui-button,a:link.ui-button,a:visited.ui-button,.ui-button{color:#454545;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus,.ui-button:hover,.ui-button:focus{border:1px solid #ccc;background:#ededed;font-weight:normal;color:#2b2b2b}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited,a.ui-button:hover,a.ui-button:focus{color:#2b2b2b;text-decoration:none}.ui-visual-focus{box-shadow:0 0 3px 1px rgb(94,158,214)}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active,a.ui-button:active,.ui-button:active,.ui-button.ui-state-active:hover{border:1px solid #003eff;background:#007fff;font-weight:normal;color:#fff}.ui-icon-background,.ui-state-active .ui-icon-background{border:#003eff;background-color:#fff}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#fff;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #dad55e;background:#fffa90;color:#777620}.ui-state-checked{border:1px solid #dad55e;background:#fffa90}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#777620}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #f1a899;background:#fddfdf;color:#5f3f3f}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#5f3f3f}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#5f3f3f}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_444444_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_444444_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon,.ui-button:hover .ui-icon,.ui-button:focus .ui-icon{background-image:url("images/ui-icons_555555_256x240.png")}.ui-state-active .ui-icon,.ui-button:active .ui-icon{background-image:url("images/ui-icons_ffffff_256x240.png")}.ui-state-highlight .ui-icon,.ui-button .ui-state-highlight.ui-icon{background-image:url("images/ui-icons_777620_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_cc0000_256x240.png")}.ui-button .ui-icon{background-image:url("images/ui-icons_777777_256x240.png")}.ui-icon-blank{background-position:16px 16px}.ui-icon-caret-1-n{background-position:0 0}.ui-icon-caret-1-ne{background-position:-16px 0}.ui-icon-caret-1-e{background-position:-32px 0}.ui-icon-caret-1-se{background-position:-48px 0}.ui-icon-caret-1-s{background-position:-65px 0}.ui-icon-caret-1-sw{background-position:-80px 0}.ui-icon-caret-1-w{background-position:-96px 0}.ui-icon-caret-1-nw{background-position:-112px 0}.ui-icon-caret-2-n-s{background-position:-128px 0}.ui-icon-caret-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-65px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-65px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:1px -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:3px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:3px}.ui-widget-overlay{background:#aaa;opacity:.3;filter:Alpha(Opacity=30)}.ui-widget-shadow{-webkit-box-shadow:0 0 5px #666;box-shadow:0 0 5px #666} \ No newline at end of file diff --git a/js/jquery-ui.min.js b/js/jquery-ui.min.js index 16ae3381..678a0ef3 100644 --- a/js/jquery-ui.min.js +++ b/js/jquery-ui.min.js @@ -1,6 +1,13 @@ -/*! jQuery UI - v1.10.4 - 2014-02-19 +/*! jQuery UI - v1.12.1 - 2018-06-17 * http://jqueryui.com -* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.position.js, jquery.ui.draggable.js, jquery.ui.resizable.js, jquery.ui.sortable.js, jquery.ui.button.js, jquery.ui.dialog.js, jquery.ui.tooltip.js, jquery.ui.effect.js, jquery.ui.effect-scale.js, jquery.ui.effect-slide.js -* Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */ +* Includes: widget.js, position.js, data.js, disable-selection.js, focusable.js, form-reset-mixin.js, jquery-1-7.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/draggable.js, widgets/droppable.js, widgets/resizable.js, widgets/selectable.js, widgets/sortable.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/selectmenu.js, widgets/slider.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js +* Copyright jQuery Foundation and other contributors; Licensed MIT */ -(function(e,t){function i(t,i){var s,a,o,r=t.nodeName.toLowerCase();return"area"===r?(s=t.parentNode,a=s.name,t.href&&a&&"map"===s.nodeName.toLowerCase()?(o=e("img[usemap=#"+a+"]")[0],!!o&&n(o)):!1):(/input|select|textarea|button|object/.test(r)?!t.disabled:"a"===r?t.href||i:i)&&n(t)}function n(t){return e.expr.filters.visible(t)&&!e(t).parents().addBack().filter(function(){return"hidden"===e.css(this,"visibility")}).length}var s=0,a=/^ui-id-\d+$/;e.ui=e.ui||{},e.extend(e.ui,{version:"1.10.4",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({focus:function(t){return function(i,n){return"number"==typeof i?this.each(function(){var t=this;setTimeout(function(){e(t).focus(),n&&n.call(t)},i)}):t.apply(this,arguments)}}(e.fn.focus),scrollParent:function(){var t;return t=e.ui.ie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,"position"))&&/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0),/fixed/.test(this.css("position"))||!t.length?e(document):t},zIndex:function(i){if(i!==t)return this.css("zIndex",i);if(this.length)for(var n,s,a=e(this[0]);a.length&&a[0]!==document;){if(n=a.css("position"),("absolute"===n||"relative"===n||"fixed"===n)&&(s=parseInt(a.css("zIndex"),10),!isNaN(s)&&0!==s))return s;a=a.parent()}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++s)})},removeUniqueId:function(){return this.each(function(){a.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(i){return!!e.data(i,t)}}):function(t,i,n){return!!e.data(t,n[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,"tabindex")))},tabbable:function(t){var n=e.attr(t,"tabindex"),s=isNaN(n);return(s||n>=0)&&i(t,!s)}}),e("").outerWidth(1).jquery||e.each(["Width","Height"],function(i,n){function s(t,i,n,s){return e.each(a,function(){i-=parseFloat(e.css(t,"padding"+this))||0,n&&(i-=parseFloat(e.css(t,"border"+this+"Width"))||0),s&&(i-=parseFloat(e.css(t,"margin"+this))||0)}),i}var a="Width"===n?["Left","Right"]:["Top","Bottom"],o=n.toLowerCase(),r={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+n]=function(i){return i===t?r["inner"+n].call(this):this.each(function(){e(this).css(o,s(this,i)+"px")})},e.fn["outer"+n]=function(t,i){return"number"!=typeof t?r["outer"+n].call(this,t):this.each(function(){e(this).css(o,s(this,t,!0,i)+"px")})}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e("").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(i){return arguments.length?t.call(this,e.camelCase(i)):t.call(this)}}(e.fn.removeData)),e.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),e.support.selectstart="onselectstart"in document.createElement("div"),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),e.extend(e.ui,{plugin:{add:function(t,i,n){var s,a=e.ui[t].prototype;for(s in n)a.plugins[s]=a.plugins[s]||[],a.plugins[s].push([i,n[s]])},call:function(e,t,i){var n,s=e.plugins[t];if(s&&e.element[0].parentNode&&11!==e.element[0].parentNode.nodeType)for(n=0;s.length>n;n++)e.options[s[n][0]]&&s[n][1].apply(e.element,i)}},hasScroll:function(t,i){if("hidden"===e(t).css("overflow"))return!1;var n=i&&"left"===i?"scrollLeft":"scrollTop",s=!1;return t[n]>0?!0:(t[n]=1,s=t[n]>0,t[n]=0,s)}})})(jQuery);(function(t,e){var i=0,s=Array.prototype.slice,n=t.cleanData;t.cleanData=function(e){for(var i,s=0;null!=(i=e[s]);s++)try{t(i).triggerHandler("remove")}catch(o){}n(e)},t.widget=function(i,s,n){var o,a,r,h,l={},c=i.split(".")[0];i=i.split(".")[1],o=c+"-"+i,n||(n=s,s=t.Widget),t.expr[":"][o.toLowerCase()]=function(e){return!!t.data(e,o)},t[c]=t[c]||{},a=t[c][i],r=t[c][i]=function(t,i){return this._createWidget?(arguments.length&&this._createWidget(t,i),e):new r(t,i)},t.extend(r,a,{version:n.version,_proto:t.extend({},n),_childConstructors:[]}),h=new s,h.options=t.widget.extend({},h.options),t.each(n,function(i,n){return t.isFunction(n)?(l[i]=function(){var t=function(){return s.prototype[i].apply(this,arguments)},e=function(t){return s.prototype[i].apply(this,t)};return function(){var i,s=this._super,o=this._superApply;return this._super=t,this._superApply=e,i=n.apply(this,arguments),this._super=s,this._superApply=o,i}}(),e):(l[i]=n,e)}),r.prototype=t.widget.extend(h,{widgetEventPrefix:a?h.widgetEventPrefix||i:i},l,{constructor:r,namespace:c,widgetName:i,widgetFullName:o}),a?(t.each(a._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,r,i._proto)}),delete a._childConstructors):s._childConstructors.push(r),t.widget.bridge(i,r)},t.widget.extend=function(i){for(var n,o,a=s.call(arguments,1),r=0,h=a.length;h>r;r++)for(n in a[r])o=a[r][n],a[r].hasOwnProperty(n)&&o!==e&&(i[n]=t.isPlainObject(o)?t.isPlainObject(i[n])?t.widget.extend({},i[n],o):t.widget.extend({},o):o);return i},t.widget.bridge=function(i,n){var o=n.prototype.widgetFullName||i;t.fn[i]=function(a){var r="string"==typeof a,h=s.call(arguments,1),l=this;return a=!r&&h.length?t.widget.extend.apply(null,[a].concat(h)):a,r?this.each(function(){var s,n=t.data(this,o);return n?t.isFunction(n[a])&&"_"!==a.charAt(0)?(s=n[a].apply(n,h),s!==n&&s!==e?(l=s&&s.jquery?l.pushStack(s.get()):s,!1):e):t.error("no such method '"+a+"' for "+i+" widget instance"):t.error("cannot call methods on "+i+" prior to initialization; "+"attempted to call method '"+a+"'")}):this.each(function(){var e=t.data(this,o);e?e.option(a||{})._init():t.data(this,o,new n(a,this))}),l}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
    ",options:{disabled:!1,create:null},_createWidget:function(e,s){s=t(s||this.defaultElement||this)[0],this.element=t(s),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this.bindings=t(),this.hoverable=t(),this.focusable=t(),s!==this&&(t.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===s&&this.destroy()}}),this.document=t(s.style?s.ownerDocument:s.document||s),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:t.noop,_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(t.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:t.noop,widget:function(){return this.element},option:function(i,s){var n,o,a,r=i;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof i)if(r={},n=i.split("."),i=n.shift(),n.length){for(o=r[i]=t.widget.extend({},this.options[i]),a=0;n.length-1>a;a++)o[n[a]]=o[n[a]]||{},o=o[n[a]];if(i=n.pop(),1===arguments.length)return o[i]===e?null:o[i];o[i]=s}else{if(1===arguments.length)return this.options[i]===e?null:this.options[i];r[i]=s}return this._setOptions(r),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return this.options[t]=e,"disabled"===t&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!e).attr("aria-disabled",e),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(i,s,n){var o,a=this;"boolean"!=typeof i&&(n=s,s=i,i=!1),n?(s=o=t(s),this.bindings=this.bindings.add(s)):(n=s,s=this.element,o=this.widget()),t.each(n,function(n,r){function h(){return i||a.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof r?a[r]:r).apply(a,arguments):e}"string"!=typeof r&&(h.guid=r.guid=r.guid||h.guid||t.guid++);var l=n.match(/^(\w+)\s*(.*)$/),c=l[1]+a.eventNamespace,u=l[2];u?o.delegate(u,c,h):s.bind(c,h)})},_off:function(t,e){e=(e||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,t.unbind(e).undelegate(e)},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){t(e.currentTarget).addClass("ui-state-hover")},mouseleave:function(e){t(e.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){t(e.currentTarget).addClass("ui-state-focus")},focusout:function(e){t(e.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}})})(jQuery);(function(t){var e=!1;t(document).mouseup(function(){e=!1}),t.widget("ui.mouse",{version:"1.10.4",options:{cancel:"input,textarea,button,select,option",distance:1,delay:0},_mouseInit:function(){var e=this;this.element.bind("mousedown."+this.widgetName,function(t){return e._mouseDown(t)}).bind("click."+this.widgetName,function(i){return!0===t.data(i.target,e.widgetName+".preventClickEvent")?(t.removeData(i.target,e.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):undefined}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName),this._mouseMoveDelegate&&t(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(i){if(!e){this._mouseStarted&&this._mouseUp(i),this._mouseDownEvent=i;var s=this,n=1===i.which,a="string"==typeof this.options.cancel&&i.target.nodeName?t(i.target).closest(this.options.cancel).length:!1;return n&&!a&&this._mouseCapture(i)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){s.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(i)&&this._mouseDelayMet(i)&&(this._mouseStarted=this._mouseStart(i)!==!1,!this._mouseStarted)?(i.preventDefault(),!0):(!0===t.data(i.target,this.widgetName+".preventClickEvent")&&t.removeData(i.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(t){return s._mouseMove(t)},this._mouseUpDelegate=function(t){return s._mouseUp(t)},t(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),i.preventDefault(),e=!0,!0)):!0}},_mouseMove:function(e){return t.ui.ie&&(!document.documentMode||9>document.documentMode)&&!e.button?this._mouseUp(e):this._mouseStarted?(this._mouseDrag(e),e.preventDefault()):(this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,e)!==!1,this._mouseStarted?this._mouseDrag(e):this._mouseUp(e)),!this._mouseStarted)},_mouseUp:function(e){return t(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,e.target===this._mouseDownEvent.target&&t.data(e.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(e)),!1},_mouseDistanceMet:function(t){return Math.max(Math.abs(this._mouseDownEvent.pageX-t.pageX),Math.abs(this._mouseDownEvent.pageY-t.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}})})(jQuery);(function(t,e){function i(t,e,i){return[parseFloat(t[0])*(p.test(t[0])?e/100:1),parseFloat(t[1])*(p.test(t[1])?i/100:1)]}function s(e,i){return parseInt(t.css(e,i),10)||0}function n(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}t.ui=t.ui||{};var a,o=Math.max,r=Math.abs,l=Math.round,h=/left|center|right/,c=/top|center|bottom/,u=/[\+\-]\d+(\.[\d]+)?%?/,d=/^\w+/,p=/%$/,f=t.fn.position;t.position={scrollbarWidth:function(){if(a!==e)return a;var i,s,n=t("
    "),o=n.children()[0];return t("body").append(n),i=o.offsetWidth,n.css("overflow","scroll"),s=o.offsetWidth,i===s&&(s=n[0].clientWidth),n.remove(),a=i-s},getScrollInfo:function(e){var i=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),s=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),n="scroll"===i||"auto"===i&&e.widths?"left":i>0?"right":"center",vertical:0>a?"top":n>0?"bottom":"middle"};u>p&&p>r(i+s)&&(l.horizontal="center"),d>g&&g>r(n+a)&&(l.vertical="middle"),l.important=o(r(i),r(s))>o(r(n),r(a))?"horizontal":"vertical",e.using.call(this,t,l)}),c.offset(t.extend(M,{using:h}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=t.left-e.collisionPosition.marginLeft,l=n-r,h=r+e.collisionWidth-a-n;e.collisionWidth>a?l>0&&0>=h?(i=t.left+l+e.collisionWidth-a-n,t.left+=l-i):t.left=h>0&&0>=l?n:l>h?n+a-e.collisionWidth:n:l>0?t.left+=l:h>0?t.left-=h:t.left=o(t.left-r,t.left)},top:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollTop:s.offset.top,a=e.within.height,r=t.top-e.collisionPosition.marginTop,l=n-r,h=r+e.collisionHeight-a-n;e.collisionHeight>a?l>0&&0>=h?(i=t.top+l+e.collisionHeight-a-n,t.top+=l-i):t.top=h>0&&0>=l?n:l>h?n+a-e.collisionHeight:n:l>0?t.top+=l:h>0?t.top-=h:t.top=o(t.top-r,t.top)}},flip:{left:function(t,e){var i,s,n=e.within,a=n.offset.left+n.scrollLeft,o=n.width,l=n.isWindow?n.scrollLeft:n.offset.left,h=t.left-e.collisionPosition.marginLeft,c=h-l,u=h+e.collisionWidth-o-l,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];0>c?(i=t.left+d+p+f+e.collisionWidth-o-a,(0>i||r(c)>i)&&(t.left+=d+p+f)):u>0&&(s=t.left-e.collisionPosition.marginLeft+d+p+f-l,(s>0||u>r(s))&&(t.left+=d+p+f))},top:function(t,e){var i,s,n=e.within,a=n.offset.top+n.scrollTop,o=n.height,l=n.isWindow?n.scrollTop:n.offset.top,h=t.top-e.collisionPosition.marginTop,c=h-l,u=h+e.collisionHeight-o-l,d="top"===e.my[1],p=d?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,f="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];0>c?(s=t.top+p+f+g+e.collisionHeight-o-a,t.top+p+f+g>c&&(0>s||r(c)>s)&&(t.top+=p+f+g)):u>0&&(i=t.top-e.collisionPosition.marginTop+p+f+g-l,t.top+p+f+g>u&&(i>0||u>r(i))&&(t.top+=p+f+g))}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}},function(){var e,i,s,n,a,o=document.getElementsByTagName("body")[0],r=document.createElement("div");e=document.createElement(o?"div":"body"),s={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},o&&t.extend(s,{position:"absolute",left:"-1000px",top:"-1000px"});for(a in s)e.style[a]=s[a];e.appendChild(r),i=o||document.documentElement,i.insertBefore(e,i.firstChild),r.style.cssText="position: absolute; left: 10.7432222px;",n=t(r).offset().left,t.support.offsetFractions=n>10&&11>n,e.innerHTML="",i.removeChild(e)}()})(jQuery);(function(t){t.widget("ui.draggable",t.ui.mouse,{version:"1.10.4",widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1,drag:null,start:null,stop:null},_create:function(){"original"!==this.options.helper||/^(?:r|a|f)/.test(this.element.css("position"))||(this.element[0].style.position="relative"),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._mouseInit()},_destroy:function(){this.element.removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._mouseDestroy()},_mouseCapture:function(e){var i=this.options;return this.helper||i.disabled||t(e.target).closest(".ui-resizable-handle").length>0?!1:(this.handle=this._getHandle(e),this.handle?(t(i.iframeFix===!0?"iframe":i.iframeFix).each(function(){t("
    ").css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1e3}).css(t(this).offset()).appendTo("body")}),!0):!1)},_mouseStart:function(e){var i=this.options;return this.helper=this._createHelper(e),this.helper.addClass("ui-draggable-dragging"),this._cacheHelperProportions(),t.ui.ddmanager&&(t.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(),this.offsetParent=this.helper.offsetParent(),this.offsetParentCssPosition=this.offsetParent.css("position"),this.offset=this.positionAbs=this.element.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},this.offset.scroll=!1,t.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.originalPosition=this.position=this._generatePosition(e),this.originalPageX=e.pageX,this.originalPageY=e.pageY,i.cursorAt&&this._adjustOffsetFromHelper(i.cursorAt),this._setContainment(),this._trigger("start",e)===!1?(this._clear(),!1):(this._cacheHelperProportions(),t.ui.ddmanager&&!i.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this._mouseDrag(e,!0),t.ui.ddmanager&&t.ui.ddmanager.dragStart(this,e),!0)},_mouseDrag:function(e,i){if("fixed"===this.offsetParentCssPosition&&(this.offset.parent=this._getParentOffset()),this.position=this._generatePosition(e),this.positionAbs=this._convertPositionTo("absolute"),!i){var s=this._uiHash();if(this._trigger("drag",e,s)===!1)return this._mouseUp({}),!1;this.position=s.position}return this.options.axis&&"y"===this.options.axis||(this.helper[0].style.left=this.position.left+"px"),this.options.axis&&"x"===this.options.axis||(this.helper[0].style.top=this.position.top+"px"),t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),!1},_mouseStop:function(e){var i=this,s=!1;return t.ui.ddmanager&&!this.options.dropBehaviour&&(s=t.ui.ddmanager.drop(this,e)),this.dropped&&(s=this.dropped,this.dropped=!1),"original"!==this.options.helper||t.contains(this.element[0].ownerDocument,this.element[0])?("invalid"===this.options.revert&&!s||"valid"===this.options.revert&&s||this.options.revert===!0||t.isFunction(this.options.revert)&&this.options.revert.call(this.element,s)?t(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){i._trigger("stop",e)!==!1&&i._clear()}):this._trigger("stop",e)!==!1&&this._clear(),!1):!1},_mouseUp:function(e){return t("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)}),t.ui.ddmanager&&t.ui.ddmanager.dragStop(this,e),t.ui.mouse.prototype._mouseUp.call(this,e)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear(),this},_getHandle:function(e){return this.options.handle?!!t(e.target).closest(this.element.find(this.options.handle)).length:!0},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper)?t(i.helper.apply(this.element[0],[e])):"clone"===i.helper?this.element.clone().removeAttr("id"):this.element;return s.parents("body").length||s.appendTo("parent"===i.appendTo?this.element[0].parentNode:i.appendTo),s[0]===this.element[0]||/(fixed|absolute)/.test(s.css("position"))||s.css("position","absolute"),s},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_getParentOffset:function(){var e=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===document.body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&t.ui.ie)&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var t=this.element.position();return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:t.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options;return n.containment?"window"===n.containment?(this.containment=[t(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,t(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,t(window).scrollLeft()+t(window).width()-this.helperProportions.width-this.margins.left,t(window).scrollTop()+(t(window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],undefined):"document"===n.containment?(this.containment=[0,0,t(document).width()-this.helperProportions.width-this.margins.left,(t(document).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],undefined):n.containment.constructor===Array?(this.containment=n.containment,undefined):("parent"===n.containment&&(n.containment=this.helper[0].parentNode),i=t(n.containment),s=i[0],s&&(e="hidden"!==i.css("overflow"),this.containment=[(parseInt(i.css("borderLeftWidth"),10)||0)+(parseInt(i.css("paddingLeft"),10)||0),(parseInt(i.css("borderTopWidth"),10)||0)+(parseInt(i.css("paddingTop"),10)||0),(e?Math.max(s.scrollWidth,s.offsetWidth):s.offsetWidth)-(parseInt(i.css("borderRightWidth"),10)||0)-(parseInt(i.css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(e?Math.max(s.scrollHeight,s.offsetHeight):s.offsetHeight)-(parseInt(i.css("borderBottomWidth"),10)||0)-(parseInt(i.css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relative_container=i),undefined):(this.containment=null,undefined)},_convertPositionTo:function(e,i){i||(i=this.position);var s="absolute"===e?1:-1,n="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent;return this.offset.scroll||(this.offset.scroll={top:n.scrollTop(),left:n.scrollLeft()}),{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():this.offset.scroll.top)*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():this.offset.scroll.left)*s}},_generatePosition:function(e){var i,s,n,a,o=this.options,r="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,l=e.pageX,h=e.pageY;return this.offset.scroll||(this.offset.scroll={top:r.scrollTop(),left:r.scrollLeft()}),this.originalPosition&&(this.containment&&(this.relative_container?(s=this.relative_container.offset(),i=[this.containment[0]+s.left,this.containment[1]+s.top,this.containment[2]+s.left,this.containment[3]+s.top]):i=this.containment,e.pageX-this.offset.click.lefti[2]&&(l=i[2]+this.offset.click.left),e.pageY-this.offset.click.top>i[3]&&(h=i[3]+this.offset.click.top)),o.grid&&(n=o.grid[1]?this.originalPageY+Math.round((h-this.originalPageY)/o.grid[1])*o.grid[1]:this.originalPageY,h=i?n-this.offset.click.top>=i[1]||n-this.offset.click.top>i[3]?n:n-this.offset.click.top>=i[1]?n-o.grid[1]:n+o.grid[1]:n,a=o.grid[0]?this.originalPageX+Math.round((l-this.originalPageX)/o.grid[0])*o.grid[0]:this.originalPageX,l=i?a-this.offset.click.left>=i[0]||a-this.offset.click.left>i[2]?a:a-this.offset.click.left>=i[0]?a-o.grid[0]:a+o.grid[0]:a)),{top:h-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():this.offset.scroll.top),left:l-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():this.offset.scroll.left)}},_clear:function(){this.helper.removeClass("ui-draggable-dragging"),this.helper[0]===this.element[0]||this.cancelHelperRemoval||this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1},_trigger:function(e,i,s){return s=s||this._uiHash(),t.ui.plugin.call(this,e,[i,s]),"drag"===e&&(this.positionAbs=this._convertPositionTo("absolute")),t.Widget.prototype._trigger.call(this,e,i,s)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),t.ui.plugin.add("draggable","connectToSortable",{start:function(e,i){var s=t(this).data("ui-draggable"),n=s.options,a=t.extend({},i,{item:s.element});s.sortables=[],t(n.connectToSortable).each(function(){var i=t.data(this,"ui-sortable");i&&!i.options.disabled&&(s.sortables.push({instance:i,shouldRevert:i.options.revert}),i.refreshPositions(),i._trigger("activate",e,a))})},stop:function(e,i){var s=t(this).data("ui-draggable"),n=t.extend({},i,{item:s.element});t.each(s.sortables,function(){this.instance.isOver?(this.instance.isOver=0,s.cancelHelperRemoval=!0,this.instance.cancelHelperRemoval=!1,this.shouldRevert&&(this.instance.options.revert=this.shouldRevert),this.instance._mouseStop(e),this.instance.options.helper=this.instance.options._helper,"original"===s.options.helper&&this.instance.currentItem.css({top:"auto",left:"auto"})):(this.instance.cancelHelperRemoval=!1,this.instance._trigger("deactivate",e,n))})},drag:function(e,i){var s=t(this).data("ui-draggable"),n=this;t.each(s.sortables,function(){var a=!1,o=this;this.instance.positionAbs=s.positionAbs,this.instance.helperProportions=s.helperProportions,this.instance.offset.click=s.offset.click,this.instance._intersectsWith(this.instance.containerCache)&&(a=!0,t.each(s.sortables,function(){return this.instance.positionAbs=s.positionAbs,this.instance.helperProportions=s.helperProportions,this.instance.offset.click=s.offset.click,this!==o&&this.instance._intersectsWith(this.instance.containerCache)&&t.contains(o.instance.element[0],this.instance.element[0])&&(a=!1),a})),a?(this.instance.isOver||(this.instance.isOver=1,this.instance.currentItem=t(n).clone().removeAttr("id").appendTo(this.instance.element).data("ui-sortable-item",!0),this.instance.options._helper=this.instance.options.helper,this.instance.options.helper=function(){return i.helper[0]},e.target=this.instance.currentItem[0],this.instance._mouseCapture(e,!0),this.instance._mouseStart(e,!0,!0),this.instance.offset.click.top=s.offset.click.top,this.instance.offset.click.left=s.offset.click.left,this.instance.offset.parent.left-=s.offset.parent.left-this.instance.offset.parent.left,this.instance.offset.parent.top-=s.offset.parent.top-this.instance.offset.parent.top,s._trigger("toSortable",e),s.dropped=this.instance.element,s.currentItem=s.element,this.instance.fromOutside=s),this.instance.currentItem&&this.instance._mouseDrag(e)):this.instance.isOver&&(this.instance.isOver=0,this.instance.cancelHelperRemoval=!0,this.instance.options.revert=!1,this.instance._trigger("out",e,this.instance._uiHash(this.instance)),this.instance._mouseStop(e,!0),this.instance.options.helper=this.instance.options._helper,this.instance.currentItem.remove(),this.instance.placeholder&&this.instance.placeholder.remove(),s._trigger("fromSortable",e),s.dropped=!1)})}}),t.ui.plugin.add("draggable","cursor",{start:function(){var e=t("body"),i=t(this).data("ui-draggable").options;e.css("cursor")&&(i._cursor=e.css("cursor")),e.css("cursor",i.cursor)},stop:function(){var e=t(this).data("ui-draggable").options;e._cursor&&t("body").css("cursor",e._cursor)}}),t.ui.plugin.add("draggable","opacity",{start:function(e,i){var s=t(i.helper),n=t(this).data("ui-draggable").options;s.css("opacity")&&(n._opacity=s.css("opacity")),s.css("opacity",n.opacity)},stop:function(e,i){var s=t(this).data("ui-draggable").options;s._opacity&&t(i.helper).css("opacity",s._opacity)}}),t.ui.plugin.add("draggable","scroll",{start:function(){var e=t(this).data("ui-draggable");e.scrollParent[0]!==document&&"HTML"!==e.scrollParent[0].tagName&&(e.overflowOffset=e.scrollParent.offset())},drag:function(e){var i=t(this).data("ui-draggable"),s=i.options,n=!1;i.scrollParent[0]!==document&&"HTML"!==i.scrollParent[0].tagName?(s.axis&&"x"===s.axis||(i.overflowOffset.top+i.scrollParent[0].offsetHeight-e.pageY=0;u--)r=p.snapElements[u].left,l=r+p.snapElements[u].width,h=p.snapElements[u].top,c=h+p.snapElements[u].height,r-f>_||m>l+f||h-f>b||v>c+f||!t.contains(p.snapElements[u].item.ownerDocument,p.snapElements[u].item)?(p.snapElements[u].snapping&&p.options.snap.release&&p.options.snap.release.call(p.element,e,t.extend(p._uiHash(),{snapItem:p.snapElements[u].item})),p.snapElements[u].snapping=!1):("inner"!==g.snapMode&&(s=f>=Math.abs(h-b),n=f>=Math.abs(c-v),a=f>=Math.abs(r-_),o=f>=Math.abs(l-m),s&&(i.position.top=p._convertPositionTo("relative",{top:h-p.helperProportions.height,left:0}).top-p.margins.top),n&&(i.position.top=p._convertPositionTo("relative",{top:c,left:0}).top-p.margins.top),a&&(i.position.left=p._convertPositionTo("relative",{top:0,left:r-p.helperProportions.width}).left-p.margins.left),o&&(i.position.left=p._convertPositionTo("relative",{top:0,left:l}).left-p.margins.left)),d=s||n||a||o,"outer"!==g.snapMode&&(s=f>=Math.abs(h-v),n=f>=Math.abs(c-b),a=f>=Math.abs(r-m),o=f>=Math.abs(l-_),s&&(i.position.top=p._convertPositionTo("relative",{top:h,left:0}).top-p.margins.top),n&&(i.position.top=p._convertPositionTo("relative",{top:c-p.helperProportions.height,left:0}).top-p.margins.top),a&&(i.position.left=p._convertPositionTo("relative",{top:0,left:r}).left-p.margins.left),o&&(i.position.left=p._convertPositionTo("relative",{top:0,left:l-p.helperProportions.width}).left-p.margins.left)),!p.snapElements[u].snapping&&(s||n||a||o||d)&&p.options.snap.snap&&p.options.snap.snap.call(p.element,e,t.extend(p._uiHash(),{snapItem:p.snapElements[u].item})),p.snapElements[u].snapping=s||n||a||o||d)}}),t.ui.plugin.add("draggable","stack",{start:function(){var e,i=this.data("ui-draggable").options,s=t.makeArray(t(i.stack)).sort(function(e,i){return(parseInt(t(e).css("zIndex"),10)||0)-(parseInt(t(i).css("zIndex"),10)||0)});s.length&&(e=parseInt(t(s[0]).css("zIndex"),10)||0,t(s).each(function(i){t(this).css("zIndex",e+i)}),this.css("zIndex",e+s.length))}}),t.ui.plugin.add("draggable","zIndex",{start:function(e,i){var s=t(i.helper),n=t(this).data("ui-draggable").options;s.css("zIndex")&&(n._zIndex=s.css("zIndex")),s.css("zIndex",n.zIndex)},stop:function(e,i){var s=t(this).data("ui-draggable").options;s._zIndex&&t(i.helper).css("zIndex",s._zIndex)}})})(jQuery);(function(t){function e(t){return parseInt(t,10)||0}function i(t){return!isNaN(parseInt(t,10))}t.widget("ui.resizable",t.ui.mouse,{version:"1.10.4",widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:90,resize:null,start:null,stop:null},_create:function(){var e,i,s,n,a,o=this,r=this.options;if(this.element.addClass("ui-resizable"),t.extend(this,{_aspectRatio:!!r.aspectRatio,aspectRatio:r.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:r.helper||r.ghost||r.animate?r.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)&&(this.element.wrap(t("
    ").css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("ui-resizable",this.element.data("ui-resizable")),this.elementIsWrapper=!0,this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")}),this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0}),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css({margin:this.originalElement.css("margin")}),this._proportionallyResize()),this.handles=r.handles||(t(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se"),this.handles.constructor===String)for("all"===this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw"),e=this.handles.split(","),this.handles={},i=0;e.length>i;i++)s=t.trim(e[i]),a="ui-resizable-"+s,n=t("
    "),n.css({zIndex:r.zIndex}),"se"===s&&n.addClass("ui-icon ui-icon-gripsmall-diagonal-se"),this.handles[s]=".ui-resizable-"+s,this.element.append(n);this._renderAxis=function(e){var i,s,n,a;e=e||this.element;for(i in this.handles)this.handles[i].constructor===String&&(this.handles[i]=t(this.handles[i],this.element).show()),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)&&(s=t(this.handles[i],this.element),a=/sw|ne|nw|se|n|s/.test(i)?s.outerHeight():s.outerWidth(),n=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join(""),e.css(n,a),this._proportionallyResize()),t(this.handles[i]).length},this._renderAxis(this.element),this._handles=t(".ui-resizable-handle",this.element).disableSelection(),this._handles.mouseover(function(){o.resizing||(this.className&&(n=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),o.axis=n&&n[1]?n[1]:"se")}),r.autoHide&&(this._handles.hide(),t(this.element).addClass("ui-resizable-autohide").mouseenter(function(){r.disabled||(t(this).removeClass("ui-resizable-autohide"),o._handles.show())}).mouseleave(function(){r.disabled||o.resizing||(t(this).addClass("ui-resizable-autohide"),o._handles.hide())})),this._mouseInit()},_destroy:function(){this._mouseDestroy();var e,i=function(e){t(e).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};return this.elementIsWrapper&&(i(this.element),e=this.element,this.originalElement.css({position:e.css("position"),width:e.outerWidth(),height:e.outerHeight(),top:e.css("top"),left:e.css("left")}).insertAfter(e),e.remove()),this.originalElement.css("resize",this.originalResizeStyle),i(this.originalElement),this},_mouseCapture:function(e){var i,s,n=!1;for(i in this.handles)s=t(this.handles[i])[0],(s===e.target||t.contains(s,e.target))&&(n=!0);return!this.options.disabled&&n},_mouseStart:function(i){var s,n,a,o=this.options,r=this.element.position(),h=this.element;return this.resizing=!0,/absolute/.test(h.css("position"))?h.css({position:"absolute",top:h.css("top"),left:h.css("left")}):h.is(".ui-draggable")&&h.css({position:"absolute",top:r.top,left:r.left}),this._renderProxy(),s=e(this.helper.css("left")),n=e(this.helper.css("top")),o.containment&&(s+=t(o.containment).scrollLeft()||0,n+=t(o.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:s,top:n},this.size=this._helper?{width:this.helper.width(),height:this.helper.height()}:{width:h.width(),height:h.height()},this.originalSize=this._helper?{width:h.outerWidth(),height:h.outerHeight()}:{width:h.width(),height:h.height()},this.originalPosition={left:s,top:n},this.sizeDiff={width:h.outerWidth()-h.width(),height:h.outerHeight()-h.height()},this.originalMousePosition={left:i.pageX,top:i.pageY},this.aspectRatio="number"==typeof o.aspectRatio?o.aspectRatio:this.originalSize.width/this.originalSize.height||1,a=t(".ui-resizable-"+this.axis).css("cursor"),t("body").css("cursor","auto"===a?this.axis+"-resize":a),h.addClass("ui-resizable-resizing"),this._propagate("start",i),!0},_mouseDrag:function(e){var i,s=this.helper,n={},a=this.originalMousePosition,o=this.axis,r=this.position.top,h=this.position.left,l=this.size.width,c=this.size.height,u=e.pageX-a.left||0,d=e.pageY-a.top||0,p=this._change[o];return p?(i=p.apply(this,[e,u,d]),this._updateVirtualBoundaries(e.shiftKey),(this._aspectRatio||e.shiftKey)&&(i=this._updateRatio(i,e)),i=this._respectSize(i,e),this._updateCache(i),this._propagate("resize",e),this.position.top!==r&&(n.top=this.position.top+"px"),this.position.left!==h&&(n.left=this.position.left+"px"),this.size.width!==l&&(n.width=this.size.width+"px"),this.size.height!==c&&(n.height=this.size.height+"px"),s.css(n),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),t.isEmptyObject(n)||this._trigger("resize",e,this.ui()),!1):!1},_mouseStop:function(e){this.resizing=!1;var i,s,n,a,o,r,h,l=this.options,c=this;return this._helper&&(i=this._proportionallyResizeElements,s=i.length&&/textarea/i.test(i[0].nodeName),n=s&&t.ui.hasScroll(i[0],"left")?0:c.sizeDiff.height,a=s?0:c.sizeDiff.width,o={width:c.helper.width()-a,height:c.helper.height()-n},r=parseInt(c.element.css("left"),10)+(c.position.left-c.originalPosition.left)||null,h=parseInt(c.element.css("top"),10)+(c.position.top-c.originalPosition.top)||null,l.animate||this.element.css(t.extend(o,{top:h,left:r})),c.helper.height(c.size.height),c.helper.width(c.size.width),this._helper&&!l.animate&&this._proportionallyResize()),t("body").css("cursor","auto"),this.element.removeClass("ui-resizable-resizing"),this._propagate("stop",e),this._helper&&this.helper.remove(),!1},_updateVirtualBoundaries:function(t){var e,s,n,a,o,r=this.options;o={minWidth:i(r.minWidth)?r.minWidth:0,maxWidth:i(r.maxWidth)?r.maxWidth:1/0,minHeight:i(r.minHeight)?r.minHeight:0,maxHeight:i(r.maxHeight)?r.maxHeight:1/0},(this._aspectRatio||t)&&(e=o.minHeight*this.aspectRatio,n=o.minWidth/this.aspectRatio,s=o.maxHeight*this.aspectRatio,a=o.maxWidth/this.aspectRatio,e>o.minWidth&&(o.minWidth=e),n>o.minHeight&&(o.minHeight=n),o.maxWidth>s&&(o.maxWidth=s),o.maxHeight>a&&(o.maxHeight=a)),this._vBoundaries=o},_updateCache:function(t){this.offset=this.helper.offset(),i(t.left)&&(this.position.left=t.left),i(t.top)&&(this.position.top=t.top),i(t.height)&&(this.size.height=t.height),i(t.width)&&(this.size.width=t.width)},_updateRatio:function(t){var e=this.position,s=this.size,n=this.axis;return i(t.height)?t.width=t.height*this.aspectRatio:i(t.width)&&(t.height=t.width/this.aspectRatio),"sw"===n&&(t.left=e.left+(s.width-t.width),t.top=null),"nw"===n&&(t.top=e.top+(s.height-t.height),t.left=e.left+(s.width-t.width)),t},_respectSize:function(t){var e=this._vBoundaries,s=this.axis,n=i(t.width)&&e.maxWidth&&e.maxWidtht.width,r=i(t.height)&&e.minHeight&&e.minHeight>t.height,h=this.originalPosition.left+this.originalSize.width,l=this.position.top+this.size.height,c=/sw|nw|w/.test(s),u=/nw|ne|n/.test(s);return o&&(t.width=e.minWidth),r&&(t.height=e.minHeight),n&&(t.width=e.maxWidth),a&&(t.height=e.maxHeight),o&&c&&(t.left=h-e.minWidth),n&&c&&(t.left=h-e.maxWidth),r&&u&&(t.top=l-e.minHeight),a&&u&&(t.top=l-e.maxHeight),t.width||t.height||t.left||!t.top?t.width||t.height||t.top||!t.left||(t.left=null):t.top=null,t},_proportionallyResize:function(){if(this._proportionallyResizeElements.length){var t,e,i,s,n,a=this.helper||this.element;for(t=0;this._proportionallyResizeElements.length>t;t++){if(n=this._proportionallyResizeElements[t],!this.borderDif)for(this.borderDif=[],i=[n.css("borderTopWidth"),n.css("borderRightWidth"),n.css("borderBottomWidth"),n.css("borderLeftWidth")],s=[n.css("paddingTop"),n.css("paddingRight"),n.css("paddingBottom"),n.css("paddingLeft")],e=0;i.length>e;e++)this.borderDif[e]=(parseInt(i[e],10)||0)+(parseInt(s[e],10)||0);n.css({height:a.height()-this.borderDif[0]-this.borderDif[2]||0,width:a.width()-this.borderDif[1]-this.borderDif[3]||0})}}},_renderProxy:function(){var e=this.element,i=this.options;this.elementOffset=e.offset(),this._helper?(this.helper=this.helper||t("
    "),this.helper.addClass(this._helper).css({width:this.element.outerWidth()-1,height:this.element.outerHeight()-1,position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++i.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(t,e){return{width:this.originalSize.width+e}},w:function(t,e){var i=this.originalSize,s=this.originalPosition;return{left:s.left+e,width:i.width-e}},n:function(t,e,i){var s=this.originalSize,n=this.originalPosition;return{top:n.top+i,height:s.height-i}},s:function(t,e,i){return{height:this.originalSize.height+i}},se:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},sw:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[e,i,s]))},ne:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},nw:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[e,i,s]))}},_propagate:function(e,i){t.ui.plugin.call(this,e,[i,this.ui()]),"resize"!==e&&this._trigger(e,i,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),t.ui.plugin.add("resizable","animate",{stop:function(e){var i=t(this).data("ui-resizable"),s=i.options,n=i._proportionallyResizeElements,a=n.length&&/textarea/i.test(n[0].nodeName),o=a&&t.ui.hasScroll(n[0],"left")?0:i.sizeDiff.height,r=a?0:i.sizeDiff.width,h={width:i.size.width-r,height:i.size.height-o},l=parseInt(i.element.css("left"),10)+(i.position.left-i.originalPosition.left)||null,c=parseInt(i.element.css("top"),10)+(i.position.top-i.originalPosition.top)||null;i.element.animate(t.extend(h,c&&l?{top:c,left:l}:{}),{duration:s.animateDuration,easing:s.animateEasing,step:function(){var s={width:parseInt(i.element.css("width"),10),height:parseInt(i.element.css("height"),10),top:parseInt(i.element.css("top"),10),left:parseInt(i.element.css("left"),10)};n&&n.length&&t(n[0]).css({width:s.width,height:s.height}),i._updateCache(s),i._propagate("resize",e)}})}}),t.ui.plugin.add("resizable","containment",{start:function(){var i,s,n,a,o,r,h,l=t(this).data("ui-resizable"),c=l.options,u=l.element,d=c.containment,p=d instanceof t?d.get(0):/parent/.test(d)?u.parent().get(0):d;p&&(l.containerElement=t(p),/document/.test(d)||d===document?(l.containerOffset={left:0,top:0},l.containerPosition={left:0,top:0},l.parentData={element:t(document),left:0,top:0,width:t(document).width(),height:t(document).height()||document.body.parentNode.scrollHeight}):(i=t(p),s=[],t(["Top","Right","Left","Bottom"]).each(function(t,n){s[t]=e(i.css("padding"+n))}),l.containerOffset=i.offset(),l.containerPosition=i.position(),l.containerSize={height:i.innerHeight()-s[3],width:i.innerWidth()-s[1]},n=l.containerOffset,a=l.containerSize.height,o=l.containerSize.width,r=t.ui.hasScroll(p,"left")?p.scrollWidth:o,h=t.ui.hasScroll(p)?p.scrollHeight:a,l.parentData={element:p,left:n.left,top:n.top,width:r,height:h}))},resize:function(e){var i,s,n,a,o=t(this).data("ui-resizable"),r=o.options,h=o.containerOffset,l=o.position,c=o._aspectRatio||e.shiftKey,u={top:0,left:0},d=o.containerElement;d[0]!==document&&/static/.test(d.css("position"))&&(u=h),l.left<(o._helper?h.left:0)&&(o.size.width=o.size.width+(o._helper?o.position.left-h.left:o.position.left-u.left),c&&(o.size.height=o.size.width/o.aspectRatio),o.position.left=r.helper?h.left:0),l.top<(o._helper?h.top:0)&&(o.size.height=o.size.height+(o._helper?o.position.top-h.top:o.position.top),c&&(o.size.width=o.size.height*o.aspectRatio),o.position.top=o._helper?h.top:0),o.offset.left=o.parentData.left+o.position.left,o.offset.top=o.parentData.top+o.position.top,i=Math.abs((o._helper?o.offset.left-u.left:o.offset.left-u.left)+o.sizeDiff.width),s=Math.abs((o._helper?o.offset.top-u.top:o.offset.top-h.top)+o.sizeDiff.height),n=o.containerElement.get(0)===o.element.parent().get(0),a=/relative|absolute/.test(o.containerElement.css("position")),n&&a&&(i-=Math.abs(o.parentData.left)),i+o.size.width>=o.parentData.width&&(o.size.width=o.parentData.width-i,c&&(o.size.height=o.size.width/o.aspectRatio)),s+o.size.height>=o.parentData.height&&(o.size.height=o.parentData.height-s,c&&(o.size.width=o.size.height*o.aspectRatio))},stop:function(){var e=t(this).data("ui-resizable"),i=e.options,s=e.containerOffset,n=e.containerPosition,a=e.containerElement,o=t(e.helper),r=o.offset(),h=o.outerWidth()-e.sizeDiff.width,l=o.outerHeight()-e.sizeDiff.height;e._helper&&!i.animate&&/relative/.test(a.css("position"))&&t(this).css({left:r.left-n.left-s.left,width:h,height:l}),e._helper&&!i.animate&&/static/.test(a.css("position"))&&t(this).css({left:r.left-n.left-s.left,width:h,height:l})}}),t.ui.plugin.add("resizable","alsoResize",{start:function(){var e=t(this).data("ui-resizable"),i=e.options,s=function(e){t(e).each(function(){var e=t(this);e.data("ui-resizable-alsoresize",{width:parseInt(e.width(),10),height:parseInt(e.height(),10),left:parseInt(e.css("left"),10),top:parseInt(e.css("top"),10)})})};"object"!=typeof i.alsoResize||i.alsoResize.parentNode?s(i.alsoResize):i.alsoResize.length?(i.alsoResize=i.alsoResize[0],s(i.alsoResize)):t.each(i.alsoResize,function(t){s(t)})},resize:function(e,i){var s=t(this).data("ui-resizable"),n=s.options,a=s.originalSize,o=s.originalPosition,r={height:s.size.height-a.height||0,width:s.size.width-a.width||0,top:s.position.top-o.top||0,left:s.position.left-o.left||0},h=function(e,s){t(e).each(function(){var e=t(this),n=t(this).data("ui-resizable-alsoresize"),a={},o=s&&s.length?s:e.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];t.each(o,function(t,e){var i=(n[e]||0)+(r[e]||0);i&&i>=0&&(a[e]=i||null)}),e.css(a)})};"object"!=typeof n.alsoResize||n.alsoResize.nodeType?h(n.alsoResize):t.each(n.alsoResize,function(t,e){h(t,e)})},stop:function(){t(this).removeData("resizable-alsoresize")}}),t.ui.plugin.add("resizable","ghost",{start:function(){var e=t(this).data("ui-resizable"),i=e.options,s=e.size;e.ghost=e.originalElement.clone(),e.ghost.css({opacity:.25,display:"block",position:"relative",height:s.height,width:s.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass("string"==typeof i.ghost?i.ghost:""),e.ghost.appendTo(e.helper)},resize:function(){var e=t(this).data("ui-resizable");e.ghost&&e.ghost.css({position:"relative",height:e.size.height,width:e.size.width})},stop:function(){var e=t(this).data("ui-resizable");e.ghost&&e.helper&&e.helper.get(0).removeChild(e.ghost.get(0))}}),t.ui.plugin.add("resizable","grid",{resize:function(){var e=t(this).data("ui-resizable"),i=e.options,s=e.size,n=e.originalSize,a=e.originalPosition,o=e.axis,r="number"==typeof i.grid?[i.grid,i.grid]:i.grid,h=r[0]||1,l=r[1]||1,c=Math.round((s.width-n.width)/h)*h,u=Math.round((s.height-n.height)/l)*l,d=n.width+c,p=n.height+u,f=i.maxWidth&&d>i.maxWidth,g=i.maxHeight&&p>i.maxHeight,m=i.minWidth&&i.minWidth>d,v=i.minHeight&&i.minHeight>p;i.grid=r,m&&(d+=h),v&&(p+=l),f&&(d-=h),g&&(p-=l),/^(se|s|e)$/.test(o)?(e.size.width=d,e.size.height=p):/^(ne)$/.test(o)?(e.size.width=d,e.size.height=p,e.position.top=a.top-u):/^(sw)$/.test(o)?(e.size.width=d,e.size.height=p,e.position.left=a.left-c):(p-l>0?(e.size.height=p,e.position.top=a.top-u):(e.size.height=l,e.position.top=a.top+n.height-l),d-h>0?(e.size.width=d,e.position.left=a.left-c):(e.size.width=h,e.position.left=a.left+n.width-h))}})})(jQuery);(function(t){function e(t,e,i){return t>e&&e+i>t}function i(t){return/left|right/.test(t.css("float"))||/inline|table-cell/.test(t.css("display"))}t.widget("ui.sortable",t.ui.mouse,{version:"1.10.4",widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_create:function(){var t=this.options;this.containerCache={},this.element.addClass("ui-sortable"),this.refresh(),this.floating=this.items.length?"x"===t.axis||i(this.items[0].item):!1,this.offset=this.element.offset(),this._mouseInit(),this.ready=!0},_destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled"),this._mouseDestroy();for(var t=this.items.length-1;t>=0;t--)this.items[t].item.removeData(this.widgetName+"-item");return this},_setOption:function(e,i){"disabled"===e?(this.options[e]=i,this.widget().toggleClass("ui-sortable-disabled",!!i)):t.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(e,i){var s=null,n=!1,o=this;return this.reverting?!1:this.options.disabled||"static"===this.options.type?!1:(this._refreshItems(e),t(e.target).parents().each(function(){return t.data(this,o.widgetName+"-item")===o?(s=t(this),!1):undefined}),t.data(e.target,o.widgetName+"-item")===o&&(s=t(e.target)),s?!this.options.handle||i||(t(this.options.handle,s).find("*").addBack().each(function(){this===e.target&&(n=!0)}),n)?(this.currentItem=s,this._removeCurrentsFromItems(),!0):!1:!1)},_mouseStart:function(e,i,s){var n,o,a=this.options;if(this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(e),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},t.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(e),this.originalPageX=e.pageX,this.originalPageY=e.pageY,a.cursorAt&&this._adjustOffsetFromHelper(a.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!==this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),a.containment&&this._setContainment(),a.cursor&&"auto"!==a.cursor&&(o=this.document.find("body"),this.storedCursor=o.css("cursor"),o.css("cursor",a.cursor),this.storedStylesheet=t("").appendTo(o)),a.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",a.opacity)),a.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",a.zIndex)),this.scrollParent[0]!==document&&"HTML"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",e,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions(),!s)for(n=this.containers.length-1;n>=0;n--)this.containers[n]._trigger("activate",e,this._uiHash(this));return t.ui.ddmanager&&(t.ui.ddmanager.current=this),t.ui.ddmanager&&!a.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this.dragging=!0,this.helper.addClass("ui-sortable-helper"),this._mouseDrag(e),!0},_mouseDrag:function(e){var i,s,n,o,a=this.options,r=!1;for(this.position=this._generatePosition(e),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs),this.options.scroll&&(this.scrollParent[0]!==document&&"HTML"!==this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-e.pageY=0;i--)if(s=this.items[i],n=s.item[0],o=this._intersectsWithPointer(s),o&&s.instance===this.currentContainer&&n!==this.currentItem[0]&&this.placeholder[1===o?"next":"prev"]()[0]!==n&&!t.contains(this.placeholder[0],n)&&("semi-dynamic"===this.options.type?!t.contains(this.element[0],n):!0)){if(this.direction=1===o?"down":"up","pointer"!==this.options.tolerance&&!this._intersectsWithSides(s))break;this._rearrange(e,s),this._trigger("change",e,this._uiHash());break}return this._contactContainers(e),t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),this._trigger("sort",e,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(e,i){if(e){if(t.ui.ddmanager&&!this.options.dropBehaviour&&t.ui.ddmanager.drop(this,e),this.options.revert){var s=this,n=this.placeholder.offset(),o=this.options.axis,a={};o&&"x"!==o||(a.left=n.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollLeft)),o&&"y"!==o||(a.top=n.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollTop)),this.reverting=!0,t(this.helper).animate(a,parseInt(this.options.revert,10)||500,function(){s._clear(e)})}else this._clear(e,i);return!1}},cancel:function(){if(this.dragging){this._mouseUp({target:null}),"original"===this.options.helper?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var e=this.containers.length-1;e>=0;e--)this.containers[e]._trigger("deactivate",null,this._uiHash(this)),this.containers[e].containerCache.over&&(this.containers[e]._trigger("out",null,this._uiHash(this)),this.containers[e].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),"original"!==this.options.helper&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),t.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?t(this.domPosition.prev).after(this.currentItem):t(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},t(i).each(function(){var i=(t(e.item||this).attr(e.attribute||"id")||"").match(e.expression||/(.+)[\-=_](.+)/);i&&s.push((e.key||i[1]+"[]")+"="+(e.key&&e.expression?i[1]:i[2]))}),!s.length&&e.key&&s.push(e.key+"="),s.join("&")},toArray:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},i.each(function(){s.push(t(e.item||this).attr(e.attribute||"id")||"")}),s},_intersectsWith:function(t){var e=this.positionAbs.left,i=e+this.helperProportions.width,s=this.positionAbs.top,n=s+this.helperProportions.height,o=t.left,a=o+t.width,r=t.top,h=r+t.height,l=this.offset.click.top,c=this.offset.click.left,u="x"===this.options.axis||s+l>r&&h>s+l,d="y"===this.options.axis||e+c>o&&a>e+c,p=u&&d;return"pointer"===this.options.tolerance||this.options.forcePointerForContainers||"pointer"!==this.options.tolerance&&this.helperProportions[this.floating?"width":"height"]>t[this.floating?"width":"height"]?p:e+this.helperProportions.width/2>o&&a>i-this.helperProportions.width/2&&s+this.helperProportions.height/2>r&&h>n-this.helperProportions.height/2},_intersectsWithPointer:function(t){var i="x"===this.options.axis||e(this.positionAbs.top+this.offset.click.top,t.top,t.height),s="y"===this.options.axis||e(this.positionAbs.left+this.offset.click.left,t.left,t.width),n=i&&s,o=this._getDragVerticalDirection(),a=this._getDragHorizontalDirection();return n?this.floating?a&&"right"===a||"down"===o?2:1:o&&("down"===o?2:1):!1},_intersectsWithSides:function(t){var i=e(this.positionAbs.top+this.offset.click.top,t.top+t.height/2,t.height),s=e(this.positionAbs.left+this.offset.click.left,t.left+t.width/2,t.width),n=this._getDragVerticalDirection(),o=this._getDragHorizontalDirection();return this.floating&&o?"right"===o&&s||"left"===o&&!s:n&&("down"===n&&i||"up"===n&&!i)},_getDragVerticalDirection:function(){var t=this.positionAbs.top-this.lastPositionAbs.top;return 0!==t&&(t>0?"down":"up")},_getDragHorizontalDirection:function(){var t=this.positionAbs.left-this.lastPositionAbs.left;return 0!==t&&(t>0?"right":"left")},refresh:function(t){return this._refreshItems(t),this.refreshPositions(),this},_connectWith:function(){var t=this.options;return t.connectWith.constructor===String?[t.connectWith]:t.connectWith},_getItemsAsjQuery:function(e){function i(){r.push(this)}var s,n,o,a,r=[],h=[],l=this._connectWith();if(l&&e)for(s=l.length-1;s>=0;s--)for(o=t(l[s]),n=o.length-1;n>=0;n--)a=t.data(o[n],this.widgetFullName),a&&a!==this&&!a.options.disabled&&h.push([t.isFunction(a.options.items)?a.options.items.call(a.element):t(a.options.items,a.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),a]);for(h.push([t.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):t(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]),s=h.length-1;s>=0;s--)h[s][0].each(i);return t(r)},_removeCurrentsFromItems:function(){var e=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=t.grep(this.items,function(t){for(var i=0;e.length>i;i++)if(e[i]===t.item[0])return!1;return!0})},_refreshItems:function(e){this.items=[],this.containers=[this];var i,s,n,o,a,r,h,l,c=this.items,u=[[t.isFunction(this.options.items)?this.options.items.call(this.element[0],e,{item:this.currentItem}):t(this.options.items,this.element),this]],d=this._connectWith();if(d&&this.ready)for(i=d.length-1;i>=0;i--)for(n=t(d[i]),s=n.length-1;s>=0;s--)o=t.data(n[s],this.widgetFullName),o&&o!==this&&!o.options.disabled&&(u.push([t.isFunction(o.options.items)?o.options.items.call(o.element[0],e,{item:this.currentItem}):t(o.options.items,o.element),o]),this.containers.push(o));for(i=u.length-1;i>=0;i--)for(a=u[i][1],r=u[i][0],s=0,l=r.length;l>s;s++)h=t(r[s]),h.data(this.widgetName+"-item",a),c.push({item:h,instance:a,width:0,height:0,left:0,top:0})},refreshPositions:function(e){this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());var i,s,n,o;for(i=this.items.length-1;i>=0;i--)s=this.items[i],s.instance!==this.currentContainer&&this.currentContainer&&s.item[0]!==this.currentItem[0]||(n=this.options.toleranceElement?t(this.options.toleranceElement,s.item):s.item,e||(s.width=n.outerWidth(),s.height=n.outerHeight()),o=n.offset(),s.left=o.left,s.top=o.top);if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(i=this.containers.length-1;i>=0;i--)o=this.containers[i].element.offset(),this.containers[i].containerCache.left=o.left,this.containers[i].containerCache.top=o.top,this.containers[i].containerCache.width=this.containers[i].element.outerWidth(),this.containers[i].containerCache.height=this.containers[i].element.outerHeight();return this},_createPlaceholder:function(e){e=e||this;var i,s=e.options;s.placeholder&&s.placeholder.constructor!==String||(i=s.placeholder,s.placeholder={element:function(){var s=e.currentItem[0].nodeName.toLowerCase(),n=t("<"+s+">",e.document[0]).addClass(i||e.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper");return"tr"===s?e.currentItem.children().each(function(){t("
    ",e.document[0]).attr("colspan",t(this).attr("colspan")||1).appendTo(n)}):"img"===s&&n.attr("src",e.currentItem.attr("src")),i||n.css("visibility","hidden"),n},update:function(t,n){(!i||s.forcePlaceholderSize)&&(n.height()||n.height(e.currentItem.innerHeight()-parseInt(e.currentItem.css("paddingTop")||0,10)-parseInt(e.currentItem.css("paddingBottom")||0,10)),n.width()||n.width(e.currentItem.innerWidth()-parseInt(e.currentItem.css("paddingLeft")||0,10)-parseInt(e.currentItem.css("paddingRight")||0,10)))}}),e.placeholder=t(s.placeholder.element.call(e.element,e.currentItem)),e.currentItem.after(e.placeholder),s.placeholder.update(e,e.placeholder)},_contactContainers:function(s){var n,o,a,r,h,l,c,u,d,p,f=null,g=null;for(n=this.containers.length-1;n>=0;n--)if(!t.contains(this.currentItem[0],this.containers[n].element[0]))if(this._intersectsWith(this.containers[n].containerCache)){if(f&&t.contains(this.containers[n].element[0],f.element[0]))continue;f=this.containers[n],g=n}else this.containers[n].containerCache.over&&(this.containers[n]._trigger("out",s,this._uiHash(this)),this.containers[n].containerCache.over=0);if(f)if(1===this.containers.length)this.containers[g].containerCache.over||(this.containers[g]._trigger("over",s,this._uiHash(this)),this.containers[g].containerCache.over=1);else{for(a=1e4,r=null,p=f.floating||i(this.currentItem),h=p?"left":"top",l=p?"width":"height",c=this.positionAbs[h]+this.offset.click[h],o=this.items.length-1;o>=0;o--)t.contains(this.containers[g].element[0],this.items[o].item[0])&&this.items[o].item[0]!==this.currentItem[0]&&(!p||e(this.positionAbs.top+this.offset.click.top,this.items[o].top,this.items[o].height))&&(u=this.items[o].item.offset()[h],d=!1,Math.abs(u-c)>Math.abs(u+this.items[o][l]-c)&&(d=!0,u+=this.items[o][l]),a>Math.abs(u-c)&&(a=Math.abs(u-c),r=this.items[o],this.direction=d?"up":"down"));if(!r&&!this.options.dropOnEmpty)return;if(this.currentContainer===this.containers[g])return;r?this._rearrange(s,r,null,!0):this._rearrange(s,null,this.containers[g].element,!0),this._trigger("change",s,this._uiHash()),this.containers[g]._trigger("change",s,this._uiHash(this)),this.currentContainer=this.containers[g],this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[g]._trigger("over",s,this._uiHash(this)),this.containers[g].containerCache.over=1}},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper)?t(i.helper.apply(this.element[0],[e,this.currentItem])):"clone"===i.helper?this.currentItem.clone():this.currentItem;return s.parents("body").length||t("parent"!==i.appendTo?i.appendTo:this.currentItem[0].parentNode)[0].appendChild(s[0]),s[0]===this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(!s[0].style.width||i.forceHelperSize)&&s.width(this.currentItem.width()),(!s[0].style.height||i.forceHelperSize)&&s.height(this.currentItem.height()),s},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var e=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===document.body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&t.ui.ie)&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var t=this.currentItem.position();return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:t.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options;"parent"===n.containment&&(n.containment=this.helper[0].parentNode),("document"===n.containment||"window"===n.containment)&&(this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,t("document"===n.containment?document:window).width()-this.helperProportions.width-this.margins.left,(t("document"===n.containment?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]),/^(document|window|parent)$/.test(n.containment)||(e=t(n.containment)[0],i=t(n.containment).offset(),s="hidden"!==t(e).css("overflow"),this.containment=[i.left+(parseInt(t(e).css("borderLeftWidth"),10)||0)+(parseInt(t(e).css("paddingLeft"),10)||0)-this.margins.left,i.top+(parseInt(t(e).css("borderTopWidth"),10)||0)+(parseInt(t(e).css("paddingTop"),10)||0)-this.margins.top,i.left+(s?Math.max(e.scrollWidth,e.offsetWidth):e.offsetWidth)-(parseInt(t(e).css("borderLeftWidth"),10)||0)-(parseInt(t(e).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,i.top+(s?Math.max(e.scrollHeight,e.offsetHeight):e.offsetHeight)-(parseInt(t(e).css("borderTopWidth"),10)||0)-(parseInt(t(e).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top])},_convertPositionTo:function(e,i){i||(i=this.position);var s="absolute"===e?1:-1,n="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,o=/(html|body)/i.test(n[0].tagName);return{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():o?0:n.scrollTop())*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():o?0:n.scrollLeft())*s}},_generatePosition:function(e){var i,s,n=this.options,o=e.pageX,a=e.pageY,r="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,h=/(html|body)/i.test(r[0].tagName);return"relative"!==this.cssPosition||this.scrollParent[0]!==document&&this.scrollParent[0]!==this.offsetParent[0]||(this.offset.relative=this._getRelativeOffset()),this.originalPosition&&(this.containment&&(e.pageX-this.offset.click.leftthis.containment[2]&&(o=this.containment[2]+this.offset.click.left),e.pageY-this.offset.click.top>this.containment[3]&&(a=this.containment[3]+this.offset.click.top)),n.grid&&(i=this.originalPageY+Math.round((a-this.originalPageY)/n.grid[1])*n.grid[1],a=this.containment?i-this.offset.click.top>=this.containment[1]&&i-this.offset.click.top<=this.containment[3]?i:i-this.offset.click.top>=this.containment[1]?i-n.grid[1]:i+n.grid[1]:i,s=this.originalPageX+Math.round((o-this.originalPageX)/n.grid[0])*n.grid[0],o=this.containment?s-this.offset.click.left>=this.containment[0]&&s-this.offset.click.left<=this.containment[2]?s:s-this.offset.click.left>=this.containment[0]?s-n.grid[0]:s+n.grid[0]:s)),{top:a-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():h?0:r.scrollTop()),left:o-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():h?0:r.scrollLeft())}},_rearrange:function(t,e,i,s){i?i[0].appendChild(this.placeholder[0]):e.item[0].parentNode.insertBefore(this.placeholder[0],"down"===this.direction?e.item[0]:e.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var n=this.counter;this._delay(function(){n===this.counter&&this.refreshPositions(!s)})},_clear:function(t,e){function i(t,e,i){return function(s){i._trigger(t,s,e._uiHash(e))}}this.reverting=!1;var s,n=[];if(!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null,this.helper[0]===this.currentItem[0]){for(s in this._storedCSS)("auto"===this._storedCSS[s]||"static"===this._storedCSS[s])&&(this._storedCSS[s]="");this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else this.currentItem.show();for(this.fromOutside&&!e&&n.push(function(t){this._trigger("receive",t,this._uiHash(this.fromOutside))}),!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(".ui-sortable-helper")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||e||n.push(function(t){this._trigger("update",t,this._uiHash())}),this!==this.currentContainer&&(e||(n.push(function(t){this._trigger("remove",t,this._uiHash())}),n.push(function(t){return function(e){t._trigger("receive",e,this._uiHash(this))}}.call(this,this.currentContainer)),n.push(function(t){return function(e){t._trigger("update",e,this._uiHash(this))}}.call(this,this.currentContainer)))),s=this.containers.length-1;s>=0;s--)e||n.push(i("deactivate",this,this.containers[s])),this.containers[s].containerCache.over&&(n.push(i("out",this,this.containers[s])),this.containers[s].containerCache.over=0);if(this.storedCursor&&(this.document.find("body").css("cursor",this.storedCursor),this.storedStylesheet.remove()),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex","auto"===this._storedZIndex?"":this._storedZIndex),this.dragging=!1,this.cancelHelperRemoval){if(!e){for(this._trigger("beforeStop",t,this._uiHash()),s=0;n.length>s;s++)n[s].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!1}if(e||this._trigger("beforeStop",t,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null,!e){for(s=0;n.length>s;s++)n[s].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!0},_trigger:function(){t.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(e){var i=e||this;return{helper:i.helper,placeholder:i.placeholder||t([]),position:i.position,originalPosition:i.originalPosition,offset:i.positionAbs,item:i.currentItem,sender:e?e.element:null}}})})(jQuery);(function(e){var t,i="ui-button ui-widget ui-state-default ui-corner-all",n="ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only",s=function(){var t=e(this);setTimeout(function(){t.find(":ui-button").button("refresh")},1)},a=function(t){var i=t.name,n=t.form,s=e([]);return i&&(i=i.replace(/'/g,"\\'"),s=n?e(n).find("[name='"+i+"']"):e("[name='"+i+"']",t.ownerDocument).filter(function(){return!this.form})),s};e.widget("ui.button",{version:"1.10.4",defaultElement:"").button({label:this.options.closeText,icons:{primary:"ui-icon-closethick"},text:!1}).addClass("ui-dialog-titlebar-close").appendTo(this.uiDialogTitlebar),this._on(this.uiDialogTitlebarClose,{click:function(e){e.preventDefault(),this.close(e)}}),t=e("").uniqueId().addClass("ui-dialog-title").prependTo(this.uiDialogTitlebar),this._title(t),this.uiDialog.attr({"aria-labelledby":t.attr("id")})},_title:function(e){this.options.title||e.html(" "),e.text(this.options.title)},_createButtonPane:function(){this.uiDialogButtonPane=e("
    ").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),this.uiButtonSet=e("
    ").addClass("ui-dialog-buttonset").appendTo(this.uiDialogButtonPane),this._createButtons()},_createButtons:function(){var t=this,i=this.options.buttons;return this.uiDialogButtonPane.remove(),this.uiButtonSet.empty(),e.isEmptyObject(i)||e.isArray(i)&&!i.length?(this.uiDialog.removeClass("ui-dialog-buttons"),undefined):(e.each(i,function(i,a){var s,n;a=e.isFunction(a)?{click:a,text:i}:a,a=e.extend({type:"button"},a),s=a.click,a.click=function(){s.apply(t.element[0],arguments)},n={icons:a.icons,text:a.showText},delete a.icons,delete a.showText,e("",a).button(n).appendTo(t.uiButtonSet)}),this.uiDialog.addClass("ui-dialog-buttons"),this.uiDialogButtonPane.appendTo(this.uiDialog),undefined)},_makeDraggable:function(){function t(e){return{position:e.position,offset:e.offset}}var i=this,a=this.options;this.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(a,s){e(this).addClass("ui-dialog-dragging"),i._blockFrames(),i._trigger("dragStart",a,t(s))},drag:function(e,a){i._trigger("drag",e,t(a))},stop:function(s,n){a.position=[n.position.left-i.document.scrollLeft(),n.position.top-i.document.scrollTop()],e(this).removeClass("ui-dialog-dragging"),i._unblockFrames(),i._trigger("dragStop",s,t(n))}})},_makeResizable:function(){function t(e){return{originalPosition:e.originalPosition,originalSize:e.originalSize,position:e.position,size:e.size}}var i=this,a=this.options,s=a.resizable,n=this.uiDialog.css("position"),r="string"==typeof s?s:"n,e,s,w,se,sw,ne,nw";this.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:this.element,maxWidth:a.maxWidth,maxHeight:a.maxHeight,minWidth:a.minWidth,minHeight:this._minHeight(),handles:r,start:function(a,s){e(this).addClass("ui-dialog-resizing"),i._blockFrames(),i._trigger("resizeStart",a,t(s))},resize:function(e,a){i._trigger("resize",e,t(a))},stop:function(s,n){a.height=e(this).height(),a.width=e(this).width(),e(this).removeClass("ui-dialog-resizing"),i._unblockFrames(),i._trigger("resizeStop",s,t(n))}}).css("position",n)},_minHeight:function(){var e=this.options;return"auto"===e.height?e.minHeight:Math.min(e.minHeight,e.height)},_position:function(){var e=this.uiDialog.is(":visible");e||this.uiDialog.show(),this.uiDialog.position(this.options.position),e||this.uiDialog.hide()},_setOptions:function(a){var s=this,n=!1,r={};e.each(a,function(e,a){s._setOption(e,a),e in t&&(n=!0),e in i&&(r[e]=a)}),n&&(this._size(),this._position()),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option",r)},_setOption:function(e,t){var i,a,s=this.uiDialog;"dialogClass"===e&&s.removeClass(this.options.dialogClass).addClass(t),"disabled"!==e&&(this._super(e,t),"appendTo"===e&&this.uiDialog.appendTo(this._appendTo()),"buttons"===e&&this._createButtons(),"closeText"===e&&this.uiDialogTitlebarClose.button({label:""+t}),"draggable"===e&&(i=s.is(":data(ui-draggable)"),i&&!t&&s.draggable("destroy"),!i&&t&&this._makeDraggable()),"position"===e&&this._position(),"resizable"===e&&(a=s.is(":data(ui-resizable)"),a&&!t&&s.resizable("destroy"),a&&"string"==typeof t&&s.resizable("option","handles",t),a||t===!1||this._makeResizable()),"title"===e&&this._title(this.uiDialogTitlebar.find(".ui-dialog-title")))},_size:function(){var e,t,i,a=this.options;this.element.show().css({width:"auto",minHeight:0,maxHeight:"none",height:0}),a.minWidth>a.width&&(a.width=a.minWidth),e=this.uiDialog.css({height:"auto",width:a.width}).outerHeight(),t=Math.max(0,a.minHeight-e),i="number"==typeof a.maxHeight?Math.max(0,a.maxHeight-e):"none","auto"===a.height?this.element.css({minHeight:t,maxHeight:i,height:"auto"}):this.element.height(Math.max(0,a.height-e)),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())},_blockFrames:function(){this.iframeBlocks=this.document.find("iframe").map(function(){var t=e(this);return e("
    ").css({position:"absolute",width:t.outerWidth(),height:t.outerHeight()}).appendTo(t.parent()).offset(t.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_allowInteraction:function(t){return e(t.target).closest(".ui-dialog").length?!0:!!e(t.target).closest(".ui-datepicker").length},_createOverlay:function(){if(this.options.modal){var t=this,i=this.widgetFullName;e.ui.dialog.overlayInstances||this._delay(function(){e.ui.dialog.overlayInstances&&this.document.bind("focusin.dialog",function(a){t._allowInteraction(a)||(a.preventDefault(),e(".ui-dialog:visible:last .ui-dialog-content").data(i)._focusTabbable())})}),this.overlay=e("
    ").addClass("ui-widget-overlay ui-front").appendTo(this._appendTo()),this._on(this.overlay,{mousedown:"_keepFocus"}),e.ui.dialog.overlayInstances++}},_destroyOverlay:function(){this.options.modal&&this.overlay&&(e.ui.dialog.overlayInstances--,e.ui.dialog.overlayInstances||this.document.unbind("focusin.dialog"),this.overlay.remove(),this.overlay=null)}}),e.ui.dialog.overlayInstances=0,e.uiBackCompat!==!1&&e.widget("ui.dialog",e.ui.dialog,{_position:function(){var t,i=this.options.position,a=[],s=[0,0];i?(("string"==typeof i||"object"==typeof i&&"0"in i)&&(a=i.split?i.split(" "):[i[0],i[1]],1===a.length&&(a[1]=a[0]),e.each(["left","top"],function(e,t){+a[e]===a[e]&&(s[e]=a[e],a[e]=t)}),i={my:a[0]+(0>s[0]?s[0]:"+"+s[0])+" "+a[1]+(0>s[1]?s[1]:"+"+s[1]),at:a.join(" ")}),i=e.extend({},e.ui.dialog.prototype.options.position,i)):i=e.ui.dialog.prototype.options.position,t=this.uiDialog.is(":visible"),t||this.uiDialog.show(),this.uiDialog.position(i),t||this.uiDialog.hide()}})})(jQuery);(function(t){function e(e,i){var s=(e.attr("aria-describedby")||"").split(/\s+/);s.push(i),e.data("ui-tooltip-id",i).attr("aria-describedby",t.trim(s.join(" ")))}function i(e){var i=e.data("ui-tooltip-id"),s=(e.attr("aria-describedby")||"").split(/\s+/),n=t.inArray(i,s);-1!==n&&s.splice(n,1),e.removeData("ui-tooltip-id"),s=t.trim(s.join(" ")),s?e.attr("aria-describedby",s):e.removeAttr("aria-describedby")}var s=0;t.widget("ui.tooltip",{version:"1.10.4",options:{content:function(){var e=t(this).attr("title")||"";return t("").text(e).html()},hide:!0,items:"[title]:not([disabled])",position:{my:"left top+15",at:"left bottom",collision:"flipfit flip"},show:!0,tooltipClass:null,track:!1,close:null,open:null},_create:function(){this._on({mouseover:"open",focusin:"open"}),this.tooltips={},this.parents={},this.options.disabled&&this._disable()},_setOption:function(e,i){var s=this;return"disabled"===e?(this[i?"_disable":"_enable"](),this.options[e]=i,void 0):(this._super(e,i),"content"===e&&t.each(this.tooltips,function(t,e){s._updateContent(e)}),void 0)},_disable:function(){var e=this;t.each(this.tooltips,function(i,s){var n=t.Event("blur");n.target=n.currentTarget=s[0],e.close(n,!0)}),this.element.find(this.options.items).addBack().each(function(){var e=t(this);e.is("[title]")&&e.data("ui-tooltip-title",e.attr("title")).attr("title","")})},_enable:function(){this.element.find(this.options.items).addBack().each(function(){var e=t(this);e.data("ui-tooltip-title")&&e.attr("title",e.data("ui-tooltip-title"))})},open:function(e){var i=this,s=t(e?e.target:this.element).closest(this.options.items);s.length&&!s.data("ui-tooltip-id")&&(s.attr("title")&&s.data("ui-tooltip-title",s.attr("title")),s.data("ui-tooltip-open",!0),e&&"mouseover"===e.type&&s.parents().each(function(){var e,s=t(this);s.data("ui-tooltip-open")&&(e=t.Event("blur"),e.target=e.currentTarget=this,i.close(e,!0)),s.attr("title")&&(s.uniqueId(),i.parents[this.id]={element:this,title:s.attr("title")},s.attr("title",""))}),this._updateContent(s,e))},_updateContent:function(t,e){var i,s=this.options.content,n=this,o=e?e.type:null;return"string"==typeof s?this._open(e,t,s):(i=s.call(t[0],function(i){t.data("ui-tooltip-open")&&n._delay(function(){e&&(e.type=o),this._open(e,t,i)})}),i&&this._open(e,t,i),void 0)},_open:function(i,s,n){function o(t){l.of=t,a.is(":hidden")||a.position(l)}var a,r,h,l=t.extend({},this.options.position);if(n){if(a=this._find(s),a.length)return a.find(".ui-tooltip-content").html(n),void 0;s.is("[title]")&&(i&&"mouseover"===i.type?s.attr("title",""):s.removeAttr("title")),a=this._tooltip(s),e(s,a.attr("id")),a.find(".ui-tooltip-content").html(n),this.options.track&&i&&/^mouse/.test(i.type)?(this._on(this.document,{mousemove:o}),o(i)):a.position(t.extend({of:s},this.options.position)),a.hide(),this._show(a,this.options.show),this.options.show&&this.options.show.delay&&(h=this.delayedShow=setInterval(function(){a.is(":visible")&&(o(l.of),clearInterval(h))},t.fx.interval)),this._trigger("open",i,{tooltip:a}),r={keyup:function(e){if(e.keyCode===t.ui.keyCode.ESCAPE){var i=t.Event(e);i.currentTarget=s[0],this.close(i,!0)}},remove:function(){this._removeTooltip(a)}},i&&"mouseover"!==i.type||(r.mouseleave="close"),i&&"focusin"!==i.type||(r.focusout="close"),this._on(!0,s,r)}},close:function(e){var s=this,n=t(e?e.currentTarget:this.element),o=this._find(n);this.closing||(clearInterval(this.delayedShow),n.data("ui-tooltip-title")&&n.attr("title",n.data("ui-tooltip-title")),i(n),o.stop(!0),this._hide(o,this.options.hide,function(){s._removeTooltip(t(this))}),n.removeData("ui-tooltip-open"),this._off(n,"mouseleave focusout keyup"),n[0]!==this.element[0]&&this._off(n,"remove"),this._off(this.document,"mousemove"),e&&"mouseleave"===e.type&&t.each(this.parents,function(e,i){t(i.element).attr("title",i.title),delete s.parents[e]}),this.closing=!0,this._trigger("close",e,{tooltip:o}),this.closing=!1)},_tooltip:function(e){var i="ui-tooltip-"+s++,n=t("
    ").attr({id:i,role:"tooltip"}).addClass("ui-tooltip ui-widget ui-corner-all ui-widget-content "+(this.options.tooltipClass||""));return t("
    ").addClass("ui-tooltip-content").appendTo(n),n.appendTo(this.document[0].body),this.tooltips[i]=e,n},_find:function(e){var i=e.data("ui-tooltip-id");return i?t("#"+i):t()},_removeTooltip:function(t){t.remove(),delete this.tooltips[t.attr("id")]},_destroy:function(){var e=this;t.each(this.tooltips,function(i,s){var n=t.Event("blur");n.target=n.currentTarget=s[0],e.close(n,!0),t("#"+i).remove(),s.data("ui-tooltip-title")&&(s.attr("title",s.data("ui-tooltip-title")),s.removeData("ui-tooltip-title"))})}})})(jQuery);(function(t,e){var i="ui-effects-";t.effects={effect:{}},function(t,e){function i(t,e,i){var s=u[e.type]||{};return null==t?i||!e.def?null:e.def:(t=s.floor?~~t:parseFloat(t),isNaN(t)?e.def:s.mod?(t+s.mod)%s.mod:0>t?0:t>s.max?s.max:t)}function s(i){var s=h(),n=s._rgba=[];return i=i.toLowerCase(),f(l,function(t,a){var o,r=a.re.exec(i),l=r&&a.parse(r),h=a.space||"rgba";return l?(o=s[h](l),s[c[h].cache]=o[c[h].cache],n=s._rgba=o._rgba,!1):e}),n.length?("0,0,0,0"===n.join()&&t.extend(n,a.transparent),s):a[i]}function n(t,e,i){return i=(i+1)%1,1>6*i?t+6*(e-t)*i:1>2*i?e:2>3*i?t+6*(e-t)*(2/3-i):t}var a,o="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",r=/^([\-+])=\s*(\d+\.?\d*)/,l=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[t[1],t[2],t[3],t[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[2.55*t[1],2.55*t[2],2.55*t[3],t[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(t){return[t[1],t[2]/100,t[3]/100,t[4]]}}],h=t.Color=function(e,i,s,n){return new t.Color.fn.parse(e,i,s,n)},c={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},u={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},d=h.support={},p=t("

    ")[0],f=t.each;p.style.cssText="background-color:rgba(1,1,1,.5)",d.rgba=p.style.backgroundColor.indexOf("rgba")>-1,f(c,function(t,e){e.cache="_"+t,e.props.alpha={idx:3,type:"percent",def:1}}),h.fn=t.extend(h.prototype,{parse:function(n,o,r,l){if(n===e)return this._rgba=[null,null,null,null],this;(n.jquery||n.nodeType)&&(n=t(n).css(o),o=e);var u=this,d=t.type(n),p=this._rgba=[];return o!==e&&(n=[n,o,r,l],d="array"),"string"===d?this.parse(s(n)||a._default):"array"===d?(f(c.rgba.props,function(t,e){p[e.idx]=i(n[e.idx],e)}),this):"object"===d?(n instanceof h?f(c,function(t,e){n[e.cache]&&(u[e.cache]=n[e.cache].slice())}):f(c,function(e,s){var a=s.cache;f(s.props,function(t,e){if(!u[a]&&s.to){if("alpha"===t||null==n[t])return;u[a]=s.to(u._rgba)}u[a][e.idx]=i(n[t],e,!0)}),u[a]&&0>t.inArray(null,u[a].slice(0,3))&&(u[a][3]=1,s.from&&(u._rgba=s.from(u[a])))}),this):e},is:function(t){var i=h(t),s=!0,n=this;return f(c,function(t,a){var o,r=i[a.cache];return r&&(o=n[a.cache]||a.to&&a.to(n._rgba)||[],f(a.props,function(t,i){return null!=r[i.idx]?s=r[i.idx]===o[i.idx]:e})),s}),s},_space:function(){var t=[],e=this;return f(c,function(i,s){e[s.cache]&&t.push(i)}),t.pop()},transition:function(t,e){var s=h(t),n=s._space(),a=c[n],o=0===this.alpha()?h("transparent"):this,r=o[a.cache]||a.to(o._rgba),l=r.slice();return s=s[a.cache],f(a.props,function(t,n){var a=n.idx,o=r[a],h=s[a],c=u[n.type]||{};null!==h&&(null===o?l[a]=h:(c.mod&&(h-o>c.mod/2?o+=c.mod:o-h>c.mod/2&&(o-=c.mod)),l[a]=i((h-o)*e+o,n)))}),this[n](l)},blend:function(e){if(1===this._rgba[3])return this;var i=this._rgba.slice(),s=i.pop(),n=h(e)._rgba;return h(t.map(i,function(t,e){return(1-s)*n[e]+s*t}))},toRgbaString:function(){var e="rgba(",i=t.map(this._rgba,function(t,e){return null==t?e>2?1:0:t});return 1===i[3]&&(i.pop(),e="rgb("),e+i.join()+")"},toHslaString:function(){var e="hsla(",i=t.map(this.hsla(),function(t,e){return null==t&&(t=e>2?1:0),e&&3>e&&(t=Math.round(100*t)+"%"),t});return 1===i[3]&&(i.pop(),e="hsl("),e+i.join()+")"},toHexString:function(e){var i=this._rgba.slice(),s=i.pop();return e&&i.push(~~(255*s)),"#"+t.map(i,function(t){return t=(t||0).toString(16),1===t.length?"0"+t:t}).join("")},toString:function(){return 0===this._rgba[3]?"transparent":this.toRgbaString()}}),h.fn.parse.prototype=h.fn,c.hsla.to=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e,i,s=t[0]/255,n=t[1]/255,a=t[2]/255,o=t[3],r=Math.max(s,n,a),l=Math.min(s,n,a),h=r-l,c=r+l,u=.5*c;return e=l===r?0:s===r?60*(n-a)/h+360:n===r?60*(a-s)/h+120:60*(s-n)/h+240,i=0===h?0:.5>=u?h/c:h/(2-c),[Math.round(e)%360,i,u,null==o?1:o]},c.hsla.from=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e=t[0]/360,i=t[1],s=t[2],a=t[3],o=.5>=s?s*(1+i):s+i-s*i,r=2*s-o;return[Math.round(255*n(r,o,e+1/3)),Math.round(255*n(r,o,e)),Math.round(255*n(r,o,e-1/3)),a]},f(c,function(s,n){var a=n.props,o=n.cache,l=n.to,c=n.from;h.fn[s]=function(s){if(l&&!this[o]&&(this[o]=l(this._rgba)),s===e)return this[o].slice();var n,r=t.type(s),u="array"===r||"object"===r?s:arguments,d=this[o].slice();return f(a,function(t,e){var s=u["object"===r?t:e.idx];null==s&&(s=d[e.idx]),d[e.idx]=i(s,e)}),c?(n=h(c(d)),n[o]=d,n):h(d)},f(a,function(e,i){h.fn[e]||(h.fn[e]=function(n){var a,o=t.type(n),l="alpha"===e?this._hsla?"hsla":"rgba":s,h=this[l](),c=h[i.idx];return"undefined"===o?c:("function"===o&&(n=n.call(this,c),o=t.type(n)),null==n&&i.empty?this:("string"===o&&(a=r.exec(n),a&&(n=c+parseFloat(a[2])*("+"===a[1]?1:-1))),h[i.idx]=n,this[l](h)))})})}),h.hook=function(e){var i=e.split(" ");f(i,function(e,i){t.cssHooks[i]={set:function(e,n){var a,o,r="";if("transparent"!==n&&("string"!==t.type(n)||(a=s(n)))){if(n=h(a||n),!d.rgba&&1!==n._rgba[3]){for(o="backgroundColor"===i?e.parentNode:e;(""===r||"transparent"===r)&&o&&o.style;)try{r=t.css(o,"backgroundColor"),o=o.parentNode}catch(l){}n=n.blend(r&&"transparent"!==r?r:"_default")}n=n.toRgbaString()}try{e.style[i]=n}catch(l){}}},t.fx.step[i]=function(e){e.colorInit||(e.start=h(e.elem,i),e.end=h(e.end),e.colorInit=!0),t.cssHooks[i].set(e.elem,e.start.transition(e.end,e.pos))}})},h.hook(o),t.cssHooks.borderColor={expand:function(t){var e={};return f(["Top","Right","Bottom","Left"],function(i,s){e["border"+s+"Color"]=t}),e}},a=t.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(jQuery),function(){function i(e){var i,s,n=e.ownerDocument.defaultView?e.ownerDocument.defaultView.getComputedStyle(e,null):e.currentStyle,a={};if(n&&n.length&&n[0]&&n[n[0]])for(s=n.length;s--;)i=n[s],"string"==typeof n[i]&&(a[t.camelCase(i)]=n[i]);else for(i in n)"string"==typeof n[i]&&(a[i]=n[i]);return a}function s(e,i){var s,n,o={};for(s in i)n=i[s],e[s]!==n&&(a[s]||(t.fx.step[s]||!isNaN(parseFloat(n)))&&(o[s]=n));return o}var n=["add","remove","toggle"],a={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};t.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(e,i){t.fx.step[i]=function(t){("none"!==t.end&&!t.setAttr||1===t.pos&&!t.setAttr)&&(jQuery.style(t.elem,i,t.end),t.setAttr=!0)}}),t.fn.addBack||(t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.effects.animateClass=function(e,a,o,r){var l=t.speed(a,o,r);return this.queue(function(){var a,o=t(this),r=o.attr("class")||"",h=l.children?o.find("*").addBack():o;h=h.map(function(){var e=t(this);return{el:e,start:i(this)}}),a=function(){t.each(n,function(t,i){e[i]&&o[i+"Class"](e[i])})},a(),h=h.map(function(){return this.end=i(this.el[0]),this.diff=s(this.start,this.end),this}),o.attr("class",r),h=h.map(function(){var e=this,i=t.Deferred(),s=t.extend({},l,{queue:!1,complete:function(){i.resolve(e)}});return this.el.animate(this.diff,s),i.promise()}),t.when.apply(t,h.get()).done(function(){a(),t.each(arguments,function(){var e=this.el;t.each(this.diff,function(t){e.css(t,"")})}),l.complete.call(o[0])})})},t.fn.extend({addClass:function(e){return function(i,s,n,a){return s?t.effects.animateClass.call(this,{add:i},s,n,a):e.apply(this,arguments)}}(t.fn.addClass),removeClass:function(e){return function(i,s,n,a){return arguments.length>1?t.effects.animateClass.call(this,{remove:i},s,n,a):e.apply(this,arguments)}}(t.fn.removeClass),toggleClass:function(i){return function(s,n,a,o,r){return"boolean"==typeof n||n===e?a?t.effects.animateClass.call(this,n?{add:s}:{remove:s},a,o,r):i.apply(this,arguments):t.effects.animateClass.call(this,{toggle:s},n,a,o)}}(t.fn.toggleClass),switchClass:function(e,i,s,n,a){return t.effects.animateClass.call(this,{add:i,remove:e},s,n,a)}})}(),function(){function s(e,i,s,n){return t.isPlainObject(e)&&(i=e,e=e.effect),e={effect:e},null==i&&(i={}),t.isFunction(i)&&(n=i,s=null,i={}),("number"==typeof i||t.fx.speeds[i])&&(n=s,s=i,i={}),t.isFunction(s)&&(n=s,s=null),i&&t.extend(e,i),s=s||i.duration,e.duration=t.fx.off?0:"number"==typeof s?s:s in t.fx.speeds?t.fx.speeds[s]:t.fx.speeds._default,e.complete=n||i.complete,e}function n(e){return!e||"number"==typeof e||t.fx.speeds[e]?!0:"string"!=typeof e||t.effects.effect[e]?t.isFunction(e)?!0:"object"!=typeof e||e.effect?!1:!0:!0}t.extend(t.effects,{version:"1.10.4",save:function(t,e){for(var s=0;e.length>s;s++)null!==e[s]&&t.data(i+e[s],t[0].style[e[s]])},restore:function(t,s){var n,a;for(a=0;s.length>a;a++)null!==s[a]&&(n=t.data(i+s[a]),n===e&&(n=""),t.css(s[a],n))},setMode:function(t,e){return"toggle"===e&&(e=t.is(":hidden")?"show":"hide"),e},getBaseline:function(t,e){var i,s;switch(t[0]){case"top":i=0;break;case"middle":i=.5;break;case"bottom":i=1;break;default:i=t[0]/e.height}switch(t[1]){case"left":s=0;break;case"center":s=.5;break;case"right":s=1;break;default:s=t[1]/e.width}return{x:s,y:i}},createWrapper:function(e){if(e.parent().is(".ui-effects-wrapper"))return e.parent();var i={width:e.outerWidth(!0),height:e.outerHeight(!0),"float":e.css("float")},s=t("

    ").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),n={width:e.width(),height:e.height()},a=document.activeElement;try{a.id}catch(o){a=document.body}return e.wrap(s),(e[0]===a||t.contains(e[0],a))&&t(a).focus(),s=e.parent(),"static"===e.css("position")?(s.css({position:"relative"}),e.css({position:"relative"})):(t.extend(i,{position:e.css("position"),zIndex:e.css("z-index")}),t.each(["top","left","bottom","right"],function(t,s){i[s]=e.css(s),isNaN(parseInt(i[s],10))&&(i[s]="auto")}),e.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),e.css(n),s.css(i).show()},removeWrapper:function(e){var i=document.activeElement;return e.parent().is(".ui-effects-wrapper")&&(e.parent().replaceWith(e),(e[0]===i||t.contains(e[0],i))&&t(i).focus()),e},setTransition:function(e,i,s,n){return n=n||{},t.each(i,function(t,i){var a=e.cssUnit(i);a[0]>0&&(n[i]=a[0]*s+a[1])}),n}}),t.fn.extend({effect:function(){function e(e){function s(){t.isFunction(a)&&a.call(n[0]),t.isFunction(e)&&e()}var n=t(this),a=i.complete,r=i.mode;(n.is(":hidden")?"hide"===r:"show"===r)?(n[r](),s()):o.call(n[0],i,s)}var i=s.apply(this,arguments),n=i.mode,a=i.queue,o=t.effects.effect[i.effect];return t.fx.off||!o?n?this[n](i.duration,i.complete):this.each(function(){i.complete&&i.complete.call(this)}):a===!1?this.each(e):this.queue(a||"fx",e)},show:function(t){return function(e){if(n(e))return t.apply(this,arguments);var i=s.apply(this,arguments);return i.mode="show",this.effect.call(this,i)}}(t.fn.show),hide:function(t){return function(e){if(n(e))return t.apply(this,arguments);var i=s.apply(this,arguments);return i.mode="hide",this.effect.call(this,i)}}(t.fn.hide),toggle:function(t){return function(e){if(n(e)||"boolean"==typeof e)return t.apply(this,arguments);var i=s.apply(this,arguments);return i.mode="toggle",this.effect.call(this,i)}}(t.fn.toggle),cssUnit:function(e){var i=this.css(e),s=[];return t.each(["em","px","%","pt"],function(t,e){i.indexOf(e)>0&&(s=[parseFloat(i),e])}),s}})}(),function(){var e={};t.each(["Quad","Cubic","Quart","Quint","Expo"],function(t,i){e[i]=function(e){return Math.pow(e,t+2)}}),t.extend(e,{Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t){return 0===t||1===t?t:-Math.pow(2,8*(t-1))*Math.sin((80*(t-1)-7.5)*Math.PI/15)},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var e,i=4;((e=Math.pow(2,--i))-1)/11>t;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*e-2)/22-t,2)}}),t.each(e,function(e,i){t.easing["easeIn"+e]=i,t.easing["easeOut"+e]=function(t){return 1-i(1-t)},t.easing["easeInOut"+e]=function(t){return.5>t?i(2*t)/2:1-i(-2*t+2)/2}})}()})(jQuery);(function(t){t.effects.effect.puff=function(e,i){var s=t(this),n=t.effects.setMode(s,e.mode||"hide"),a="hide"===n,o=parseInt(e.percent,10)||150,r=o/100,l={height:s.height(),width:s.width(),outerHeight:s.outerHeight(),outerWidth:s.outerWidth()};t.extend(e,{effect:"scale",queue:!1,fade:!0,mode:n,complete:i,percent:a?o:100,from:a?l:{height:l.height*r,width:l.width*r,outerHeight:l.outerHeight*r,outerWidth:l.outerWidth*r}}),s.effect(e)},t.effects.effect.scale=function(e,i){var s=t(this),n=t.extend(!0,{},e),a=t.effects.setMode(s,e.mode||"effect"),o=parseInt(e.percent,10)||(0===parseInt(e.percent,10)?0:"hide"===a?0:100),r=e.direction||"both",l=e.origin,h={height:s.height(),width:s.width(),outerHeight:s.outerHeight(),outerWidth:s.outerWidth()},c={y:"horizontal"!==r?o/100:1,x:"vertical"!==r?o/100:1};n.effect="size",n.queue=!1,n.complete=i,"effect"!==a&&(n.origin=l||["middle","center"],n.restore=!0),n.from=e.from||("show"===a?{height:0,width:0,outerHeight:0,outerWidth:0}:h),n.to={height:h.height*c.y,width:h.width*c.x,outerHeight:h.outerHeight*c.y,outerWidth:h.outerWidth*c.x},n.fade&&("show"===a&&(n.from.opacity=0,n.to.opacity=1),"hide"===a&&(n.from.opacity=1,n.to.opacity=0)),s.effect(n)},t.effects.effect.size=function(e,i){var s,n,a,o=t(this),r=["position","top","bottom","left","right","width","height","overflow","opacity"],l=["position","top","bottom","left","right","overflow","opacity"],h=["width","height","overflow"],c=["fontSize"],u=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],d=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],p=t.effects.setMode(o,e.mode||"effect"),f=e.restore||"effect"!==p,g=e.scale||"both",m=e.origin||["middle","center"],v=o.css("position"),_=f?r:l,b={height:0,width:0,outerHeight:0,outerWidth:0};"show"===p&&o.show(),s={height:o.height(),width:o.width(),outerHeight:o.outerHeight(),outerWidth:o.outerWidth()},"toggle"===e.mode&&"show"===p?(o.from=e.to||b,o.to=e.from||s):(o.from=e.from||("show"===p?b:s),o.to=e.to||("hide"===p?b:s)),a={from:{y:o.from.height/s.height,x:o.from.width/s.width},to:{y:o.to.height/s.height,x:o.to.width/s.width}},("box"===g||"both"===g)&&(a.from.y!==a.to.y&&(_=_.concat(u),o.from=t.effects.setTransition(o,u,a.from.y,o.from),o.to=t.effects.setTransition(o,u,a.to.y,o.to)),a.from.x!==a.to.x&&(_=_.concat(d),o.from=t.effects.setTransition(o,d,a.from.x,o.from),o.to=t.effects.setTransition(o,d,a.to.x,o.to))),("content"===g||"both"===g)&&a.from.y!==a.to.y&&(_=_.concat(c).concat(h),o.from=t.effects.setTransition(o,c,a.from.y,o.from),o.to=t.effects.setTransition(o,c,a.to.y,o.to)),t.effects.save(o,_),o.show(),t.effects.createWrapper(o),o.css("overflow","hidden").css(o.from),m&&(n=t.effects.getBaseline(m,s),o.from.top=(s.outerHeight-o.outerHeight())*n.y,o.from.left=(s.outerWidth-o.outerWidth())*n.x,o.to.top=(s.outerHeight-o.to.outerHeight)*n.y,o.to.left=(s.outerWidth-o.to.outerWidth)*n.x),o.css(o.from),("content"===g||"both"===g)&&(u=u.concat(["marginTop","marginBottom"]).concat(c),d=d.concat(["marginLeft","marginRight"]),h=r.concat(u).concat(d),o.find("*[width]").each(function(){var i=t(this),s={height:i.height(),width:i.width(),outerHeight:i.outerHeight(),outerWidth:i.outerWidth()};f&&t.effects.save(i,h),i.from={height:s.height*a.from.y,width:s.width*a.from.x,outerHeight:s.outerHeight*a.from.y,outerWidth:s.outerWidth*a.from.x},i.to={height:s.height*a.to.y,width:s.width*a.to.x,outerHeight:s.height*a.to.y,outerWidth:s.width*a.to.x},a.from.y!==a.to.y&&(i.from=t.effects.setTransition(i,u,a.from.y,i.from),i.to=t.effects.setTransition(i,u,a.to.y,i.to)),a.from.x!==a.to.x&&(i.from=t.effects.setTransition(i,d,a.from.x,i.from),i.to=t.effects.setTransition(i,d,a.to.x,i.to)),i.css(i.from),i.animate(i.to,e.duration,e.easing,function(){f&&t.effects.restore(i,h)})})),o.animate(o.to,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){0===o.to.opacity&&o.css("opacity",o.from.opacity),"hide"===p&&o.hide(),t.effects.restore(o,_),f||("static"===v?o.css({position:"relative",top:o.to.top,left:o.to.left}):t.each(["top","left"],function(t,e){o.css(e,function(e,i){var s=parseInt(i,10),n=t?o.to.left:o.to.top;return"auto"===i?n+"px":s+n+"px"})})),t.effects.removeWrapper(o),i()}})}})(jQuery);(function(t){t.effects.effect.slide=function(e,i){var s,n=t(this),a=["position","top","bottom","left","right","width","height"],o=t.effects.setMode(n,e.mode||"show"),r="show"===o,l=e.direction||"left",h="up"===l||"down"===l?"top":"left",c="up"===l||"left"===l,u={};t.effects.save(n,a),n.show(),s=e.distance||n["top"===h?"outerHeight":"outerWidth"](!0),t.effects.createWrapper(n).css({overflow:"hidden"}),r&&n.css(h,c?isNaN(s)?"-"+s:-s:s),u[h]=(r?c?"+=":"-=":c?"-=":"+=")+s,n.animate(u,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){"hide"===o&&n.hide(),t.effects.restore(n,a),t.effects.removeWrapper(n),i()}})}})(jQuery); \ No newline at end of file +(function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)})(function(t){function e(t){for(var e=t.css("visibility");"inherit"===e;)t=t.parent(),e=t.css("visibility");return"hidden"!==e}function i(t){for(var e,i;t.length&&t[0]!==document;){if(e=t.css("position"),("absolute"===e||"relative"===e||"fixed"===e)&&(i=parseInt(t.css("zIndex"),10),!isNaN(i)&&0!==i))return i;t=t.parent()}return 0}function s(){this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},t.extend(this._defaults,this.regional[""]),this.regional.en=t.extend(!0,{},this.regional[""]),this.regional["en-US"]=t.extend(!0,{},this.regional.en),this.dpDiv=n(t("
    "))}function n(e){var i="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return e.on("mouseout",i,function(){t(this).removeClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).removeClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).removeClass("ui-datepicker-next-hover")}).on("mouseover",i,o)}function o(){t.datepicker._isDisabledDatepicker(p.inline?p.dpDiv.parent()[0]:p.input[0])||(t(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),t(this).addClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).addClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).addClass("ui-datepicker-next-hover"))}function a(e,i){t.extend(e,i);for(var s in i)null==i[s]&&(e[s]=i[s]);return e}function r(t){return function(){var e=this.element.val();t.apply(this,arguments),this._refresh(),e!==this.element.val()&&this._trigger("change")}}t.ui=t.ui||{},t.ui.version="1.12.1";var h=0,l=Array.prototype.slice;t.cleanData=function(e){return function(i){var s,n,o;for(o=0;null!=(n=i[o]);o++)try{s=t._data(n,"events"),s&&s.remove&&t(n).triggerHandler("remove")}catch(a){}e(i)}}(t.cleanData),t.widget=function(e,i,s){var n,o,a,r={},h=e.split(".")[0];e=e.split(".")[1];var l=h+"-"+e;return s||(s=i,i=t.Widget),t.isArray(s)&&(s=t.extend.apply(null,[{}].concat(s))),t.expr[":"][l.toLowerCase()]=function(e){return!!t.data(e,l)},t[h]=t[h]||{},n=t[h][e],o=t[h][e]=function(t,e){return this._createWidget?(arguments.length&&this._createWidget(t,e),void 0):new o(t,e)},t.extend(o,n,{version:s.version,_proto:t.extend({},s),_childConstructors:[]}),a=new i,a.options=t.widget.extend({},a.options),t.each(s,function(e,s){return t.isFunction(s)?(r[e]=function(){function t(){return i.prototype[e].apply(this,arguments)}function n(t){return i.prototype[e].apply(this,t)}return function(){var e,i=this._super,o=this._superApply;return this._super=t,this._superApply=n,e=s.apply(this,arguments),this._super=i,this._superApply=o,e}}(),void 0):(r[e]=s,void 0)}),o.prototype=t.widget.extend(a,{widgetEventPrefix:n?a.widgetEventPrefix||e:e},r,{constructor:o,namespace:h,widgetName:e,widgetFullName:l}),n?(t.each(n._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete n._childConstructors):i._childConstructors.push(o),t.widget.bridge(e,o),o},t.widget.extend=function(e){for(var i,s,n=l.call(arguments,1),o=0,a=n.length;a>o;o++)for(i in n[o])s=n[o][i],n[o].hasOwnProperty(i)&&void 0!==s&&(e[i]=t.isPlainObject(s)?t.isPlainObject(e[i])?t.widget.extend({},e[i],s):t.widget.extend({},s):s);return e},t.widget.bridge=function(e,i){var s=i.prototype.widgetFullName||e;t.fn[e]=function(n){var o="string"==typeof n,a=l.call(arguments,1),r=this;return o?this.length||"instance"!==n?this.each(function(){var i,o=t.data(this,s);return"instance"===n?(r=o,!1):o?t.isFunction(o[n])&&"_"!==n.charAt(0)?(i=o[n].apply(o,a),i!==o&&void 0!==i?(r=i&&i.jquery?r.pushStack(i.get()):i,!1):void 0):t.error("no such method '"+n+"' for "+e+" widget instance"):t.error("cannot call methods on "+e+" prior to initialization; "+"attempted to call method '"+n+"'")}):r=void 0:(a.length&&(n=t.widget.extend.apply(null,[n].concat(a))),this.each(function(){var e=t.data(this,s);e?(e.option(n||{}),e._init&&e._init()):t.data(this,s,new i(n,this))})),r}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
    ",options:{classes:{},disabled:!1,create:null},_createWidget:function(e,i){i=t(i||this.defaultElement||this)[0],this.element=t(i),this.uuid=h++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=t(),this.hoverable=t(),this.focusable=t(),this.classesElementLookup={},i!==this&&(t.data(i,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===i&&this.destroy()}}),this.document=t(i.style?i.ownerDocument:i.document||i),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){var e=this;this._destroy(),t.each(this.classesElementLookup,function(t,i){e._removeClass(i,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:t.noop,widget:function(){return this.element},option:function(e,i){var s,n,o,a=e;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof e)if(a={},s=e.split("."),e=s.shift(),s.length){for(n=a[e]=t.widget.extend({},this.options[e]),o=0;s.length-1>o;o++)n[s[o]]=n[s[o]]||{},n=n[s[o]];if(e=s.pop(),1===arguments.length)return void 0===n[e]?null:n[e];n[e]=i}else{if(1===arguments.length)return void 0===this.options[e]?null:this.options[e];a[e]=i}return this._setOptions(a),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return"classes"===t&&this._setOptionClasses(e),this.options[t]=e,"disabled"===t&&this._setOptionDisabled(e),this},_setOptionClasses:function(e){var i,s,n;for(i in e)n=this.classesElementLookup[i],e[i]!==this.options.classes[i]&&n&&n.length&&(s=t(n.get()),this._removeClass(n,i),s.addClass(this._classes({element:s,keys:i,classes:e,add:!0})))},_setOptionDisabled:function(t){this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!!t),t&&(this._removeClass(this.hoverable,null,"ui-state-hover"),this._removeClass(this.focusable,null,"ui-state-focus"))},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_classes:function(e){function i(i,o){var a,r;for(r=0;i.length>r;r++)a=n.classesElementLookup[i[r]]||t(),a=e.add?t(t.unique(a.get().concat(e.element.get()))):t(a.not(e.element).get()),n.classesElementLookup[i[r]]=a,s.push(i[r]),o&&e.classes[i[r]]&&s.push(e.classes[i[r]])}var s=[],n=this;return e=t.extend({element:this.element,classes:this.options.classes||{}},e),this._on(e.element,{remove:"_untrackClassesElement"}),e.keys&&i(e.keys.match(/\S+/g)||[],!0),e.extra&&i(e.extra.match(/\S+/g)||[]),s.join(" ")},_untrackClassesElement:function(e){var i=this;t.each(i.classesElementLookup,function(s,n){-1!==t.inArray(e.target,n)&&(i.classesElementLookup[s]=t(n.not(e.target).get()))})},_removeClass:function(t,e,i){return this._toggleClass(t,e,i,!1)},_addClass:function(t,e,i){return this._toggleClass(t,e,i,!0)},_toggleClass:function(t,e,i,s){s="boolean"==typeof s?s:i;var n="string"==typeof t||null===t,o={extra:n?e:i,keys:n?t:e,element:n?this.element:t,add:s};return o.element.toggleClass(this._classes(o),s),this},_on:function(e,i,s){var n,o=this;"boolean"!=typeof e&&(s=i,i=e,e=!1),s?(i=n=t(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),t.each(s,function(s,a){function r(){return e||o.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof a?o[a]:a).apply(o,arguments):void 0}"string"!=typeof a&&(r.guid=a.guid=a.guid||r.guid||t.guid++);var h=s.match(/^([\w:-]*)\s*(.*)$/),l=h[1]+o.eventNamespace,c=h[2];c?n.on(l,c,r):i.on(l,r)})},_off:function(e,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.off(i).off(i),this.bindings=t(this.bindings.not(e).get()),this.focusable=t(this.focusable.not(e).get()),this.hoverable=t(this.hoverable.not(e).get())},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){this._addClass(t(e.currentTarget),null,"ui-state-hover")},mouseleave:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){this._addClass(t(e.currentTarget),null,"ui-state-focus")},focusout:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}}),t.widget,function(){function e(t,e,i){return[parseFloat(t[0])*(u.test(t[0])?e/100:1),parseFloat(t[1])*(u.test(t[1])?i/100:1)]}function i(e,i){return parseInt(t.css(e,i),10)||0}function s(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}var n,o=Math.max,a=Math.abs,r=/left|center|right/,h=/top|center|bottom/,l=/[\+\-]\d+(\.[\d]+)?%?/,c=/^\w+/,u=/%$/,d=t.fn.position;t.position={scrollbarWidth:function(){if(void 0!==n)return n;var e,i,s=t("
    "),o=s.children()[0];return t("body").append(s),e=o.offsetWidth,s.css("overflow","scroll"),i=o.offsetWidth,e===i&&(i=s[0].clientWidth),s.remove(),n=e-i},getScrollInfo:function(e){var i=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),s=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),n="scroll"===i||"auto"===i&&e.widthi?"left":e>0?"right":"center",vertical:0>r?"top":s>0?"bottom":"middle"};l>p&&p>a(e+i)&&(u.horizontal="center"),c>f&&f>a(s+r)&&(u.vertical="middle"),u.important=o(a(e),a(i))>o(a(s),a(r))?"horizontal":"vertical",n.using.call(this,t,u)}),h.offset(t.extend(D,{using:r}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=t.left-e.collisionPosition.marginLeft,h=n-r,l=r+e.collisionWidth-a-n;e.collisionWidth>a?h>0&&0>=l?(i=t.left+h+e.collisionWidth-a-n,t.left+=h-i):t.left=l>0&&0>=h?n:h>l?n+a-e.collisionWidth:n:h>0?t.left+=h:l>0?t.left-=l:t.left=o(t.left-r,t.left)},top:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollTop:s.offset.top,a=e.within.height,r=t.top-e.collisionPosition.marginTop,h=n-r,l=r+e.collisionHeight-a-n;e.collisionHeight>a?h>0&&0>=l?(i=t.top+h+e.collisionHeight-a-n,t.top+=h-i):t.top=l>0&&0>=h?n:h>l?n+a-e.collisionHeight:n:h>0?t.top+=h:l>0?t.top-=l:t.top=o(t.top-r,t.top)}},flip:{left:function(t,e){var i,s,n=e.within,o=n.offset.left+n.scrollLeft,r=n.width,h=n.isWindow?n.scrollLeft:n.offset.left,l=t.left-e.collisionPosition.marginLeft,c=l-h,u=l+e.collisionWidth-r-h,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];0>c?(i=t.left+d+p+f+e.collisionWidth-r-o,(0>i||a(c)>i)&&(t.left+=d+p+f)):u>0&&(s=t.left-e.collisionPosition.marginLeft+d+p+f-h,(s>0||u>a(s))&&(t.left+=d+p+f))},top:function(t,e){var i,s,n=e.within,o=n.offset.top+n.scrollTop,r=n.height,h=n.isWindow?n.scrollTop:n.offset.top,l=t.top-e.collisionPosition.marginTop,c=l-h,u=l+e.collisionHeight-r-h,d="top"===e.my[1],p=d?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,f="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];0>c?(s=t.top+p+f+g+e.collisionHeight-r-o,(0>s||a(c)>s)&&(t.top+=p+f+g)):u>0&&(i=t.top-e.collisionPosition.marginTop+p+f+g-h,(i>0||u>a(i))&&(t.top+=p+f+g))}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}}}(),t.ui.position,t.extend(t.expr[":"],{data:t.expr.createPseudo?t.expr.createPseudo(function(e){return function(i){return!!t.data(i,e)}}):function(e,i,s){return!!t.data(e,s[3])}}),t.fn.extend({disableSelection:function(){var t="onselectstart"in document.createElement("div")?"selectstart":"mousedown";return function(){return this.on(t+".ui-disableSelection",function(t){t.preventDefault()})}}(),enableSelection:function(){return this.off(".ui-disableSelection")}}),t.ui.focusable=function(i,s){var n,o,a,r,h,l=i.nodeName.toLowerCase();return"area"===l?(n=i.parentNode,o=n.name,i.href&&o&&"map"===n.nodeName.toLowerCase()?(a=t("img[usemap='#"+o+"']"),a.length>0&&a.is(":visible")):!1):(/^(input|select|textarea|button|object)$/.test(l)?(r=!i.disabled,r&&(h=t(i).closest("fieldset")[0],h&&(r=!h.disabled))):r="a"===l?i.href||s:s,r&&t(i).is(":visible")&&e(t(i)))},t.extend(t.expr[":"],{focusable:function(e){return t.ui.focusable(e,null!=t.attr(e,"tabindex"))}}),t.ui.focusable,t.fn.form=function(){return"string"==typeof this[0].form?this.closest("form"):t(this[0].form)},t.ui.formResetMixin={_formResetHandler:function(){var e=t(this);setTimeout(function(){var i=e.data("ui-form-reset-instances");t.each(i,function(){this.refresh()})})},_bindFormResetHandler:function(){if(this.form=this.element.form(),this.form.length){var t=this.form.data("ui-form-reset-instances")||[];t.length||this.form.on("reset.ui-form-reset",this._formResetHandler),t.push(this),this.form.data("ui-form-reset-instances",t)}},_unbindFormResetHandler:function(){if(this.form.length){var e=this.form.data("ui-form-reset-instances");e.splice(t.inArray(this,e),1),e.length?this.form.data("ui-form-reset-instances",e):this.form.removeData("ui-form-reset-instances").off("reset.ui-form-reset")}}},"1.7"===t.fn.jquery.substring(0,3)&&(t.each(["Width","Height"],function(e,i){function s(e,i,s,o){return t.each(n,function(){i-=parseFloat(t.css(e,"padding"+this))||0,s&&(i-=parseFloat(t.css(e,"border"+this+"Width"))||0),o&&(i-=parseFloat(t.css(e,"margin"+this))||0)}),i}var n="Width"===i?["Left","Right"]:["Top","Bottom"],o=i.toLowerCase(),a={innerWidth:t.fn.innerWidth,innerHeight:t.fn.innerHeight,outerWidth:t.fn.outerWidth,outerHeight:t.fn.outerHeight};t.fn["inner"+i]=function(e){return void 0===e?a["inner"+i].call(this):this.each(function(){t(this).css(o,s(this,e)+"px")})},t.fn["outer"+i]=function(e,n){return"number"!=typeof e?a["outer"+i].call(this,e):this.each(function(){t(this).css(o,s(this,e,!0,n)+"px")})}}),t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.ui.keyCode={BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38},t.ui.escapeSelector=function(){var t=/([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g;return function(e){return e.replace(t,"\\$1")}}(),t.fn.labels=function(){var e,i,s,n,o;return this[0].labels&&this[0].labels.length?this.pushStack(this[0].labels):(n=this.eq(0).parents("label"),s=this.attr("id"),s&&(e=this.eq(0).parents().last(),o=e.add(e.length?e.siblings():this.siblings()),i="label[for='"+t.ui.escapeSelector(s)+"']",n=n.add(o.find(i).addBack(i))),this.pushStack(n))},t.fn.scrollParent=function(e){var i=this.css("position"),s="absolute"===i,n=e?/(auto|scroll|hidden)/:/(auto|scroll)/,o=this.parents().filter(function(){var e=t(this);return s&&"static"===e.css("position")?!1:n.test(e.css("overflow")+e.css("overflow-y")+e.css("overflow-x"))}).eq(0);return"fixed"!==i&&o.length?o:t(this[0].ownerDocument||document)},t.extend(t.expr[":"],{tabbable:function(e){var i=t.attr(e,"tabindex"),s=null!=i;return(!s||i>=0)&&t.ui.focusable(e,s)}}),t.fn.extend({uniqueId:function(){var t=0;return function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++t)})}}(),removeUniqueId:function(){return this.each(function(){/^ui-id-\d+$/.test(this.id)&&t(this).removeAttr("id")})}}),t.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase());var c=!1;t(document).on("mouseup",function(){c=!1}),t.widget("ui.mouse",{version:"1.12.1",options:{cancel:"input, textarea, button, select, option",distance:1,delay:0},_mouseInit:function(){var e=this;this.element.on("mousedown."+this.widgetName,function(t){return e._mouseDown(t)}).on("click."+this.widgetName,function(i){return!0===t.data(i.target,e.widgetName+".preventClickEvent")?(t.removeData(i.target,e.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):void 0}),this.started=!1},_mouseDestroy:function(){this.element.off("."+this.widgetName),this._mouseMoveDelegate&&this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(e){if(!c){this._mouseMoved=!1,this._mouseStarted&&this._mouseUp(e),this._mouseDownEvent=e;var i=this,s=1===e.which,n="string"==typeof this.options.cancel&&e.target.nodeName?t(e.target).closest(this.options.cancel).length:!1;return s&&!n&&this._mouseCapture(e)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){i.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(e)!==!1,!this._mouseStarted)?(e.preventDefault(),!0):(!0===t.data(e.target,this.widgetName+".preventClickEvent")&&t.removeData(e.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(t){return i._mouseMove(t)},this._mouseUpDelegate=function(t){return i._mouseUp(t)},this.document.on("mousemove."+this.widgetName,this._mouseMoveDelegate).on("mouseup."+this.widgetName,this._mouseUpDelegate),e.preventDefault(),c=!0,!0)):!0}},_mouseMove:function(e){if(this._mouseMoved){if(t.ui.ie&&(!document.documentMode||9>document.documentMode)&&!e.button)return this._mouseUp(e);if(!e.which)if(e.originalEvent.altKey||e.originalEvent.ctrlKey||e.originalEvent.metaKey||e.originalEvent.shiftKey)this.ignoreMissingWhich=!0;else if(!this.ignoreMissingWhich)return this._mouseUp(e)}return(e.which||e.button)&&(this._mouseMoved=!0),this._mouseStarted?(this._mouseDrag(e),e.preventDefault()):(this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,e)!==!1,this._mouseStarted?this._mouseDrag(e):this._mouseUp(e)),!this._mouseStarted)},_mouseUp:function(e){this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,e.target===this._mouseDownEvent.target&&t.data(e.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(e)),this._mouseDelayTimer&&(clearTimeout(this._mouseDelayTimer),delete this._mouseDelayTimer),this.ignoreMissingWhich=!1,c=!1,e.preventDefault()},_mouseDistanceMet:function(t){return Math.max(Math.abs(this._mouseDownEvent.pageX-t.pageX),Math.abs(this._mouseDownEvent.pageY-t.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),t.ui.plugin={add:function(e,i,s){var n,o=t.ui[e].prototype;for(n in s)o.plugins[n]=o.plugins[n]||[],o.plugins[n].push([i,s[n]])},call:function(t,e,i,s){var n,o=t.plugins[e];if(o&&(s||t.element[0].parentNode&&11!==t.element[0].parentNode.nodeType))for(n=0;o.length>n;n++)t.options[o[n][0]]&&o[n][1].apply(t.element,i)}},t.ui.safeActiveElement=function(t){var e;try{e=t.activeElement}catch(i){e=t.body}return e||(e=t.body),e.nodeName||(e=t.body),e},t.ui.safeBlur=function(e){e&&"body"!==e.nodeName.toLowerCase()&&t(e).trigger("blur")},t.widget("ui.draggable",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1,drag:null,start:null,stop:null},_create:function(){"original"===this.options.helper&&this._setPositionRelative(),this.options.addClasses&&this._addClass("ui-draggable"),this._setHandleClassName(),this._mouseInit()},_setOption:function(t,e){this._super(t,e),"handle"===t&&(this._removeHandleClassName(),this._setHandleClassName())},_destroy:function(){return(this.helper||this.element).is(".ui-draggable-dragging")?(this.destroyOnClear=!0,void 0):(this._removeHandleClassName(),this._mouseDestroy(),void 0)},_mouseCapture:function(e){var i=this.options;return this.helper||i.disabled||t(e.target).closest(".ui-resizable-handle").length>0?!1:(this.handle=this._getHandle(e),this.handle?(this._blurActiveElement(e),this._blockFrames(i.iframeFix===!0?"iframe":i.iframeFix),!0):!1)},_blockFrames:function(e){this.iframeBlocks=this.document.find(e).map(function(){var e=t(this);return t("
    ").css("position","absolute").appendTo(e.parent()).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()).offset(e.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_blurActiveElement:function(e){var i=t.ui.safeActiveElement(this.document[0]),s=t(e.target);s.closest(i).length||t.ui.safeBlur(i)},_mouseStart:function(e){var i=this.options;return this.helper=this._createHelper(e),this._addClass(this.helper,"ui-draggable-dragging"),this._cacheHelperProportions(),t.ui.ddmanager&&(t.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(!0),this.offsetParent=this.helper.offsetParent(),this.hasFixedAncestor=this.helper.parents().filter(function(){return"fixed"===t(this).css("position")}).length>0,this.positionAbs=this.element.offset(),this._refreshOffsets(e),this.originalPosition=this.position=this._generatePosition(e,!1),this.originalPageX=e.pageX,this.originalPageY=e.pageY,i.cursorAt&&this._adjustOffsetFromHelper(i.cursorAt),this._setContainment(),this._trigger("start",e)===!1?(this._clear(),!1):(this._cacheHelperProportions(),t.ui.ddmanager&&!i.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this._mouseDrag(e,!0),t.ui.ddmanager&&t.ui.ddmanager.dragStart(this,e),!0)},_refreshOffsets:function(t){this.offset={top:this.positionAbs.top-this.margins.top,left:this.positionAbs.left-this.margins.left,scroll:!1,parent:this._getParentOffset(),relative:this._getRelativeOffset()},this.offset.click={left:t.pageX-this.offset.left,top:t.pageY-this.offset.top}},_mouseDrag:function(e,i){if(this.hasFixedAncestor&&(this.offset.parent=this._getParentOffset()),this.position=this._generatePosition(e,!0),this.positionAbs=this._convertPositionTo("absolute"),!i){var s=this._uiHash();if(this._trigger("drag",e,s)===!1)return this._mouseUp(new t.Event("mouseup",e)),!1;this.position=s.position}return this.helper[0].style.left=this.position.left+"px",this.helper[0].style.top=this.position.top+"px",t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),!1},_mouseStop:function(e){var i=this,s=!1;return t.ui.ddmanager&&!this.options.dropBehaviour&&(s=t.ui.ddmanager.drop(this,e)),this.dropped&&(s=this.dropped,this.dropped=!1),"invalid"===this.options.revert&&!s||"valid"===this.options.revert&&s||this.options.revert===!0||t.isFunction(this.options.revert)&&this.options.revert.call(this.element,s)?t(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){i._trigger("stop",e)!==!1&&i._clear()}):this._trigger("stop",e)!==!1&&this._clear(),!1},_mouseUp:function(e){return this._unblockFrames(),t.ui.ddmanager&&t.ui.ddmanager.dragStop(this,e),this.handleElement.is(e.target)&&this.element.trigger("focus"),t.ui.mouse.prototype._mouseUp.call(this,e)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp(new t.Event("mouseup",{target:this.element[0]})):this._clear(),this},_getHandle:function(e){return this.options.handle?!!t(e.target).closest(this.element.find(this.options.handle)).length:!0},_setHandleClassName:function(){this.handleElement=this.options.handle?this.element.find(this.options.handle):this.element,this._addClass(this.handleElement,"ui-draggable-handle")},_removeHandleClassName:function(){this._removeClass(this.handleElement,"ui-draggable-handle")},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper),n=s?t(i.helper.apply(this.element[0],[e])):"clone"===i.helper?this.element.clone().removeAttr("id"):this.element;return n.parents("body").length||n.appendTo("parent"===i.appendTo?this.element[0].parentNode:i.appendTo),s&&n[0]===this.element[0]&&this._setPositionRelative(),n[0]===this.element[0]||/(fixed|absolute)/.test(n.css("position"))||n.css("position","absolute"),n},_setPositionRelative:function(){/^(?:r|a|f)/.test(this.element.css("position"))||(this.element[0].style.position="relative")},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_isRootNode:function(t){return/(html|body)/i.test(t.tagName)||t===this.document[0]},_getParentOffset:function(){var e=this.offsetParent.offset(),i=this.document[0];return"absolute"===this.cssPosition&&this.scrollParent[0]!==i&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),this._isRootNode(this.offsetParent[0])&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"!==this.cssPosition)return{top:0,left:0};var t=this.element.position(),e=this._isRootNode(this.scrollParent[0]);return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+(e?0:this.scrollParent.scrollTop()),left:t.left-(parseInt(this.helper.css("left"),10)||0)+(e?0:this.scrollParent.scrollLeft())}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options,o=this.document[0];return this.relativeContainer=null,n.containment?"window"===n.containment?(this.containment=[t(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,t(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,t(window).scrollLeft()+t(window).width()-this.helperProportions.width-this.margins.left,t(window).scrollTop()+(t(window).height()||o.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],void 0):"document"===n.containment?(this.containment=[0,0,t(o).width()-this.helperProportions.width-this.margins.left,(t(o).height()||o.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],void 0):n.containment.constructor===Array?(this.containment=n.containment,void 0):("parent"===n.containment&&(n.containment=this.helper[0].parentNode),i=t(n.containment),s=i[0],s&&(e=/(scroll|auto)/.test(i.css("overflow")),this.containment=[(parseInt(i.css("borderLeftWidth"),10)||0)+(parseInt(i.css("paddingLeft"),10)||0),(parseInt(i.css("borderTopWidth"),10)||0)+(parseInt(i.css("paddingTop"),10)||0),(e?Math.max(s.scrollWidth,s.offsetWidth):s.offsetWidth)-(parseInt(i.css("borderRightWidth"),10)||0)-(parseInt(i.css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(e?Math.max(s.scrollHeight,s.offsetHeight):s.offsetHeight)-(parseInt(i.css("borderBottomWidth"),10)||0)-(parseInt(i.css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relativeContainer=i),void 0):(this.containment=null,void 0) +},_convertPositionTo:function(t,e){e||(e=this.position);var i="absolute"===t?1:-1,s=this._isRootNode(this.scrollParent[0]);return{top:e.top+this.offset.relative.top*i+this.offset.parent.top*i-("fixed"===this.cssPosition?-this.offset.scroll.top:s?0:this.offset.scroll.top)*i,left:e.left+this.offset.relative.left*i+this.offset.parent.left*i-("fixed"===this.cssPosition?-this.offset.scroll.left:s?0:this.offset.scroll.left)*i}},_generatePosition:function(t,e){var i,s,n,o,a=this.options,r=this._isRootNode(this.scrollParent[0]),h=t.pageX,l=t.pageY;return r&&this.offset.scroll||(this.offset.scroll={top:this.scrollParent.scrollTop(),left:this.scrollParent.scrollLeft()}),e&&(this.containment&&(this.relativeContainer?(s=this.relativeContainer.offset(),i=[this.containment[0]+s.left,this.containment[1]+s.top,this.containment[2]+s.left,this.containment[3]+s.top]):i=this.containment,t.pageX-this.offset.click.lefti[2]&&(h=i[2]+this.offset.click.left),t.pageY-this.offset.click.top>i[3]&&(l=i[3]+this.offset.click.top)),a.grid&&(n=a.grid[1]?this.originalPageY+Math.round((l-this.originalPageY)/a.grid[1])*a.grid[1]:this.originalPageY,l=i?n-this.offset.click.top>=i[1]||n-this.offset.click.top>i[3]?n:n-this.offset.click.top>=i[1]?n-a.grid[1]:n+a.grid[1]:n,o=a.grid[0]?this.originalPageX+Math.round((h-this.originalPageX)/a.grid[0])*a.grid[0]:this.originalPageX,h=i?o-this.offset.click.left>=i[0]||o-this.offset.click.left>i[2]?o:o-this.offset.click.left>=i[0]?o-a.grid[0]:o+a.grid[0]:o),"y"===a.axis&&(h=this.originalPageX),"x"===a.axis&&(l=this.originalPageY)),{top:l-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.offset.scroll.top:r?0:this.offset.scroll.top),left:h-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.offset.scroll.left:r?0:this.offset.scroll.left)}},_clear:function(){this._removeClass(this.helper,"ui-draggable-dragging"),this.helper[0]===this.element[0]||this.cancelHelperRemoval||this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1,this.destroyOnClear&&this.destroy()},_trigger:function(e,i,s){return s=s||this._uiHash(),t.ui.plugin.call(this,e,[i,s,this],!0),/^(drag|start|stop)/.test(e)&&(this.positionAbs=this._convertPositionTo("absolute"),s.offset=this.positionAbs),t.Widget.prototype._trigger.call(this,e,i,s)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),t.ui.plugin.add("draggable","connectToSortable",{start:function(e,i,s){var n=t.extend({},i,{item:s.element});s.sortables=[],t(s.options.connectToSortable).each(function(){var i=t(this).sortable("instance");i&&!i.options.disabled&&(s.sortables.push(i),i.refreshPositions(),i._trigger("activate",e,n))})},stop:function(e,i,s){var n=t.extend({},i,{item:s.element});s.cancelHelperRemoval=!1,t.each(s.sortables,function(){var t=this;t.isOver?(t.isOver=0,s.cancelHelperRemoval=!0,t.cancelHelperRemoval=!1,t._storedCSS={position:t.placeholder.css("position"),top:t.placeholder.css("top"),left:t.placeholder.css("left")},t._mouseStop(e),t.options.helper=t.options._helper):(t.cancelHelperRemoval=!0,t._trigger("deactivate",e,n))})},drag:function(e,i,s){t.each(s.sortables,function(){var n=!1,o=this;o.positionAbs=s.positionAbs,o.helperProportions=s.helperProportions,o.offset.click=s.offset.click,o._intersectsWith(o.containerCache)&&(n=!0,t.each(s.sortables,function(){return this.positionAbs=s.positionAbs,this.helperProportions=s.helperProportions,this.offset.click=s.offset.click,this!==o&&this._intersectsWith(this.containerCache)&&t.contains(o.element[0],this.element[0])&&(n=!1),n})),n?(o.isOver||(o.isOver=1,s._parent=i.helper.parent(),o.currentItem=i.helper.appendTo(o.element).data("ui-sortable-item",!0),o.options._helper=o.options.helper,o.options.helper=function(){return i.helper[0]},e.target=o.currentItem[0],o._mouseCapture(e,!0),o._mouseStart(e,!0,!0),o.offset.click.top=s.offset.click.top,o.offset.click.left=s.offset.click.left,o.offset.parent.left-=s.offset.parent.left-o.offset.parent.left,o.offset.parent.top-=s.offset.parent.top-o.offset.parent.top,s._trigger("toSortable",e),s.dropped=o.element,t.each(s.sortables,function(){this.refreshPositions()}),s.currentItem=s.element,o.fromOutside=s),o.currentItem&&(o._mouseDrag(e),i.position=o.position)):o.isOver&&(o.isOver=0,o.cancelHelperRemoval=!0,o.options._revert=o.options.revert,o.options.revert=!1,o._trigger("out",e,o._uiHash(o)),o._mouseStop(e,!0),o.options.revert=o.options._revert,o.options.helper=o.options._helper,o.placeholder&&o.placeholder.remove(),i.helper.appendTo(s._parent),s._refreshOffsets(e),i.position=s._generatePosition(e,!0),s._trigger("fromSortable",e),s.dropped=!1,t.each(s.sortables,function(){this.refreshPositions()}))})}}),t.ui.plugin.add("draggable","cursor",{start:function(e,i,s){var n=t("body"),o=s.options;n.css("cursor")&&(o._cursor=n.css("cursor")),n.css("cursor",o.cursor)},stop:function(e,i,s){var n=s.options;n._cursor&&t("body").css("cursor",n._cursor)}}),t.ui.plugin.add("draggable","opacity",{start:function(e,i,s){var n=t(i.helper),o=s.options;n.css("opacity")&&(o._opacity=n.css("opacity")),n.css("opacity",o.opacity)},stop:function(e,i,s){var n=s.options;n._opacity&&t(i.helper).css("opacity",n._opacity)}}),t.ui.plugin.add("draggable","scroll",{start:function(t,e,i){i.scrollParentNotHidden||(i.scrollParentNotHidden=i.helper.scrollParent(!1)),i.scrollParentNotHidden[0]!==i.document[0]&&"HTML"!==i.scrollParentNotHidden[0].tagName&&(i.overflowOffset=i.scrollParentNotHidden.offset())},drag:function(e,i,s){var n=s.options,o=!1,a=s.scrollParentNotHidden[0],r=s.document[0];a!==r&&"HTML"!==a.tagName?(n.axis&&"x"===n.axis||(s.overflowOffset.top+a.offsetHeight-e.pageY=0;d--)h=s.snapElements[d].left-s.margins.left,l=h+s.snapElements[d].width,c=s.snapElements[d].top-s.margins.top,u=c+s.snapElements[d].height,h-g>_||m>l+g||c-g>b||v>u+g||!t.contains(s.snapElements[d].item.ownerDocument,s.snapElements[d].item)?(s.snapElements[d].snapping&&s.options.snap.release&&s.options.snap.release.call(s.element,e,t.extend(s._uiHash(),{snapItem:s.snapElements[d].item})),s.snapElements[d].snapping=!1):("inner"!==f.snapMode&&(n=g>=Math.abs(c-b),o=g>=Math.abs(u-v),a=g>=Math.abs(h-_),r=g>=Math.abs(l-m),n&&(i.position.top=s._convertPositionTo("relative",{top:c-s.helperProportions.height,left:0}).top),o&&(i.position.top=s._convertPositionTo("relative",{top:u,left:0}).top),a&&(i.position.left=s._convertPositionTo("relative",{top:0,left:h-s.helperProportions.width}).left),r&&(i.position.left=s._convertPositionTo("relative",{top:0,left:l}).left)),p=n||o||a||r,"outer"!==f.snapMode&&(n=g>=Math.abs(c-v),o=g>=Math.abs(u-b),a=g>=Math.abs(h-m),r=g>=Math.abs(l-_),n&&(i.position.top=s._convertPositionTo("relative",{top:c,left:0}).top),o&&(i.position.top=s._convertPositionTo("relative",{top:u-s.helperProportions.height,left:0}).top),a&&(i.position.left=s._convertPositionTo("relative",{top:0,left:h}).left),r&&(i.position.left=s._convertPositionTo("relative",{top:0,left:l-s.helperProportions.width}).left)),!s.snapElements[d].snapping&&(n||o||a||r||p)&&s.options.snap.snap&&s.options.snap.snap.call(s.element,e,t.extend(s._uiHash(),{snapItem:s.snapElements[d].item})),s.snapElements[d].snapping=n||o||a||r||p)}}),t.ui.plugin.add("draggable","stack",{start:function(e,i,s){var n,o=s.options,a=t.makeArray(t(o.stack)).sort(function(e,i){return(parseInt(t(e).css("zIndex"),10)||0)-(parseInt(t(i).css("zIndex"),10)||0)});a.length&&(n=parseInt(t(a[0]).css("zIndex"),10)||0,t(a).each(function(e){t(this).css("zIndex",n+e)}),this.css("zIndex",n+a.length))}}),t.ui.plugin.add("draggable","zIndex",{start:function(e,i,s){var n=t(i.helper),o=s.options;n.css("zIndex")&&(o._zIndex=n.css("zIndex")),n.css("zIndex",o.zIndex)},stop:function(e,i,s){var n=s.options;n._zIndex&&t(i.helper).css("zIndex",n._zIndex)}}),t.ui.draggable,t.widget("ui.droppable",{version:"1.12.1",widgetEventPrefix:"drop",options:{accept:"*",addClasses:!0,greedy:!1,scope:"default",tolerance:"intersect",activate:null,deactivate:null,drop:null,out:null,over:null},_create:function(){var e,i=this.options,s=i.accept;this.isover=!1,this.isout=!0,this.accept=t.isFunction(s)?s:function(t){return t.is(s)},this.proportions=function(){return arguments.length?(e=arguments[0],void 0):e?e:e={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight}},this._addToManager(i.scope),i.addClasses&&this._addClass("ui-droppable")},_addToManager:function(e){t.ui.ddmanager.droppables[e]=t.ui.ddmanager.droppables[e]||[],t.ui.ddmanager.droppables[e].push(this)},_splice:function(t){for(var e=0;t.length>e;e++)t[e]===this&&t.splice(e,1)},_destroy:function(){var e=t.ui.ddmanager.droppables[this.options.scope];this._splice(e)},_setOption:function(e,i){if("accept"===e)this.accept=t.isFunction(i)?i:function(t){return t.is(i)};else if("scope"===e){var s=t.ui.ddmanager.droppables[this.options.scope];this._splice(s),this._addToManager(i)}this._super(e,i)},_activate:function(e){var i=t.ui.ddmanager.current;this._addActiveClass(),i&&this._trigger("activate",e,this.ui(i))},_deactivate:function(e){var i=t.ui.ddmanager.current;this._removeActiveClass(),i&&this._trigger("deactivate",e,this.ui(i))},_over:function(e){var i=t.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this._addHoverClass(),this._trigger("over",e,this.ui(i)))},_out:function(e){var i=t.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this._removeHoverClass(),this._trigger("out",e,this.ui(i)))},_drop:function(e,i){var s=i||t.ui.ddmanager.current,n=!1;return s&&(s.currentItem||s.element)[0]!==this.element[0]?(this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function(){var i=t(this).droppable("instance");return i.options.greedy&&!i.options.disabled&&i.options.scope===s.options.scope&&i.accept.call(i.element[0],s.currentItem||s.element)&&u(s,t.extend(i,{offset:i.element.offset()}),i.options.tolerance,e)?(n=!0,!1):void 0}),n?!1:this.accept.call(this.element[0],s.currentItem||s.element)?(this._removeActiveClass(),this._removeHoverClass(),this._trigger("drop",e,this.ui(s)),this.element):!1):!1},ui:function(t){return{draggable:t.currentItem||t.element,helper:t.helper,position:t.position,offset:t.positionAbs}},_addHoverClass:function(){this._addClass("ui-droppable-hover")},_removeHoverClass:function(){this._removeClass("ui-droppable-hover")},_addActiveClass:function(){this._addClass("ui-droppable-active")},_removeActiveClass:function(){this._removeClass("ui-droppable-active")}});var u=t.ui.intersect=function(){function t(t,e,i){return t>=e&&e+i>t}return function(e,i,s,n){if(!i.offset)return!1;var o=(e.positionAbs||e.position.absolute).left+e.margins.left,a=(e.positionAbs||e.position.absolute).top+e.margins.top,r=o+e.helperProportions.width,h=a+e.helperProportions.height,l=i.offset.left,c=i.offset.top,u=l+i.proportions().width,d=c+i.proportions().height;switch(s){case"fit":return o>=l&&u>=r&&a>=c&&d>=h;case"intersect":return o+e.helperProportions.width/2>l&&u>r-e.helperProportions.width/2&&a+e.helperProportions.height/2>c&&d>h-e.helperProportions.height/2;case"pointer":return t(n.pageY,c,i.proportions().height)&&t(n.pageX,l,i.proportions().width);case"touch":return(a>=c&&d>=a||h>=c&&d>=h||c>a&&h>d)&&(o>=l&&u>=o||r>=l&&u>=r||l>o&&r>u);default:return!1}}}();t.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(e,i){var s,n,o=t.ui.ddmanager.droppables[e.options.scope]||[],a=i?i.type:null,r=(e.currentItem||e.element).find(":data(ui-droppable)").addBack();t:for(s=0;o.length>s;s++)if(!(o[s].options.disabled||e&&!o[s].accept.call(o[s].element[0],e.currentItem||e.element))){for(n=0;r.length>n;n++)if(r[n]===o[s].element[0]){o[s].proportions().height=0;continue t}o[s].visible="none"!==o[s].element.css("display"),o[s].visible&&("mousedown"===a&&o[s]._activate.call(o[s],i),o[s].offset=o[s].element.offset(),o[s].proportions({width:o[s].element[0].offsetWidth,height:o[s].element[0].offsetHeight}))}},drop:function(e,i){var s=!1;return t.each((t.ui.ddmanager.droppables[e.options.scope]||[]).slice(),function(){this.options&&(!this.options.disabled&&this.visible&&u(e,this,this.options.tolerance,i)&&(s=this._drop.call(this,i)||s),!this.options.disabled&&this.visible&&this.accept.call(this.element[0],e.currentItem||e.element)&&(this.isout=!0,this.isover=!1,this._deactivate.call(this,i)))}),s},dragStart:function(e,i){e.element.parentsUntil("body").on("scroll.droppable",function(){e.options.refreshPositions||t.ui.ddmanager.prepareOffsets(e,i)})},drag:function(e,i){e.options.refreshPositions&&t.ui.ddmanager.prepareOffsets(e,i),t.each(t.ui.ddmanager.droppables[e.options.scope]||[],function(){if(!this.options.disabled&&!this.greedyChild&&this.visible){var s,n,o,a=u(e,this,this.options.tolerance,i),r=!a&&this.isover?"isout":a&&!this.isover?"isover":null;r&&(this.options.greedy&&(n=this.options.scope,o=this.element.parents(":data(ui-droppable)").filter(function(){return t(this).droppable("instance").options.scope===n}),o.length&&(s=t(o[0]).droppable("instance"),s.greedyChild="isover"===r)),s&&"isover"===r&&(s.isover=!1,s.isout=!0,s._out.call(s,i)),this[r]=!0,this["isout"===r?"isover":"isout"]=!1,this["isover"===r?"_over":"_out"].call(this,i),s&&"isout"===r&&(s.isout=!1,s.isover=!0,s._over.call(s,i)))}})},dragStop:function(e,i){e.element.parentsUntil("body").off("scroll.droppable"),e.options.refreshPositions||t.ui.ddmanager.prepareOffsets(e,i)}},t.uiBackCompat!==!1&&t.widget("ui.droppable",t.ui.droppable,{options:{hoverClass:!1,activeClass:!1},_addActiveClass:function(){this._super(),this.options.activeClass&&this.element.addClass(this.options.activeClass)},_removeActiveClass:function(){this._super(),this.options.activeClass&&this.element.removeClass(this.options.activeClass)},_addHoverClass:function(){this._super(),this.options.hoverClass&&this.element.addClass(this.options.hoverClass)},_removeHoverClass:function(){this._super(),this.options.hoverClass&&this.element.removeClass(this.options.hoverClass)}}),t.ui.droppable,t.widget("ui.resizable",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,classes:{"ui-resizable-se":"ui-icon ui-icon-gripsmall-diagonal-se"},containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:90,resize:null,start:null,stop:null},_num:function(t){return parseFloat(t)||0},_isNumber:function(t){return!isNaN(parseFloat(t))},_hasScroll:function(e,i){if("hidden"===t(e).css("overflow"))return!1;var s=i&&"left"===i?"scrollLeft":"scrollTop",n=!1;return e[s]>0?!0:(e[s]=1,n=e[s]>0,e[s]=0,n)},_create:function(){var e,i=this.options,s=this;this._addClass("ui-resizable"),t.extend(this,{_aspectRatio:!!i.aspectRatio,aspectRatio:i.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:i.helper||i.ghost||i.animate?i.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/^(canvas|textarea|input|select|button|img)$/i)&&(this.element.wrap(t("
    ").css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("ui-resizable",this.element.resizable("instance")),this.elementIsWrapper=!0,e={marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom"),marginLeft:this.originalElement.css("marginLeft")},this.element.css(e),this.originalElement.css("margin",0),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css(e),this._proportionallyResize()),this._setupHandles(),i.autoHide&&t(this.element).on("mouseenter",function(){i.disabled||(s._removeClass("ui-resizable-autohide"),s._handles.show())}).on("mouseleave",function(){i.disabled||s.resizing||(s._addClass("ui-resizable-autohide"),s._handles.hide())}),this._mouseInit()},_destroy:function(){this._mouseDestroy();var e,i=function(e){t(e).removeData("resizable").removeData("ui-resizable").off(".resizable").find(".ui-resizable-handle").remove()};return this.elementIsWrapper&&(i(this.element),e=this.element,this.originalElement.css({position:e.css("position"),width:e.outerWidth(),height:e.outerHeight(),top:e.css("top"),left:e.css("left")}).insertAfter(e),e.remove()),this.originalElement.css("resize",this.originalResizeStyle),i(this.originalElement),this},_setOption:function(t,e){switch(this._super(t,e),t){case"handles":this._removeHandles(),this._setupHandles();break;default:}},_setupHandles:function(){var e,i,s,n,o,a=this.options,r=this;if(this.handles=a.handles||(t(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se"),this._handles=t(),this.handles.constructor===String)for("all"===this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw"),s=this.handles.split(","),this.handles={},i=0;s.length>i;i++)e=t.trim(s[i]),n="ui-resizable-"+e,o=t("
    "),this._addClass(o,"ui-resizable-handle "+n),o.css({zIndex:a.zIndex}),this.handles[e]=".ui-resizable-"+e,this.element.append(o);this._renderAxis=function(e){var i,s,n,o;e=e||this.element;for(i in this.handles)this.handles[i].constructor===String?this.handles[i]=this.element.children(this.handles[i]).first().show():(this.handles[i].jquery||this.handles[i].nodeType)&&(this.handles[i]=t(this.handles[i]),this._on(this.handles[i],{mousedown:r._mouseDown})),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/^(textarea|input|select|button)$/i)&&(s=t(this.handles[i],this.element),o=/sw|ne|nw|se|n|s/.test(i)?s.outerHeight():s.outerWidth(),n=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join(""),e.css(n,o),this._proportionallyResize()),this._handles=this._handles.add(this.handles[i])},this._renderAxis(this.element),this._handles=this._handles.add(this.element.find(".ui-resizable-handle")),this._handles.disableSelection(),this._handles.on("mouseover",function(){r.resizing||(this.className&&(o=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),r.axis=o&&o[1]?o[1]:"se")}),a.autoHide&&(this._handles.hide(),this._addClass("ui-resizable-autohide"))},_removeHandles:function(){this._handles.remove()},_mouseCapture:function(e){var i,s,n=!1;for(i in this.handles)s=t(this.handles[i])[0],(s===e.target||t.contains(s,e.target))&&(n=!0);return!this.options.disabled&&n},_mouseStart:function(e){var i,s,n,o=this.options,a=this.element;return this.resizing=!0,this._renderProxy(),i=this._num(this.helper.css("left")),s=this._num(this.helper.css("top")),o.containment&&(i+=t(o.containment).scrollLeft()||0,s+=t(o.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:i,top:s},this.size=this._helper?{width:this.helper.width(),height:this.helper.height()}:{width:a.width(),height:a.height()},this.originalSize=this._helper?{width:a.outerWidth(),height:a.outerHeight()}:{width:a.width(),height:a.height()},this.sizeDiff={width:a.outerWidth()-a.width(),height:a.outerHeight()-a.height()},this.originalPosition={left:i,top:s},this.originalMousePosition={left:e.pageX,top:e.pageY},this.aspectRatio="number"==typeof o.aspectRatio?o.aspectRatio:this.originalSize.width/this.originalSize.height||1,n=t(".ui-resizable-"+this.axis).css("cursor"),t("body").css("cursor","auto"===n?this.axis+"-resize":n),this._addClass("ui-resizable-resizing"),this._propagate("start",e),!0},_mouseDrag:function(e){var i,s,n=this.originalMousePosition,o=this.axis,a=e.pageX-n.left||0,r=e.pageY-n.top||0,h=this._change[o];return this._updatePrevProperties(),h?(i=h.apply(this,[e,a,r]),this._updateVirtualBoundaries(e.shiftKey),(this._aspectRatio||e.shiftKey)&&(i=this._updateRatio(i,e)),i=this._respectSize(i,e),this._updateCache(i),this._propagate("resize",e),s=this._applyChanges(),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),t.isEmptyObject(s)||(this._updatePrevProperties(),this._trigger("resize",e,this.ui()),this._applyChanges()),!1):!1},_mouseStop:function(e){this.resizing=!1;var i,s,n,o,a,r,h,l=this.options,c=this;return this._helper&&(i=this._proportionallyResizeElements,s=i.length&&/textarea/i.test(i[0].nodeName),n=s&&this._hasScroll(i[0],"left")?0:c.sizeDiff.height,o=s?0:c.sizeDiff.width,a={width:c.helper.width()-o,height:c.helper.height()-n},r=parseFloat(c.element.css("left"))+(c.position.left-c.originalPosition.left)||null,h=parseFloat(c.element.css("top"))+(c.position.top-c.originalPosition.top)||null,l.animate||this.element.css(t.extend(a,{top:h,left:r})),c.helper.height(c.size.height),c.helper.width(c.size.width),this._helper&&!l.animate&&this._proportionallyResize()),t("body").css("cursor","auto"),this._removeClass("ui-resizable-resizing"),this._propagate("stop",e),this._helper&&this.helper.remove(),!1},_updatePrevProperties:function(){this.prevPosition={top:this.position.top,left:this.position.left},this.prevSize={width:this.size.width,height:this.size.height}},_applyChanges:function(){var t={};return this.position.top!==this.prevPosition.top&&(t.top=this.position.top+"px"),this.position.left!==this.prevPosition.left&&(t.left=this.position.left+"px"),this.size.width!==this.prevSize.width&&(t.width=this.size.width+"px"),this.size.height!==this.prevSize.height&&(t.height=this.size.height+"px"),this.helper.css(t),t},_updateVirtualBoundaries:function(t){var e,i,s,n,o,a=this.options;o={minWidth:this._isNumber(a.minWidth)?a.minWidth:0,maxWidth:this._isNumber(a.maxWidth)?a.maxWidth:1/0,minHeight:this._isNumber(a.minHeight)?a.minHeight:0,maxHeight:this._isNumber(a.maxHeight)?a.maxHeight:1/0},(this._aspectRatio||t)&&(e=o.minHeight*this.aspectRatio,s=o.minWidth/this.aspectRatio,i=o.maxHeight*this.aspectRatio,n=o.maxWidth/this.aspectRatio,e>o.minWidth&&(o.minWidth=e),s>o.minHeight&&(o.minHeight=s),o.maxWidth>i&&(o.maxWidth=i),o.maxHeight>n&&(o.maxHeight=n)),this._vBoundaries=o},_updateCache:function(t){this.offset=this.helper.offset(),this._isNumber(t.left)&&(this.position.left=t.left),this._isNumber(t.top)&&(this.position.top=t.top),this._isNumber(t.height)&&(this.size.height=t.height),this._isNumber(t.width)&&(this.size.width=t.width)},_updateRatio:function(t){var e=this.position,i=this.size,s=this.axis;return this._isNumber(t.height)?t.width=t.height*this.aspectRatio:this._isNumber(t.width)&&(t.height=t.width/this.aspectRatio),"sw"===s&&(t.left=e.left+(i.width-t.width),t.top=null),"nw"===s&&(t.top=e.top+(i.height-t.height),t.left=e.left+(i.width-t.width)),t},_respectSize:function(t){var e=this._vBoundaries,i=this.axis,s=this._isNumber(t.width)&&e.maxWidth&&e.maxWidtht.width,a=this._isNumber(t.height)&&e.minHeight&&e.minHeight>t.height,r=this.originalPosition.left+this.originalSize.width,h=this.originalPosition.top+this.originalSize.height,l=/sw|nw|w/.test(i),c=/nw|ne|n/.test(i);return o&&(t.width=e.minWidth),a&&(t.height=e.minHeight),s&&(t.width=e.maxWidth),n&&(t.height=e.maxHeight),o&&l&&(t.left=r-e.minWidth),s&&l&&(t.left=r-e.maxWidth),a&&c&&(t.top=h-e.minHeight),n&&c&&(t.top=h-e.maxHeight),t.width||t.height||t.left||!t.top?t.width||t.height||t.top||!t.left||(t.left=null):t.top=null,t},_getPaddingPlusBorderDimensions:function(t){for(var e=0,i=[],s=[t.css("borderTopWidth"),t.css("borderRightWidth"),t.css("borderBottomWidth"),t.css("borderLeftWidth")],n=[t.css("paddingTop"),t.css("paddingRight"),t.css("paddingBottom"),t.css("paddingLeft")];4>e;e++)i[e]=parseFloat(s[e])||0,i[e]+=parseFloat(n[e])||0;return{height:i[0]+i[2],width:i[1]+i[3]}},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var t,e=0,i=this.helper||this.element;this._proportionallyResizeElements.length>e;e++)t=this._proportionallyResizeElements[e],this.outerDimensions||(this.outerDimensions=this._getPaddingPlusBorderDimensions(t)),t.css({height:i.height()-this.outerDimensions.height||0,width:i.width()-this.outerDimensions.width||0})},_renderProxy:function(){var e=this.element,i=this.options;this.elementOffset=e.offset(),this._helper?(this.helper=this.helper||t("
    "),this._addClass(this.helper,this._helper),this.helper.css({width:this.element.outerWidth(),height:this.element.outerHeight(),position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++i.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(t,e){return{width:this.originalSize.width+e}},w:function(t,e){var i=this.originalSize,s=this.originalPosition;return{left:s.left+e,width:i.width-e}},n:function(t,e,i){var s=this.originalSize,n=this.originalPosition;return{top:n.top+i,height:s.height-i}},s:function(t,e,i){return{height:this.originalSize.height+i}},se:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},sw:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[e,i,s]))},ne:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},nw:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[e,i,s]))}},_propagate:function(e,i){t.ui.plugin.call(this,e,[i,this.ui()]),"resize"!==e&&this._trigger(e,i,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),t.ui.plugin.add("resizable","animate",{stop:function(e){var i=t(this).resizable("instance"),s=i.options,n=i._proportionallyResizeElements,o=n.length&&/textarea/i.test(n[0].nodeName),a=o&&i._hasScroll(n[0],"left")?0:i.sizeDiff.height,r=o?0:i.sizeDiff.width,h={width:i.size.width-r,height:i.size.height-a},l=parseFloat(i.element.css("left"))+(i.position.left-i.originalPosition.left)||null,c=parseFloat(i.element.css("top"))+(i.position.top-i.originalPosition.top)||null;i.element.animate(t.extend(h,c&&l?{top:c,left:l}:{}),{duration:s.animateDuration,easing:s.animateEasing,step:function(){var s={width:parseFloat(i.element.css("width")),height:parseFloat(i.element.css("height")),top:parseFloat(i.element.css("top")),left:parseFloat(i.element.css("left"))};n&&n.length&&t(n[0]).css({width:s.width,height:s.height}),i._updateCache(s),i._propagate("resize",e)}})}}),t.ui.plugin.add("resizable","containment",{start:function(){var e,i,s,n,o,a,r,h=t(this).resizable("instance"),l=h.options,c=h.element,u=l.containment,d=u instanceof t?u.get(0):/parent/.test(u)?c.parent().get(0):u;d&&(h.containerElement=t(d),/document/.test(u)||u===document?(h.containerOffset={left:0,top:0},h.containerPosition={left:0,top:0},h.parentData={element:t(document),left:0,top:0,width:t(document).width(),height:t(document).height()||document.body.parentNode.scrollHeight}):(e=t(d),i=[],t(["Top","Right","Left","Bottom"]).each(function(t,s){i[t]=h._num(e.css("padding"+s))}),h.containerOffset=e.offset(),h.containerPosition=e.position(),h.containerSize={height:e.innerHeight()-i[3],width:e.innerWidth()-i[1]},s=h.containerOffset,n=h.containerSize.height,o=h.containerSize.width,a=h._hasScroll(d,"left")?d.scrollWidth:o,r=h._hasScroll(d)?d.scrollHeight:n,h.parentData={element:d,left:s.left,top:s.top,width:a,height:r}))},resize:function(e){var i,s,n,o,a=t(this).resizable("instance"),r=a.options,h=a.containerOffset,l=a.position,c=a._aspectRatio||e.shiftKey,u={top:0,left:0},d=a.containerElement,p=!0;d[0]!==document&&/static/.test(d.css("position"))&&(u=h),l.left<(a._helper?h.left:0)&&(a.size.width=a.size.width+(a._helper?a.position.left-h.left:a.position.left-u.left),c&&(a.size.height=a.size.width/a.aspectRatio,p=!1),a.position.left=r.helper?h.left:0),l.top<(a._helper?h.top:0)&&(a.size.height=a.size.height+(a._helper?a.position.top-h.top:a.position.top),c&&(a.size.width=a.size.height*a.aspectRatio,p=!1),a.position.top=a._helper?h.top:0),n=a.containerElement.get(0)===a.element.parent().get(0),o=/relative|absolute/.test(a.containerElement.css("position")),n&&o?(a.offset.left=a.parentData.left+a.position.left,a.offset.top=a.parentData.top+a.position.top):(a.offset.left=a.element.offset().left,a.offset.top=a.element.offset().top),i=Math.abs(a.sizeDiff.width+(a._helper?a.offset.left-u.left:a.offset.left-h.left)),s=Math.abs(a.sizeDiff.height+(a._helper?a.offset.top-u.top:a.offset.top-h.top)),i+a.size.width>=a.parentData.width&&(a.size.width=a.parentData.width-i,c&&(a.size.height=a.size.width/a.aspectRatio,p=!1)),s+a.size.height>=a.parentData.height&&(a.size.height=a.parentData.height-s,c&&(a.size.width=a.size.height*a.aspectRatio,p=!1)),p||(a.position.left=a.prevPosition.left,a.position.top=a.prevPosition.top,a.size.width=a.prevSize.width,a.size.height=a.prevSize.height)},stop:function(){var e=t(this).resizable("instance"),i=e.options,s=e.containerOffset,n=e.containerPosition,o=e.containerElement,a=t(e.helper),r=a.offset(),h=a.outerWidth()-e.sizeDiff.width,l=a.outerHeight()-e.sizeDiff.height;e._helper&&!i.animate&&/relative/.test(o.css("position"))&&t(this).css({left:r.left-n.left-s.left,width:h,height:l}),e._helper&&!i.animate&&/static/.test(o.css("position"))&&t(this).css({left:r.left-n.left-s.left,width:h,height:l})}}),t.ui.plugin.add("resizable","alsoResize",{start:function(){var e=t(this).resizable("instance"),i=e.options;t(i.alsoResize).each(function(){var e=t(this);e.data("ui-resizable-alsoresize",{width:parseFloat(e.width()),height:parseFloat(e.height()),left:parseFloat(e.css("left")),top:parseFloat(e.css("top"))})})},resize:function(e,i){var s=t(this).resizable("instance"),n=s.options,o=s.originalSize,a=s.originalPosition,r={height:s.size.height-o.height||0,width:s.size.width-o.width||0,top:s.position.top-a.top||0,left:s.position.left-a.left||0}; +t(n.alsoResize).each(function(){var e=t(this),s=t(this).data("ui-resizable-alsoresize"),n={},o=e.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];t.each(o,function(t,e){var i=(s[e]||0)+(r[e]||0);i&&i>=0&&(n[e]=i||null)}),e.css(n)})},stop:function(){t(this).removeData("ui-resizable-alsoresize")}}),t.ui.plugin.add("resizable","ghost",{start:function(){var e=t(this).resizable("instance"),i=e.size;e.ghost=e.originalElement.clone(),e.ghost.css({opacity:.25,display:"block",position:"relative",height:i.height,width:i.width,margin:0,left:0,top:0}),e._addClass(e.ghost,"ui-resizable-ghost"),t.uiBackCompat!==!1&&"string"==typeof e.options.ghost&&e.ghost.addClass(this.options.ghost),e.ghost.appendTo(e.helper)},resize:function(){var e=t(this).resizable("instance");e.ghost&&e.ghost.css({position:"relative",height:e.size.height,width:e.size.width})},stop:function(){var e=t(this).resizable("instance");e.ghost&&e.helper&&e.helper.get(0).removeChild(e.ghost.get(0))}}),t.ui.plugin.add("resizable","grid",{resize:function(){var e,i=t(this).resizable("instance"),s=i.options,n=i.size,o=i.originalSize,a=i.originalPosition,r=i.axis,h="number"==typeof s.grid?[s.grid,s.grid]:s.grid,l=h[0]||1,c=h[1]||1,u=Math.round((n.width-o.width)/l)*l,d=Math.round((n.height-o.height)/c)*c,p=o.width+u,f=o.height+d,g=s.maxWidth&&p>s.maxWidth,m=s.maxHeight&&f>s.maxHeight,_=s.minWidth&&s.minWidth>p,v=s.minHeight&&s.minHeight>f;s.grid=h,_&&(p+=l),v&&(f+=c),g&&(p-=l),m&&(f-=c),/^(se|s|e)$/.test(r)?(i.size.width=p,i.size.height=f):/^(ne)$/.test(r)?(i.size.width=p,i.size.height=f,i.position.top=a.top-d):/^(sw)$/.test(r)?(i.size.width=p,i.size.height=f,i.position.left=a.left-u):((0>=f-c||0>=p-l)&&(e=i._getPaddingPlusBorderDimensions(this)),f-c>0?(i.size.height=f,i.position.top=a.top-d):(f=c-e.height,i.size.height=f,i.position.top=a.top+o.height-f),p-l>0?(i.size.width=p,i.position.left=a.left-u):(p=l-e.width,i.size.width=p,i.position.left=a.left+o.width-p))}}),t.ui.resizable,t.widget("ui.selectable",t.ui.mouse,{version:"1.12.1",options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch",selected:null,selecting:null,start:null,stop:null,unselected:null,unselecting:null},_create:function(){var e=this;this._addClass("ui-selectable"),this.dragged=!1,this.refresh=function(){e.elementPos=t(e.element[0]).offset(),e.selectees=t(e.options.filter,e.element[0]),e._addClass(e.selectees,"ui-selectee"),e.selectees.each(function(){var i=t(this),s=i.offset(),n={left:s.left-e.elementPos.left,top:s.top-e.elementPos.top};t.data(this,"selectable-item",{element:this,$element:i,left:n.left,top:n.top,right:n.left+i.outerWidth(),bottom:n.top+i.outerHeight(),startselected:!1,selected:i.hasClass("ui-selected"),selecting:i.hasClass("ui-selecting"),unselecting:i.hasClass("ui-unselecting")})})},this.refresh(),this._mouseInit(),this.helper=t("
    "),this._addClass(this.helper,"ui-selectable-helper")},_destroy:function(){this.selectees.removeData("selectable-item"),this._mouseDestroy()},_mouseStart:function(e){var i=this,s=this.options;this.opos=[e.pageX,e.pageY],this.elementPos=t(this.element[0]).offset(),this.options.disabled||(this.selectees=t(s.filter,this.element[0]),this._trigger("start",e),t(s.appendTo).append(this.helper),this.helper.css({left:e.pageX,top:e.pageY,width:0,height:0}),s.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var s=t.data(this,"selectable-item");s.startselected=!0,e.metaKey||e.ctrlKey||(i._removeClass(s.$element,"ui-selected"),s.selected=!1,i._addClass(s.$element,"ui-unselecting"),s.unselecting=!0,i._trigger("unselecting",e,{unselecting:s.element}))}),t(e.target).parents().addBack().each(function(){var s,n=t.data(this,"selectable-item");return n?(s=!e.metaKey&&!e.ctrlKey||!n.$element.hasClass("ui-selected"),i._removeClass(n.$element,s?"ui-unselecting":"ui-selected")._addClass(n.$element,s?"ui-selecting":"ui-unselecting"),n.unselecting=!s,n.selecting=s,n.selected=s,s?i._trigger("selecting",e,{selecting:n.element}):i._trigger("unselecting",e,{unselecting:n.element}),!1):void 0}))},_mouseDrag:function(e){if(this.dragged=!0,!this.options.disabled){var i,s=this,n=this.options,o=this.opos[0],a=this.opos[1],r=e.pageX,h=e.pageY;return o>r&&(i=r,r=o,o=i),a>h&&(i=h,h=a,a=i),this.helper.css({left:o,top:a,width:r-o,height:h-a}),this.selectees.each(function(){var i=t.data(this,"selectable-item"),l=!1,c={};i&&i.element!==s.element[0]&&(c.left=i.left+s.elementPos.left,c.right=i.right+s.elementPos.left,c.top=i.top+s.elementPos.top,c.bottom=i.bottom+s.elementPos.top,"touch"===n.tolerance?l=!(c.left>r||o>c.right||c.top>h||a>c.bottom):"fit"===n.tolerance&&(l=c.left>o&&r>c.right&&c.top>a&&h>c.bottom),l?(i.selected&&(s._removeClass(i.$element,"ui-selected"),i.selected=!1),i.unselecting&&(s._removeClass(i.$element,"ui-unselecting"),i.unselecting=!1),i.selecting||(s._addClass(i.$element,"ui-selecting"),i.selecting=!0,s._trigger("selecting",e,{selecting:i.element}))):(i.selecting&&((e.metaKey||e.ctrlKey)&&i.startselected?(s._removeClass(i.$element,"ui-selecting"),i.selecting=!1,s._addClass(i.$element,"ui-selected"),i.selected=!0):(s._removeClass(i.$element,"ui-selecting"),i.selecting=!1,i.startselected&&(s._addClass(i.$element,"ui-unselecting"),i.unselecting=!0),s._trigger("unselecting",e,{unselecting:i.element}))),i.selected&&(e.metaKey||e.ctrlKey||i.startselected||(s._removeClass(i.$element,"ui-selected"),i.selected=!1,s._addClass(i.$element,"ui-unselecting"),i.unselecting=!0,s._trigger("unselecting",e,{unselecting:i.element})))))}),!1}},_mouseStop:function(e){var i=this;return this.dragged=!1,t(".ui-unselecting",this.element[0]).each(function(){var s=t.data(this,"selectable-item");i._removeClass(s.$element,"ui-unselecting"),s.unselecting=!1,s.startselected=!1,i._trigger("unselected",e,{unselected:s.element})}),t(".ui-selecting",this.element[0]).each(function(){var s=t.data(this,"selectable-item");i._removeClass(s.$element,"ui-selecting")._addClass(s.$element,"ui-selected"),s.selecting=!1,s.selected=!0,s.startselected=!0,i._trigger("selected",e,{selected:s.element})}),this._trigger("stop",e),this.helper.remove(),!1}}),t.widget("ui.sortable",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_isOverAxis:function(t,e,i){return t>=e&&e+i>t},_isFloating:function(t){return/left|right/.test(t.css("float"))||/inline|table-cell/.test(t.css("display"))},_create:function(){this.containerCache={},this._addClass("ui-sortable"),this.refresh(),this.offset=this.element.offset(),this._mouseInit(),this._setHandleClassName(),this.ready=!0},_setOption:function(t,e){this._super(t,e),"handle"===t&&this._setHandleClassName()},_setHandleClassName:function(){var e=this;this._removeClass(this.element.find(".ui-sortable-handle"),"ui-sortable-handle"),t.each(this.items,function(){e._addClass(this.instance.options.handle?this.item.find(this.instance.options.handle):this.item,"ui-sortable-handle")})},_destroy:function(){this._mouseDestroy();for(var t=this.items.length-1;t>=0;t--)this.items[t].item.removeData(this.widgetName+"-item");return this},_mouseCapture:function(e,i){var s=null,n=!1,o=this;return this.reverting?!1:this.options.disabled||"static"===this.options.type?!1:(this._refreshItems(e),t(e.target).parents().each(function(){return t.data(this,o.widgetName+"-item")===o?(s=t(this),!1):void 0}),t.data(e.target,o.widgetName+"-item")===o&&(s=t(e.target)),s?!this.options.handle||i||(t(this.options.handle,s).find("*").addBack().each(function(){this===e.target&&(n=!0)}),n)?(this.currentItem=s,this._removeCurrentsFromItems(),!0):!1:!1)},_mouseStart:function(e,i,s){var n,o,a=this.options;if(this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(e),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},t.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(e),this.originalPageX=e.pageX,this.originalPageY=e.pageY,a.cursorAt&&this._adjustOffsetFromHelper(a.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!==this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),a.containment&&this._setContainment(),a.cursor&&"auto"!==a.cursor&&(o=this.document.find("body"),this.storedCursor=o.css("cursor"),o.css("cursor",a.cursor),this.storedStylesheet=t("").appendTo(o)),a.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",a.opacity)),a.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",a.zIndex)),this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",e,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions(),!s)for(n=this.containers.length-1;n>=0;n--)this.containers[n]._trigger("activate",e,this._uiHash(this));return t.ui.ddmanager&&(t.ui.ddmanager.current=this),t.ui.ddmanager&&!a.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this.dragging=!0,this._addClass(this.helper,"ui-sortable-helper"),this._mouseDrag(e),!0},_mouseDrag:function(e){var i,s,n,o,a=this.options,r=!1;for(this.position=this._generatePosition(e),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs),this.options.scroll&&(this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-e.pageY=0;i--)if(s=this.items[i],n=s.item[0],o=this._intersectsWithPointer(s),o&&s.instance===this.currentContainer&&n!==this.currentItem[0]&&this.placeholder[1===o?"next":"prev"]()[0]!==n&&!t.contains(this.placeholder[0],n)&&("semi-dynamic"===this.options.type?!t.contains(this.element[0],n):!0)){if(this.direction=1===o?"down":"up","pointer"!==this.options.tolerance&&!this._intersectsWithSides(s))break;this._rearrange(e,s),this._trigger("change",e,this._uiHash());break}return this._contactContainers(e),t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),this._trigger("sort",e,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(e,i){if(e){if(t.ui.ddmanager&&!this.options.dropBehaviour&&t.ui.ddmanager.drop(this,e),this.options.revert){var s=this,n=this.placeholder.offset(),o=this.options.axis,a={};o&&"x"!==o||(a.left=n.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollLeft)),o&&"y"!==o||(a.top=n.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollTop)),this.reverting=!0,t(this.helper).animate(a,parseInt(this.options.revert,10)||500,function(){s._clear(e)})}else this._clear(e,i);return!1}},cancel:function(){if(this.dragging){this._mouseUp(new t.Event("mouseup",{target:null})),"original"===this.options.helper?(this.currentItem.css(this._storedCSS),this._removeClass(this.currentItem,"ui-sortable-helper")):this.currentItem.show();for(var e=this.containers.length-1;e>=0;e--)this.containers[e]._trigger("deactivate",null,this._uiHash(this)),this.containers[e].containerCache.over&&(this.containers[e]._trigger("out",null,this._uiHash(this)),this.containers[e].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),"original"!==this.options.helper&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),t.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?t(this.domPosition.prev).after(this.currentItem):t(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},t(i).each(function(){var i=(t(e.item||this).attr(e.attribute||"id")||"").match(e.expression||/(.+)[\-=_](.+)/);i&&s.push((e.key||i[1]+"[]")+"="+(e.key&&e.expression?i[1]:i[2]))}),!s.length&&e.key&&s.push(e.key+"="),s.join("&")},toArray:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},i.each(function(){s.push(t(e.item||this).attr(e.attribute||"id")||"")}),s},_intersectsWith:function(t){var e=this.positionAbs.left,i=e+this.helperProportions.width,s=this.positionAbs.top,n=s+this.helperProportions.height,o=t.left,a=o+t.width,r=t.top,h=r+t.height,l=this.offset.click.top,c=this.offset.click.left,u="x"===this.options.axis||s+l>r&&h>s+l,d="y"===this.options.axis||e+c>o&&a>e+c,p=u&&d;return"pointer"===this.options.tolerance||this.options.forcePointerForContainers||"pointer"!==this.options.tolerance&&this.helperProportions[this.floating?"width":"height"]>t[this.floating?"width":"height"]?p:e+this.helperProportions.width/2>o&&a>i-this.helperProportions.width/2&&s+this.helperProportions.height/2>r&&h>n-this.helperProportions.height/2},_intersectsWithPointer:function(t){var e,i,s="x"===this.options.axis||this._isOverAxis(this.positionAbs.top+this.offset.click.top,t.top,t.height),n="y"===this.options.axis||this._isOverAxis(this.positionAbs.left+this.offset.click.left,t.left,t.width),o=s&&n;return o?(e=this._getDragVerticalDirection(),i=this._getDragHorizontalDirection(),this.floating?"right"===i||"down"===e?2:1:e&&("down"===e?2:1)):!1},_intersectsWithSides:function(t){var e=this._isOverAxis(this.positionAbs.top+this.offset.click.top,t.top+t.height/2,t.height),i=this._isOverAxis(this.positionAbs.left+this.offset.click.left,t.left+t.width/2,t.width),s=this._getDragVerticalDirection(),n=this._getDragHorizontalDirection();return this.floating&&n?"right"===n&&i||"left"===n&&!i:s&&("down"===s&&e||"up"===s&&!e)},_getDragVerticalDirection:function(){var t=this.positionAbs.top-this.lastPositionAbs.top;return 0!==t&&(t>0?"down":"up")},_getDragHorizontalDirection:function(){var t=this.positionAbs.left-this.lastPositionAbs.left;return 0!==t&&(t>0?"right":"left")},refresh:function(t){return this._refreshItems(t),this._setHandleClassName(),this.refreshPositions(),this},_connectWith:function(){var t=this.options;return t.connectWith.constructor===String?[t.connectWith]:t.connectWith},_getItemsAsjQuery:function(e){function i(){r.push(this)}var s,n,o,a,r=[],h=[],l=this._connectWith();if(l&&e)for(s=l.length-1;s>=0;s--)for(o=t(l[s],this.document[0]),n=o.length-1;n>=0;n--)a=t.data(o[n],this.widgetFullName),a&&a!==this&&!a.options.disabled&&h.push([t.isFunction(a.options.items)?a.options.items.call(a.element):t(a.options.items,a.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),a]);for(h.push([t.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):t(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]),s=h.length-1;s>=0;s--)h[s][0].each(i);return t(r)},_removeCurrentsFromItems:function(){var e=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=t.grep(this.items,function(t){for(var i=0;e.length>i;i++)if(e[i]===t.item[0])return!1;return!0})},_refreshItems:function(e){this.items=[],this.containers=[this];var i,s,n,o,a,r,h,l,c=this.items,u=[[t.isFunction(this.options.items)?this.options.items.call(this.element[0],e,{item:this.currentItem}):t(this.options.items,this.element),this]],d=this._connectWith();if(d&&this.ready)for(i=d.length-1;i>=0;i--)for(n=t(d[i],this.document[0]),s=n.length-1;s>=0;s--)o=t.data(n[s],this.widgetFullName),o&&o!==this&&!o.options.disabled&&(u.push([t.isFunction(o.options.items)?o.options.items.call(o.element[0],e,{item:this.currentItem}):t(o.options.items,o.element),o]),this.containers.push(o));for(i=u.length-1;i>=0;i--)for(a=u[i][1],r=u[i][0],s=0,l=r.length;l>s;s++)h=t(r[s]),h.data(this.widgetName+"-item",a),c.push({item:h,instance:a,width:0,height:0,left:0,top:0})},refreshPositions:function(e){this.floating=this.items.length?"x"===this.options.axis||this._isFloating(this.items[0].item):!1,this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());var i,s,n,o;for(i=this.items.length-1;i>=0;i--)s=this.items[i],s.instance!==this.currentContainer&&this.currentContainer&&s.item[0]!==this.currentItem[0]||(n=this.options.toleranceElement?t(this.options.toleranceElement,s.item):s.item,e||(s.width=n.outerWidth(),s.height=n.outerHeight()),o=n.offset(),s.left=o.left,s.top=o.top);if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(i=this.containers.length-1;i>=0;i--)o=this.containers[i].element.offset(),this.containers[i].containerCache.left=o.left,this.containers[i].containerCache.top=o.top,this.containers[i].containerCache.width=this.containers[i].element.outerWidth(),this.containers[i].containerCache.height=this.containers[i].element.outerHeight();return this},_createPlaceholder:function(e){e=e||this;var i,s=e.options;s.placeholder&&s.placeholder.constructor!==String||(i=s.placeholder,s.placeholder={element:function(){var s=e.currentItem[0].nodeName.toLowerCase(),n=t("<"+s+">",e.document[0]);return e._addClass(n,"ui-sortable-placeholder",i||e.currentItem[0].className)._removeClass(n,"ui-sortable-helper"),"tbody"===s?e._createTrPlaceholder(e.currentItem.find("tr").eq(0),t("
    ",e.document[0]).appendTo(n)):"tr"===s?e._createTrPlaceholder(e.currentItem,n):"img"===s&&n.attr("src",e.currentItem.attr("src")),i||n.css("visibility","hidden"),n},update:function(t,n){(!i||s.forcePlaceholderSize)&&(n.height()||n.height(e.currentItem.innerHeight()-parseInt(e.currentItem.css("paddingTop")||0,10)-parseInt(e.currentItem.css("paddingBottom")||0,10)),n.width()||n.width(e.currentItem.innerWidth()-parseInt(e.currentItem.css("paddingLeft")||0,10)-parseInt(e.currentItem.css("paddingRight")||0,10)))}}),e.placeholder=t(s.placeholder.element.call(e.element,e.currentItem)),e.currentItem.after(e.placeholder),s.placeholder.update(e,e.placeholder)},_createTrPlaceholder:function(e,i){var s=this;e.children().each(function(){t("",s.document[0]).attr("colspan",t(this).attr("colspan")||1).appendTo(i)})},_contactContainers:function(e){var i,s,n,o,a,r,h,l,c,u,d=null,p=null;for(i=this.containers.length-1;i>=0;i--)if(!t.contains(this.currentItem[0],this.containers[i].element[0]))if(this._intersectsWith(this.containers[i].containerCache)){if(d&&t.contains(this.containers[i].element[0],d.element[0]))continue;d=this.containers[i],p=i}else this.containers[i].containerCache.over&&(this.containers[i]._trigger("out",e,this._uiHash(this)),this.containers[i].containerCache.over=0);if(d)if(1===this.containers.length)this.containers[p].containerCache.over||(this.containers[p]._trigger("over",e,this._uiHash(this)),this.containers[p].containerCache.over=1);else{for(n=1e4,o=null,c=d.floating||this._isFloating(this.currentItem),a=c?"left":"top",r=c?"width":"height",u=c?"pageX":"pageY",s=this.items.length-1;s>=0;s--)t.contains(this.containers[p].element[0],this.items[s].item[0])&&this.items[s].item[0]!==this.currentItem[0]&&(h=this.items[s].item.offset()[a],l=!1,e[u]-h>this.items[s][r]/2&&(l=!0),n>Math.abs(e[u]-h)&&(n=Math.abs(e[u]-h),o=this.items[s],this.direction=l?"up":"down"));if(!o&&!this.options.dropOnEmpty)return;if(this.currentContainer===this.containers[p])return this.currentContainer.containerCache.over||(this.containers[p]._trigger("over",e,this._uiHash()),this.currentContainer.containerCache.over=1),void 0;o?this._rearrange(e,o,null,!0):this._rearrange(e,null,this.containers[p].element,!0),this._trigger("change",e,this._uiHash()),this.containers[p]._trigger("change",e,this._uiHash(this)),this.currentContainer=this.containers[p],this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[p]._trigger("over",e,this._uiHash(this)),this.containers[p].containerCache.over=1}},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper)?t(i.helper.apply(this.element[0],[e,this.currentItem])):"clone"===i.helper?this.currentItem.clone():this.currentItem;return s.parents("body").length||t("parent"!==i.appendTo?i.appendTo:this.currentItem[0].parentNode)[0].appendChild(s[0]),s[0]===this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(!s[0].style.width||i.forceHelperSize)&&s.width(this.currentItem.width()),(!s[0].style.height||i.forceHelperSize)&&s.height(this.currentItem.height()),s},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var e=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===this.document[0].body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&t.ui.ie)&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var t=this.currentItem.position();return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:t.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options;"parent"===n.containment&&(n.containment=this.helper[0].parentNode),("document"===n.containment||"window"===n.containment)&&(this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,"document"===n.containment?this.document.width():this.window.width()-this.helperProportions.width-this.margins.left,("document"===n.containment?this.document.height()||document.body.parentNode.scrollHeight:this.window.height()||this.document[0].body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]),/^(document|window|parent)$/.test(n.containment)||(e=t(n.containment)[0],i=t(n.containment).offset(),s="hidden"!==t(e).css("overflow"),this.containment=[i.left+(parseInt(t(e).css("borderLeftWidth"),10)||0)+(parseInt(t(e).css("paddingLeft"),10)||0)-this.margins.left,i.top+(parseInt(t(e).css("borderTopWidth"),10)||0)+(parseInt(t(e).css("paddingTop"),10)||0)-this.margins.top,i.left+(s?Math.max(e.scrollWidth,e.offsetWidth):e.offsetWidth)-(parseInt(t(e).css("borderLeftWidth"),10)||0)-(parseInt(t(e).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,i.top+(s?Math.max(e.scrollHeight,e.offsetHeight):e.offsetHeight)-(parseInt(t(e).css("borderTopWidth"),10)||0)-(parseInt(t(e).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top])},_convertPositionTo:function(e,i){i||(i=this.position);var s="absolute"===e?1:-1,n="absolute"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,o=/(html|body)/i.test(n[0].tagName);return{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():o?0:n.scrollTop())*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():o?0:n.scrollLeft())*s}},_generatePosition:function(e){var i,s,n=this.options,o=e.pageX,a=e.pageY,r="absolute"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,h=/(html|body)/i.test(r[0].tagName);return"relative"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&this.scrollParent[0]!==this.offsetParent[0]||(this.offset.relative=this._getRelativeOffset()),this.originalPosition&&(this.containment&&(e.pageX-this.offset.click.leftthis.containment[2]&&(o=this.containment[2]+this.offset.click.left),e.pageY-this.offset.click.top>this.containment[3]&&(a=this.containment[3]+this.offset.click.top)),n.grid&&(i=this.originalPageY+Math.round((a-this.originalPageY)/n.grid[1])*n.grid[1],a=this.containment?i-this.offset.click.top>=this.containment[1]&&i-this.offset.click.top<=this.containment[3]?i:i-this.offset.click.top>=this.containment[1]?i-n.grid[1]:i+n.grid[1]:i,s=this.originalPageX+Math.round((o-this.originalPageX)/n.grid[0])*n.grid[0],o=this.containment?s-this.offset.click.left>=this.containment[0]&&s-this.offset.click.left<=this.containment[2]?s:s-this.offset.click.left>=this.containment[0]?s-n.grid[0]:s+n.grid[0]:s)),{top:a-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():h?0:r.scrollTop()),left:o-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():h?0:r.scrollLeft())}},_rearrange:function(t,e,i,s){i?i[0].appendChild(this.placeholder[0]):e.item[0].parentNode.insertBefore(this.placeholder[0],"down"===this.direction?e.item[0]:e.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var n=this.counter;this._delay(function(){n===this.counter&&this.refreshPositions(!s)})},_clear:function(t,e){function i(t,e,i){return function(s){i._trigger(t,s,e._uiHash(e))}}this.reverting=!1;var s,n=[];if(!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null,this.helper[0]===this.currentItem[0]){for(s in this._storedCSS)("auto"===this._storedCSS[s]||"static"===this._storedCSS[s])&&(this._storedCSS[s]="");this.currentItem.css(this._storedCSS),this._removeClass(this.currentItem,"ui-sortable-helper")}else this.currentItem.show();for(this.fromOutside&&!e&&n.push(function(t){this._trigger("receive",t,this._uiHash(this.fromOutside))}),!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(".ui-sortable-helper")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||e||n.push(function(t){this._trigger("update",t,this._uiHash())}),this!==this.currentContainer&&(e||(n.push(function(t){this._trigger("remove",t,this._uiHash())}),n.push(function(t){return function(e){t._trigger("receive",e,this._uiHash(this))}}.call(this,this.currentContainer)),n.push(function(t){return function(e){t._trigger("update",e,this._uiHash(this))}}.call(this,this.currentContainer)))),s=this.containers.length-1;s>=0;s--)e||n.push(i("deactivate",this,this.containers[s])),this.containers[s].containerCache.over&&(n.push(i("out",this,this.containers[s])),this.containers[s].containerCache.over=0);if(this.storedCursor&&(this.document.find("body").css("cursor",this.storedCursor),this.storedStylesheet.remove()),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex","auto"===this._storedZIndex?"":this._storedZIndex),this.dragging=!1,e||this._trigger("beforeStop",t,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.cancelHelperRemoval||(this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null),!e){for(s=0;n.length>s;s++)n[s].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!this.cancelHelperRemoval},_trigger:function(){t.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(e){var i=e||this;return{helper:i.helper,placeholder:i.placeholder||t([]),position:i.position,originalPosition:i.originalPosition,offset:i.positionAbs,item:i.currentItem,sender:e?e.element:null}}}),t.widget("ui.accordion",{version:"1.12.1",options:{active:0,animate:{},classes:{"ui-accordion-header":"ui-corner-top","ui-accordion-header-collapsed":"ui-corner-all","ui-accordion-content":"ui-corner-bottom"},collapsible:!1,event:"click",header:"> li > :first-child, > :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},hideProps:{borderTopWidth:"hide",borderBottomWidth:"hide",paddingTop:"hide",paddingBottom:"hide",height:"hide"},showProps:{borderTopWidth:"show",borderBottomWidth:"show",paddingTop:"show",paddingBottom:"show",height:"show"},_create:function(){var e=this.options;this.prevShow=this.prevHide=t(),this._addClass("ui-accordion","ui-widget ui-helper-reset"),this.element.attr("role","tablist"),e.collapsible||e.active!==!1&&null!=e.active||(e.active=0),this._processPanels(),0>e.active&&(e.active+=this.headers.length),this._refresh()},_getCreateEventData:function(){return{header:this.active,panel:this.active.length?this.active.next():t()}},_createIcons:function(){var e,i,s=this.options.icons;s&&(e=t(""),this._addClass(e,"ui-accordion-header-icon","ui-icon "+s.header),e.prependTo(this.headers),i=this.active.children(".ui-accordion-header-icon"),this._removeClass(i,s.header)._addClass(i,null,s.activeHeader)._addClass(this.headers,"ui-accordion-icons")) +},_destroyIcons:function(){this._removeClass(this.headers,"ui-accordion-icons"),this.headers.children(".ui-accordion-header-icon").remove()},_destroy:function(){var t;this.element.removeAttr("role"),this.headers.removeAttr("role aria-expanded aria-selected aria-controls tabIndex").removeUniqueId(),this._destroyIcons(),t=this.headers.next().css("display","").removeAttr("role aria-hidden aria-labelledby").removeUniqueId(),"content"!==this.options.heightStyle&&t.css("height","")},_setOption:function(t,e){return"active"===t?(this._activate(e),void 0):("event"===t&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(e)),this._super(t,e),"collapsible"!==t||e||this.options.active!==!1||this._activate(0),"icons"===t&&(this._destroyIcons(),e&&this._createIcons()),void 0)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t),this._toggleClass(null,"ui-state-disabled",!!t),this._toggleClass(this.headers.add(this.headers.next()),null,"ui-state-disabled",!!t)},_keydown:function(e){if(!e.altKey&&!e.ctrlKey){var i=t.ui.keyCode,s=this.headers.length,n=this.headers.index(e.target),o=!1;switch(e.keyCode){case i.RIGHT:case i.DOWN:o=this.headers[(n+1)%s];break;case i.LEFT:case i.UP:o=this.headers[(n-1+s)%s];break;case i.SPACE:case i.ENTER:this._eventHandler(e);break;case i.HOME:o=this.headers[0];break;case i.END:o=this.headers[s-1]}o&&(t(e.target).attr("tabIndex",-1),t(o).attr("tabIndex",0),t(o).trigger("focus"),e.preventDefault())}},_panelKeyDown:function(e){e.keyCode===t.ui.keyCode.UP&&e.ctrlKey&&t(e.currentTarget).prev().trigger("focus")},refresh:function(){var e=this.options;this._processPanels(),e.active===!1&&e.collapsible===!0||!this.headers.length?(e.active=!1,this.active=t()):e.active===!1?this._activate(0):this.active.length&&!t.contains(this.element[0],this.active[0])?this.headers.length===this.headers.find(".ui-state-disabled").length?(e.active=!1,this.active=t()):this._activate(Math.max(0,e.active-1)):e.active=this.headers.index(this.active),this._destroyIcons(),this._refresh()},_processPanels:function(){var t=this.headers,e=this.panels;this.headers=this.element.find(this.options.header),this._addClass(this.headers,"ui-accordion-header ui-accordion-header-collapsed","ui-state-default"),this.panels=this.headers.next().filter(":not(.ui-accordion-content-active)").hide(),this._addClass(this.panels,"ui-accordion-content","ui-helper-reset ui-widget-content"),e&&(this._off(t.not(this.headers)),this._off(e.not(this.panels)))},_refresh:function(){var e,i=this.options,s=i.heightStyle,n=this.element.parent();this.active=this._findActive(i.active),this._addClass(this.active,"ui-accordion-header-active","ui-state-active")._removeClass(this.active,"ui-accordion-header-collapsed"),this._addClass(this.active.next(),"ui-accordion-content-active"),this.active.next().show(),this.headers.attr("role","tab").each(function(){var e=t(this),i=e.uniqueId().attr("id"),s=e.next(),n=s.uniqueId().attr("id");e.attr("aria-controls",n),s.attr("aria-labelledby",i)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}).next().attr({"aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}).next().attr({"aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._createIcons(),this._setupEvents(i.event),"fill"===s?(e=n.height(),this.element.siblings(":visible").each(function(){var i=t(this),s=i.css("position");"absolute"!==s&&"fixed"!==s&&(e-=i.outerHeight(!0))}),this.headers.each(function(){e-=t(this).outerHeight(!0)}),this.headers.next().each(function(){t(this).height(Math.max(0,e-t(this).innerHeight()+t(this).height()))}).css("overflow","auto")):"auto"===s&&(e=0,this.headers.next().each(function(){var i=t(this).is(":visible");i||t(this).show(),e=Math.max(e,t(this).css("height","").height()),i||t(this).hide()}).height(e))},_activate:function(e){var i=this._findActive(e)[0];i!==this.active[0]&&(i=i||this.active[0],this._eventHandler({target:i,currentTarget:i,preventDefault:t.noop}))},_findActive:function(e){return"number"==typeof e?this.headers.eq(e):t()},_setupEvents:function(e){var i={keydown:"_keydown"};e&&t.each(e.split(" "),function(t,e){i[e]="_eventHandler"}),this._off(this.headers.add(this.headers.next())),this._on(this.headers,i),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._hoverable(this.headers),this._focusable(this.headers)},_eventHandler:function(e){var i,s,n=this.options,o=this.active,a=t(e.currentTarget),r=a[0]===o[0],h=r&&n.collapsible,l=h?t():a.next(),c=o.next(),u={oldHeader:o,oldPanel:c,newHeader:h?t():a,newPanel:l};e.preventDefault(),r&&!n.collapsible||this._trigger("beforeActivate",e,u)===!1||(n.active=h?!1:this.headers.index(a),this.active=r?t():a,this._toggle(u),this._removeClass(o,"ui-accordion-header-active","ui-state-active"),n.icons&&(i=o.children(".ui-accordion-header-icon"),this._removeClass(i,null,n.icons.activeHeader)._addClass(i,null,n.icons.header)),r||(this._removeClass(a,"ui-accordion-header-collapsed")._addClass(a,"ui-accordion-header-active","ui-state-active"),n.icons&&(s=a.children(".ui-accordion-header-icon"),this._removeClass(s,null,n.icons.header)._addClass(s,null,n.icons.activeHeader)),this._addClass(a.next(),"ui-accordion-content-active")))},_toggle:function(e){var i=e.newPanel,s=this.prevShow.length?this.prevShow:e.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=i,this.prevHide=s,this.options.animate?this._animate(i,s,e):(s.hide(),i.show(),this._toggleComplete(e)),s.attr({"aria-hidden":"true"}),s.prev().attr({"aria-selected":"false","aria-expanded":"false"}),i.length&&s.length?s.prev().attr({tabIndex:-1,"aria-expanded":"false"}):i.length&&this.headers.filter(function(){return 0===parseInt(t(this).attr("tabIndex"),10)}).attr("tabIndex",-1),i.attr("aria-hidden","false").prev().attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_animate:function(t,e,i){var s,n,o,a=this,r=0,h=t.css("box-sizing"),l=t.length&&(!e.length||t.index()",delay:300,options:{icons:{submenu:"ui-icon-caret-1-e"},items:"> *",menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().attr({role:this.options.role,tabIndex:0}),this._addClass("ui-menu","ui-widget ui-widget-content"),this._on({"mousedown .ui-menu-item":function(t){t.preventDefault()},"click .ui-menu-item":function(e){var i=t(e.target),s=t(t.ui.safeActiveElement(this.document[0]));!this.mouseHandled&&i.not(".ui-state-disabled").length&&(this.select(e),e.isPropagationStopped()||(this.mouseHandled=!0),i.has(".ui-menu").length?this.expand(e):!this.element.is(":focus")&&s.closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(e){if(!this.previousFilter){var i=t(e.target).closest(".ui-menu-item"),s=t(e.currentTarget);i[0]===s[0]&&(this._removeClass(s.siblings().children(".ui-state-active"),null,"ui-state-active"),this.focus(e,s))}},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var i=this.active||this.element.find(this.options.items).eq(0);e||this.focus(t,i)},blur:function(e){this._delay(function(){var i=!t.contains(this.element[0],t.ui.safeActiveElement(this.document[0]));i&&this.collapseAll(e)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(t){this._closeOnDocumentClick(t)&&this.collapseAll(t),this.mouseHandled=!1}})},_destroy:function(){var e=this.element.find(".ui-menu-item").removeAttr("role aria-disabled"),i=e.children(".ui-menu-item-wrapper").removeUniqueId().removeAttr("tabIndex role aria-haspopup");this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeAttr("role aria-labelledby aria-expanded aria-hidden aria-disabled tabIndex").removeUniqueId().show(),i.children().each(function(){var e=t(this);e.data("ui-menu-submenu-caret")&&e.remove()})},_keydown:function(e){var i,s,n,o,a=!0;switch(e.keyCode){case t.ui.keyCode.PAGE_UP:this.previousPage(e);break;case t.ui.keyCode.PAGE_DOWN:this.nextPage(e);break;case t.ui.keyCode.HOME:this._move("first","first",e);break;case t.ui.keyCode.END:this._move("last","last",e);break;case t.ui.keyCode.UP:this.previous(e);break;case t.ui.keyCode.DOWN:this.next(e);break;case t.ui.keyCode.LEFT:this.collapse(e);break;case t.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(e);break;case t.ui.keyCode.ENTER:case t.ui.keyCode.SPACE:this._activate(e);break;case t.ui.keyCode.ESCAPE:this.collapse(e);break;default:a=!1,s=this.previousFilter||"",o=!1,n=e.keyCode>=96&&105>=e.keyCode?""+(e.keyCode-96):String.fromCharCode(e.keyCode),clearTimeout(this.filterTimer),n===s?o=!0:n=s+n,i=this._filterMenuItems(n),i=o&&-1!==i.index(this.active.next())?this.active.nextAll(".ui-menu-item"):i,i.length||(n=String.fromCharCode(e.keyCode),i=this._filterMenuItems(n)),i.length?(this.focus(e,i),this.previousFilter=n,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter}a&&e.preventDefault()},_activate:function(t){this.active&&!this.active.is(".ui-state-disabled")&&(this.active.children("[aria-haspopup='true']").length?this.expand(t):this.select(t))},refresh:function(){var e,i,s,n,o,a=this,r=this.options.icons.submenu,h=this.element.find(this.options.menus);this._toggleClass("ui-menu-icons",null,!!this.element.find(".ui-icon").length),s=h.filter(":not(.ui-menu)").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var e=t(this),i=e.prev(),s=t("").data("ui-menu-submenu-caret",!0);a._addClass(s,"ui-menu-icon","ui-icon "+r),i.attr("aria-haspopup","true").prepend(s),e.attr("aria-labelledby",i.attr("id"))}),this._addClass(s,"ui-menu","ui-widget ui-widget-content ui-front"),e=h.add(this.element),i=e.find(this.options.items),i.not(".ui-menu-item").each(function(){var e=t(this);a._isDivider(e)&&a._addClass(e,"ui-menu-divider","ui-widget-content")}),n=i.not(".ui-menu-item, .ui-menu-divider"),o=n.children().not(".ui-menu").uniqueId().attr({tabIndex:-1,role:this._itemRole()}),this._addClass(n,"ui-menu-item")._addClass(o,"ui-menu-item-wrapper"),i.filter(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!t.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){if("icons"===t){var i=this.element.find(".ui-menu-icon");this._removeClass(i,null,this.options.icons.submenu)._addClass(i,null,e.submenu)}this._super(t,e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t+""),this._toggleClass(null,"ui-state-disabled",!!t)},focus:function(t,e){var i,s,n;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),s=this.active.children(".ui-menu-item-wrapper"),this._addClass(s,null,"ui-state-active"),this.options.role&&this.element.attr("aria-activedescendant",s.attr("id")),n=this.active.parent().closest(".ui-menu-item").children(".ui-menu-item-wrapper"),this._addClass(n,null,"ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),i=e.children(".ui-menu"),i.length&&t&&/^mouse/.test(t.type)&&this._startOpening(i),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(e){var i,s,n,o,a,r;this._hasScroll()&&(i=parseFloat(t.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(t.css(this.activeMenu[0],"paddingTop"))||0,n=e.offset().top-this.activeMenu.offset().top-i-s,o=this.activeMenu.scrollTop(),a=this.activeMenu.height(),r=e.outerHeight(),0>n?this.activeMenu.scrollTop(o+n):n+r>a&&this.activeMenu.scrollTop(o+n-a+r))},blur:function(t,e){e||clearTimeout(this.timer),this.active&&(this._removeClass(this.active.children(".ui-menu-item-wrapper"),null,"ui-state-active"),this._trigger("blur",t,{item:this.active}),this.active=null)},_startOpening:function(t){clearTimeout(this.timer),"true"===t.attr("aria-hidden")&&(this.timer=this._delay(function(){this._close(),this._open(t)},this.delay))},_open:function(e){var i=t.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(e.parents(".ui-menu")).hide().attr("aria-hidden","true"),e.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(i)},collapseAll:function(e,i){clearTimeout(this.timer),this.timer=this._delay(function(){var s=i?this.element:t(e&&e.target).closest(this.element.find(".ui-menu"));s.length||(s=this.element),this._close(s),this.blur(e),this._removeClass(s.find(".ui-state-active"),null,"ui-state-active"),this.activeMenu=s},this.delay)},_close:function(t){t||(t=this.active?this.active.parent():this.element),t.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false")},_closeOnDocumentClick:function(e){return!t(e.target).closest(".ui-menu").length},_isDivider:function(t){return!/[^\-\u2014\u2013\s]/.test(t.text())},collapse:function(t){var e=this.active&&this.active.parent().closest(".ui-menu-item",this.element);e&&e.length&&(this._close(),this.focus(t,e))},expand:function(t){var e=this.active&&this.active.children(".ui-menu ").find(this.options.items).first();e&&e.length&&(this._open(e.parent()),this._delay(function(){this.focus(t,e)}))},next:function(t){this._move("next","first",t)},previous:function(t){this._move("prev","last",t)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(t,e,i){var s;this.active&&(s="first"===t||"last"===t?this.active["first"===t?"prevAll":"nextAll"](".ui-menu-item").eq(-1):this.active[t+"All"](".ui-menu-item").eq(0)),s&&s.length&&this.active||(s=this.activeMenu.find(this.options.items)[e]()),this.focus(i,s)},nextPage:function(e){var i,s,n;return this.active?(this.isLastItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return i=t(this),0>i.offset().top-s-n}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items)[this.active?"last":"first"]())),void 0):(this.next(e),void 0)},previousPage:function(e){var i,s,n;return this.active?(this.isFirstItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return i=t(this),i.offset().top-s+n>0}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items).first())),void 0):(this.next(e),void 0)},_hasScroll:function(){return this.element.outerHeight()",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var e,i,s,n=this.element[0].nodeName.toLowerCase(),o="textarea"===n,a="input"===n;this.isMultiLine=o||!a&&this._isContentEditable(this.element),this.valueMethod=this.element[o||a?"val":"text"],this.isNewMenu=!0,this._addClass("ui-autocomplete-input"),this.element.attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return e=!0,s=!0,i=!0,void 0;e=!1,s=!1,i=!1;var o=t.ui.keyCode;switch(n.keyCode){case o.PAGE_UP:e=!0,this._move("previousPage",n);break;case o.PAGE_DOWN:e=!0,this._move("nextPage",n);break;case o.UP:e=!0,this._keyEvent("previous",n);break;case o.DOWN:e=!0,this._keyEvent("next",n);break;case o.ENTER:this.menu.active&&(e=!0,n.preventDefault(),this.menu.select(n));break;case o.TAB:this.menu.active&&this.menu.select(n);break;case o.ESCAPE:this.menu.element.is(":visible")&&(this.isMultiLine||this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(e)return e=!1,(!this.isMultiLine||this.menu.element.is(":visible"))&&s.preventDefault(),void 0;if(!i){var n=t.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(t){return s?(s=!1,t.preventDefault(),void 0):(this._searchTimeout(t),void 0)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(t){return this.cancelBlur?(delete this.cancelBlur,void 0):(clearTimeout(this.searching),this.close(t),this._change(t),void 0)}}),this._initSource(),this.menu=t("
    UserAPI-keys{$lng['login']['username']}API-key SecretAllowed fromValid until{$lng['apikeys']['allowed_from']}{$lng['apikeys']['valid_until']} {$lng['panel']['options']}
     
     
    "+"",P=u?"":"",w=0;7>w;w++)M=(w+c)%7,P+="";for(T+=P+"",S=this._getDaysInMonth(te,Z),te===t.selectedYear&&Z===t.selectedMonth&&(t.selectedDay=Math.min(t.selectedDay,S)),H=(this._getFirstDayOfMonth(te,Z)-c+7)%7,z=Math.ceil((H+S)/7),O=X?this.maxRows>z?this.maxRows:z:z,this.maxRows=O,A=this._daylightSavingAdjust(new Date(te,Z,1-H)),N=0;O>N;N++){for(T+="",W=u?"":"",w=0;7>w;w++)E=m?m.apply(t.input?t.input[0]:null,[A]):[!0,""],F=A.getMonth()!==Z,L=F&&!v||!E[0]||Q&&Q>A||J&&A>J,W+="",A.setDate(A.getDate()+1),A=this._daylightSavingAdjust(A);T+=W+""}Z++,Z>11&&(Z=0,te++),T+="
    "+this._get(t,"weekHeader")+"=5?" class='ui-datepicker-week-end'":"")+">"+""+p[M]+"
    "+this._get(t,"calculateWeek")(A)+""+(F&&!_?" ":L?""+A.getDate()+"":""+A.getDate()+"")+"
    "+(X?""+(U[0]>0&&C===U[1]-1?"
    ":""):""),x+=T}y+=x}return y+=l,t._keyEvent=!1,y},_generateMonthYearHeader:function(t,e,i,s,n,o,a,r){var h,l,c,u,d,p,f,g,m=this._get(t,"changeMonth"),_=this._get(t,"changeYear"),v=this._get(t,"showMonthAfterYear"),b="
    ",y="";if(o||!m)y+=""+a[e]+"";else{for(h=s&&s.getFullYear()===i,l=n&&n.getFullYear()===i,y+=""}if(v||(b+=y+(!o&&m&&_?"":" ")),!t.yearshtml)if(t.yearshtml="",o||!_)b+=""+i+"";else{for(u=this._get(t,"yearRange").split(":"),d=(new Date).getFullYear(),p=function(t){var e=t.match(/c[+\-].*/)?i+parseInt(t.substring(1),10):t.match(/[+\-].*/)?d+parseInt(t,10):parseInt(t,10);return isNaN(e)?d:e},f=p(u[0]),g=Math.max(f,p(u[1]||"")),f=s?Math.max(f,s.getFullYear()):f,g=n?Math.min(g,n.getFullYear()):g,t.yearshtml+="",b+=t.yearshtml,t.yearshtml=null}return b+=this._get(t,"yearSuffix"),v&&(b+=(!o&&m&&_?"":" ")+y),b+="
    "},_adjustInstDate:function(t,e,i){var s=t.selectedYear+("Y"===i?e:0),n=t.selectedMonth+("M"===i?e:0),o=Math.min(t.selectedDay,this._getDaysInMonth(s,n))+("D"===i?e:0),a=this._restrictMinMax(t,this._daylightSavingAdjust(new Date(s,n,o)));t.selectedDay=a.getDate(),t.drawMonth=t.selectedMonth=a.getMonth(),t.drawYear=t.selectedYear=a.getFullYear(),("M"===i||"Y"===i)&&this._notifyChange(t)},_restrictMinMax:function(t,e){var i=this._getMinMaxDate(t,"min"),s=this._getMinMaxDate(t,"max"),n=i&&i>e?i:e;return s&&n>s?s:n},_notifyChange:function(t){var e=this._get(t,"onChangeMonthYear");e&&e.apply(t.input?t.input[0]:null,[t.selectedYear,t.selectedMonth+1,t])},_getNumberOfMonths:function(t){var e=this._get(t,"numberOfMonths");return null==e?[1,1]:"number"==typeof e?[1,e]:e},_getMinMaxDate:function(t,e){return this._determineDate(t,this._get(t,e+"Date"),null)},_getDaysInMonth:function(t,e){return 32-this._daylightSavingAdjust(new Date(t,e,32)).getDate()},_getFirstDayOfMonth:function(t,e){return new Date(t,e,1).getDay()},_canAdjustMonth:function(t,e,i,s){var n=this._getNumberOfMonths(t),o=this._daylightSavingAdjust(new Date(i,s+(0>e?e:n[0]*n[1]),1));return 0>e&&o.setDate(this._getDaysInMonth(o.getFullYear(),o.getMonth())),this._isInRange(t,o)},_isInRange:function(t,e){var i,s,n=this._getMinMaxDate(t,"min"),o=this._getMinMaxDate(t,"max"),a=null,r=null,h=this._get(t,"yearRange");return h&&(i=h.split(":"),s=(new Date).getFullYear(),a=parseInt(i[0],10),r=parseInt(i[1],10),i[0].match(/[+\-].*/)&&(a+=s),i[1].match(/[+\-].*/)&&(r+=s)),(!n||e.getTime()>=n.getTime())&&(!o||e.getTime()<=o.getTime())&&(!a||e.getFullYear()>=a)&&(!r||r>=e.getFullYear())},_getFormatConfig:function(t){var e=this._get(t,"shortYearCutoff");return e="string"!=typeof e?e:(new Date).getFullYear()%100+parseInt(e,10),{shortYearCutoff:e,dayNamesShort:this._get(t,"dayNamesShort"),dayNames:this._get(t,"dayNames"),monthNamesShort:this._get(t,"monthNamesShort"),monthNames:this._get(t,"monthNames")}},_formatDate:function(t,e,i,s){e||(t.currentDay=t.selectedDay,t.currentMonth=t.selectedMonth,t.currentYear=t.selectedYear);var n=e?"object"==typeof e?e:this._daylightSavingAdjust(new Date(s,i,e)):this._daylightSavingAdjust(new Date(t.currentYear,t.currentMonth,t.currentDay));return this.formatDate(this._get(t,"dateFormat"),n,this._getFormatConfig(t))}}),t.fn.datepicker=function(e){if(!this.length)return this;t.datepicker.initialized||(t(document).on("mousedown",t.datepicker._checkExternalClick),t.datepicker.initialized=!0),0===t("#"+t.datepicker._mainDivId).length&&t("body").append(t.datepicker.dpDiv);var i=Array.prototype.slice.call(arguments,1);return"string"!=typeof e||"isDisabled"!==e&&"getDate"!==e&&"widget"!==e?"option"===e&&2===arguments.length&&"string"==typeof arguments[1]?t.datepicker["_"+e+"Datepicker"].apply(t.datepicker,[this[0]].concat(i)):this.each(function(){"string"==typeof e?t.datepicker["_"+e+"Datepicker"].apply(t.datepicker,[this].concat(i)):t.datepicker._attachDatepicker(this,e)}):t.datepicker["_"+e+"Datepicker"].apply(t.datepicker,[this[0]].concat(i))},t.datepicker=new s,t.datepicker.initialized=!1,t.datepicker.uuid=(new Date).getTime(),t.datepicker.version="1.12.1",t.datepicker,t.widget("ui.dialog",{version:"1.12.1",options:{appendTo:"body",autoOpen:!0,buttons:[],classes:{"ui-dialog":"ui-corner-all","ui-dialog-titlebar":"ui-corner-all"},closeOnEscape:!0,closeText:"Close",draggable:!0,hide:null,height:"auto",maxHeight:null,maxWidth:null,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",of:window,collision:"fit",using:function(e){var i=t(this).css(e).offset().top;0>i&&t(this).css("top",e.top-i)}},resizable:!0,show:null,title:null,width:300,beforeClose:null,close:null,drag:null,dragStart:null,dragStop:null,focus:null,open:null,resize:null,resizeStart:null,resizeStop:null},sizeRelatedOptions:{buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},resizableRelatedOptions:{maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0},_create:function(){this.originalCss={display:this.element[0].style.display,width:this.element[0].style.width,minHeight:this.element[0].style.minHeight,maxHeight:this.element[0].style.maxHeight,height:this.element[0].style.height},this.originalPosition={parent:this.element.parent(),index:this.element.parent().children().index(this.element)},this.originalTitle=this.element.attr("title"),null==this.options.title&&null!=this.originalTitle&&(this.options.title=this.originalTitle),this.options.disabled&&(this.options.disabled=!1),this._createWrapper(),this.element.show().removeAttr("title").appendTo(this.uiDialog),this._addClass("ui-dialog-content","ui-widget-content"),this._createTitlebar(),this._createButtonPane(),this.options.draggable&&t.fn.draggable&&this._makeDraggable(),this.options.resizable&&t.fn.resizable&&this._makeResizable(),this._isOpen=!1,this._trackFocus()},_init:function(){this.options.autoOpen&&this.open()},_appendTo:function(){var e=this.options.appendTo;return e&&(e.jquery||e.nodeType)?t(e):this.document.find(e||"body").eq(0)},_destroy:function(){var t,e=this.originalPosition;this._untrackInstance(),this._destroyOverlay(),this.element.removeUniqueId().css(this.originalCss).detach(),this.uiDialog.remove(),this.originalTitle&&this.element.attr("title",this.originalTitle),t=e.parent.children().eq(e.index),t.length&&t[0]!==this.element[0]?t.before(this.element):e.parent.append(this.element)},widget:function(){return this.uiDialog},disable:t.noop,enable:t.noop,close:function(e){var i=this;this._isOpen&&this._trigger("beforeClose",e)!==!1&&(this._isOpen=!1,this._focusedElement=null,this._destroyOverlay(),this._untrackInstance(),this.opener.filter(":focusable").trigger("focus").length||t.ui.safeBlur(t.ui.safeActiveElement(this.document[0])),this._hide(this.uiDialog,this.options.hide,function(){i._trigger("close",e)}))},isOpen:function(){return this._isOpen},moveToTop:function(){this._moveToTop()},_moveToTop:function(e,i){var s=!1,n=this.uiDialog.siblings(".ui-front:visible").map(function(){return+t(this).css("z-index")}).get(),o=Math.max.apply(null,n);return o>=+this.uiDialog.css("z-index")&&(this.uiDialog.css("z-index",o+1),s=!0),s&&!i&&this._trigger("focus",e),s},open:function(){var e=this;return this._isOpen?(this._moveToTop()&&this._focusTabbable(),void 0):(this._isOpen=!0,this.opener=t(t.ui.safeActiveElement(this.document[0])),this._size(),this._position(),this._createOverlay(),this._moveToTop(null,!0),this.overlay&&this.overlay.css("z-index",this.uiDialog.css("z-index")-1),this._show(this.uiDialog,this.options.show,function(){e._focusTabbable(),e._trigger("focus")}),this._makeFocusTarget(),this._trigger("open"),void 0)},_focusTabbable:function(){var t=this._focusedElement;t||(t=this.element.find("[autofocus]")),t.length||(t=this.element.find(":tabbable")),t.length||(t=this.uiDialogButtonPane.find(":tabbable")),t.length||(t=this.uiDialogTitlebarClose.filter(":tabbable")),t.length||(t=this.uiDialog),t.eq(0).trigger("focus")},_keepFocus:function(e){function i(){var e=t.ui.safeActiveElement(this.document[0]),i=this.uiDialog[0]===e||t.contains(this.uiDialog[0],e);i||this._focusTabbable()}e.preventDefault(),i.call(this),this._delay(i)},_createWrapper:function(){this.uiDialog=t("
    ").hide().attr({tabIndex:-1,role:"dialog"}).appendTo(this._appendTo()),this._addClass(this.uiDialog,"ui-dialog","ui-widget ui-widget-content ui-front"),this._on(this.uiDialog,{keydown:function(e){if(this.options.closeOnEscape&&!e.isDefaultPrevented()&&e.keyCode&&e.keyCode===t.ui.keyCode.ESCAPE)return e.preventDefault(),this.close(e),void 0;if(e.keyCode===t.ui.keyCode.TAB&&!e.isDefaultPrevented()){var i=this.uiDialog.find(":tabbable"),s=i.filter(":first"),n=i.filter(":last");e.target!==n[0]&&e.target!==this.uiDialog[0]||e.shiftKey?e.target!==s[0]&&e.target!==this.uiDialog[0]||!e.shiftKey||(this._delay(function(){n.trigger("focus")}),e.preventDefault()):(this._delay(function(){s.trigger("focus")}),e.preventDefault())}},mousedown:function(t){this._moveToTop(t)&&this._focusTabbable()}}),this.element.find("[aria-describedby]").length||this.uiDialog.attr({"aria-describedby":this.element.uniqueId().attr("id")})},_createTitlebar:function(){var e;this.uiDialogTitlebar=t("
    "),this._addClass(this.uiDialogTitlebar,"ui-dialog-titlebar","ui-widget-header ui-helper-clearfix"),this._on(this.uiDialogTitlebar,{mousedown:function(e){t(e.target).closest(".ui-dialog-titlebar-close")||this.uiDialog.trigger("focus")}}),this.uiDialogTitlebarClose=t("").button({label:t("").text(this.options.closeText).html(),icon:"ui-icon-closethick",showLabel:!1}).appendTo(this.uiDialogTitlebar),this._addClass(this.uiDialogTitlebarClose,"ui-dialog-titlebar-close"),this._on(this.uiDialogTitlebarClose,{click:function(t){t.preventDefault(),this.close(t)}}),e=t("").uniqueId().prependTo(this.uiDialogTitlebar),this._addClass(e,"ui-dialog-title"),this._title(e),this.uiDialogTitlebar.prependTo(this.uiDialog),this.uiDialog.attr({"aria-labelledby":e.attr("id")})},_title:function(t){this.options.title?t.text(this.options.title):t.html(" ")},_createButtonPane:function(){this.uiDialogButtonPane=t("
    "),this._addClass(this.uiDialogButtonPane,"ui-dialog-buttonpane","ui-widget-content ui-helper-clearfix"),this.uiButtonSet=t("
    ").appendTo(this.uiDialogButtonPane),this._addClass(this.uiButtonSet,"ui-dialog-buttonset"),this._createButtons()},_createButtons:function(){var e=this,i=this.options.buttons;return this.uiDialogButtonPane.remove(),this.uiButtonSet.empty(),t.isEmptyObject(i)||t.isArray(i)&&!i.length?(this._removeClass(this.uiDialog,"ui-dialog-buttons"),void 0):(t.each(i,function(i,s){var n,o;s=t.isFunction(s)?{click:s,text:i}:s,s=t.extend({type:"button"},s),n=s.click,o={icon:s.icon,iconPosition:s.iconPosition,showLabel:s.showLabel,icons:s.icons,text:s.text},delete s.click,delete s.icon,delete s.iconPosition,delete s.showLabel,delete s.icons,"boolean"==typeof s.text&&delete s.text,t("",s).button(o).appendTo(e.uiButtonSet).on("click",function(){n.apply(e.element[0],arguments)})}),this._addClass(this.uiDialog,"ui-dialog-buttons"),this.uiDialogButtonPane.appendTo(this.uiDialog),void 0)},_makeDraggable:function(){function e(t){return{position:t.position,offset:t.offset}}var i=this,s=this.options;this.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(s,n){i._addClass(t(this),"ui-dialog-dragging"),i._blockFrames(),i._trigger("dragStart",s,e(n))},drag:function(t,s){i._trigger("drag",t,e(s))},stop:function(n,o){var a=o.offset.left-i.document.scrollLeft(),r=o.offset.top-i.document.scrollTop();s.position={my:"left top",at:"left"+(a>=0?"+":"")+a+" "+"top"+(r>=0?"+":"")+r,of:i.window},i._removeClass(t(this),"ui-dialog-dragging"),i._unblockFrames(),i._trigger("dragStop",n,e(o))}})},_makeResizable:function(){function e(t){return{originalPosition:t.originalPosition,originalSize:t.originalSize,position:t.position,size:t.size}}var i=this,s=this.options,n=s.resizable,o=this.uiDialog.css("position"),a="string"==typeof n?n:"n,e,s,w,se,sw,ne,nw";this.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:this.element,maxWidth:s.maxWidth,maxHeight:s.maxHeight,minWidth:s.minWidth,minHeight:this._minHeight(),handles:a,start:function(s,n){i._addClass(t(this),"ui-dialog-resizing"),i._blockFrames(),i._trigger("resizeStart",s,e(n))},resize:function(t,s){i._trigger("resize",t,e(s))},stop:function(n,o){var a=i.uiDialog.offset(),r=a.left-i.document.scrollLeft(),h=a.top-i.document.scrollTop();s.height=i.uiDialog.height(),s.width=i.uiDialog.width(),s.position={my:"left top",at:"left"+(r>=0?"+":"")+r+" "+"top"+(h>=0?"+":"")+h,of:i.window},i._removeClass(t(this),"ui-dialog-resizing"),i._unblockFrames(),i._trigger("resizeStop",n,e(o))}}).css("position",o)},_trackFocus:function(){this._on(this.widget(),{focusin:function(e){this._makeFocusTarget(),this._focusedElement=t(e.target)}})},_makeFocusTarget:function(){this._untrackInstance(),this._trackingInstances().unshift(this)},_untrackInstance:function(){var e=this._trackingInstances(),i=t.inArray(this,e);-1!==i&&e.splice(i,1)},_trackingInstances:function(){var t=this.document.data("ui-dialog-instances");return t||(t=[],this.document.data("ui-dialog-instances",t)),t},_minHeight:function(){var t=this.options;return"auto"===t.height?t.minHeight:Math.min(t.minHeight,t.height)},_position:function(){var t=this.uiDialog.is(":visible");t||this.uiDialog.show(),this.uiDialog.position(this.options.position),t||this.uiDialog.hide()},_setOptions:function(e){var i=this,s=!1,n={};t.each(e,function(t,e){i._setOption(t,e),t in i.sizeRelatedOptions&&(s=!0),t in i.resizableRelatedOptions&&(n[t]=e)}),s&&(this._size(),this._position()),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option",n)},_setOption:function(e,i){var s,n,o=this.uiDialog;"disabled"!==e&&(this._super(e,i),"appendTo"===e&&this.uiDialog.appendTo(this._appendTo()),"buttons"===e&&this._createButtons(),"closeText"===e&&this.uiDialogTitlebarClose.button({label:t("").text(""+this.options.closeText).html()}),"draggable"===e&&(s=o.is(":data(ui-draggable)"),s&&!i&&o.draggable("destroy"),!s&&i&&this._makeDraggable()),"position"===e&&this._position(),"resizable"===e&&(n=o.is(":data(ui-resizable)"),n&&!i&&o.resizable("destroy"),n&&"string"==typeof i&&o.resizable("option","handles",i),n||i===!1||this._makeResizable()),"title"===e&&this._title(this.uiDialogTitlebar.find(".ui-dialog-title")))},_size:function(){var t,e,i,s=this.options;this.element.show().css({width:"auto",minHeight:0,maxHeight:"none",height:0}),s.minWidth>s.width&&(s.width=s.minWidth),t=this.uiDialog.css({height:"auto",width:s.width}).outerHeight(),e=Math.max(0,s.minHeight-t),i="number"==typeof s.maxHeight?Math.max(0,s.maxHeight-t):"none","auto"===s.height?this.element.css({minHeight:e,maxHeight:i,height:"auto"}):this.element.height(Math.max(0,s.height-t)),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())},_blockFrames:function(){this.iframeBlocks=this.document.find("iframe").map(function(){var e=t(this);return t("
    ").css({position:"absolute",width:e.outerWidth(),height:e.outerHeight()}).appendTo(e.parent()).offset(e.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_allowInteraction:function(e){return t(e.target).closest(".ui-dialog").length?!0:!!t(e.target).closest(".ui-datepicker").length},_createOverlay:function(){if(this.options.modal){var e=!0;this._delay(function(){e=!1}),this.document.data("ui-dialog-overlays")||this._on(this.document,{focusin:function(t){e||this._allowInteraction(t)||(t.preventDefault(),this._trackingInstances()[0]._focusTabbable())}}),this.overlay=t("
    ").appendTo(this._appendTo()),this._addClass(this.overlay,null,"ui-widget-overlay ui-front"),this._on(this.overlay,{mousedown:"_keepFocus"}),this.document.data("ui-dialog-overlays",(this.document.data("ui-dialog-overlays")||0)+1)}},_destroyOverlay:function(){if(this.options.modal&&this.overlay){var t=this.document.data("ui-dialog-overlays")-1;t?this.document.data("ui-dialog-overlays",t):(this._off(this.document,"focusin"),this.document.removeData("ui-dialog-overlays")),this.overlay.remove(),this.overlay=null}}}),t.uiBackCompat!==!1&&t.widget("ui.dialog",t.ui.dialog,{options:{dialogClass:""},_createWrapper:function(){this._super(),this.uiDialog.addClass(this.options.dialogClass)},_setOption:function(t,e){"dialogClass"===t&&this.uiDialog.removeClass(this.options.dialogClass).addClass(e),this._superApply(arguments)}}),t.ui.dialog,t.widget("ui.progressbar",{version:"1.12.1",options:{classes:{"ui-progressbar":"ui-corner-all","ui-progressbar-value":"ui-corner-left","ui-progressbar-complete":"ui-corner-right"},max:100,value:0,change:null,complete:null},min:0,_create:function(){this.oldValue=this.options.value=this._constrainedValue(),this.element.attr({role:"progressbar","aria-valuemin":this.min}),this._addClass("ui-progressbar","ui-widget ui-widget-content"),this.valueDiv=t("
    ").appendTo(this.element),this._addClass(this.valueDiv,"ui-progressbar-value","ui-widget-header"),this._refreshValue()},_destroy:function(){this.element.removeAttr("role aria-valuemin aria-valuemax aria-valuenow"),this.valueDiv.remove()},value:function(t){return void 0===t?this.options.value:(this.options.value=this._constrainedValue(t),this._refreshValue(),void 0)},_constrainedValue:function(t){return void 0===t&&(t=this.options.value),this.indeterminate=t===!1,"number"!=typeof t&&(t=0),this.indeterminate?!1:Math.min(this.options.max,Math.max(this.min,t))},_setOptions:function(t){var e=t.value;delete t.value,this._super(t),this.options.value=this._constrainedValue(e),this._refreshValue()},_setOption:function(t,e){"max"===t&&(e=Math.max(this.min,e)),this._super(t,e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t),this._toggleClass(null,"ui-state-disabled",!!t)},_percentage:function(){return this.indeterminate?100:100*(this.options.value-this.min)/(this.options.max-this.min)},_refreshValue:function(){var e=this.options.value,i=this._percentage();this.valueDiv.toggle(this.indeterminate||e>this.min).width(i.toFixed(0)+"%"),this._toggleClass(this.valueDiv,"ui-progressbar-complete",null,e===this.options.max)._toggleClass("ui-progressbar-indeterminate",null,this.indeterminate),this.indeterminate?(this.element.removeAttr("aria-valuenow"),this.overlayDiv||(this.overlayDiv=t("
    ").appendTo(this.valueDiv),this._addClass(this.overlayDiv,"ui-progressbar-overlay"))):(this.element.attr({"aria-valuemax":this.options.max,"aria-valuenow":e}),this.overlayDiv&&(this.overlayDiv.remove(),this.overlayDiv=null)),this.oldValue!==e&&(this.oldValue=e,this._trigger("change")),e===this.options.max&&this._trigger("complete")}}),t.widget("ui.selectmenu",[t.ui.formResetMixin,{version:"1.12.1",defaultElement:"",widgetEventPrefix:"spin",options:{classes:{"ui-spinner":"ui-corner-all","ui-spinner-down":"ui-corner-br","ui-spinner-up":"ui-corner-tr"},culture:null,icons:{down:"ui-icon-triangle-1-s",up:"ui-icon-triangle-1-n"},incremental:!0,max:null,min:null,numberFormat:null,page:10,step:1,change:null,spin:null,start:null,stop:null},_create:function(){this._setOption("max",this.options.max),this._setOption("min",this.options.min),this._setOption("step",this.options.step),""!==this.value()&&this._value(this.element.val(),!0),this._draw(),this._on(this._events),this._refresh(),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_getCreateOptions:function(){var e=this._super(),i=this.element;return t.each(["min","max","step"],function(t,s){var n=i.attr(s);null!=n&&n.length&&(e[s]=n)}),e},_events:{keydown:function(t){this._start(t)&&this._keydown(t)&&t.preventDefault()},keyup:"_stop",focus:function(){this.previous=this.element.val()},blur:function(t){return this.cancelBlur?(delete this.cancelBlur,void 0):(this._stop(),this._refresh(),this.previous!==this.element.val()&&this._trigger("change",t),void 0)},mousewheel:function(t,e){if(e){if(!this.spinning&&!this._start(t))return!1;this._spin((e>0?1:-1)*this.options.step,t),clearTimeout(this.mousewheelTimer),this.mousewheelTimer=this._delay(function(){this.spinning&&this._stop(t)},100),t.preventDefault()}},"mousedown .ui-spinner-button":function(e){function i(){var e=this.element[0]===t.ui.safeActiveElement(this.document[0]);e||(this.element.trigger("focus"),this.previous=s,this._delay(function(){this.previous=s}))}var s;s=this.element[0]===t.ui.safeActiveElement(this.document[0])?this.previous:this.element.val(),e.preventDefault(),i.call(this),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur,i.call(this)}),this._start(e)!==!1&&this._repeat(null,t(e.currentTarget).hasClass("ui-spinner-up")?1:-1,e)},"mouseup .ui-spinner-button":"_stop","mouseenter .ui-spinner-button":function(e){return t(e.currentTarget).hasClass("ui-state-active")?this._start(e)===!1?!1:(this._repeat(null,t(e.currentTarget).hasClass("ui-spinner-up")?1:-1,e),void 0):void 0},"mouseleave .ui-spinner-button":"_stop"},_enhance:function(){this.uiSpinner=this.element.attr("autocomplete","off").wrap("").parent().append("")},_draw:function(){this._enhance(),this._addClass(this.uiSpinner,"ui-spinner","ui-widget ui-widget-content"),this._addClass("ui-spinner-input"),this.element.attr("role","spinbutton"),this.buttons=this.uiSpinner.children("a").attr("tabIndex",-1).attr("aria-hidden",!0).button({classes:{"ui-button":""}}),this._removeClass(this.buttons,"ui-corner-all"),this._addClass(this.buttons.first(),"ui-spinner-button ui-spinner-up"),this._addClass(this.buttons.last(),"ui-spinner-button ui-spinner-down"),this.buttons.first().button({icon:this.options.icons.up,showLabel:!1}),this.buttons.last().button({icon:this.options.icons.down,showLabel:!1}),this.buttons.height()>Math.ceil(.5*this.uiSpinner.height())&&this.uiSpinner.height()>0&&this.uiSpinner.height(this.uiSpinner.height())},_keydown:function(e){var i=this.options,s=t.ui.keyCode;switch(e.keyCode){case s.UP:return this._repeat(null,1,e),!0;case s.DOWN:return this._repeat(null,-1,e),!0;case s.PAGE_UP:return this._repeat(null,i.page,e),!0;case s.PAGE_DOWN:return this._repeat(null,-i.page,e),!0}return!1},_start:function(t){return this.spinning||this._trigger("start",t)!==!1?(this.counter||(this.counter=1),this.spinning=!0,!0):!1},_repeat:function(t,e,i){t=t||500,clearTimeout(this.timer),this.timer=this._delay(function(){this._repeat(40,e,i)},t),this._spin(e*this.options.step,i)},_spin:function(t,e){var i=this.value()||0;this.counter||(this.counter=1),i=this._adjustValue(i+t*this._increment(this.counter)),this.spinning&&this._trigger("spin",e,{value:i})===!1||(this._value(i),this.counter++)},_increment:function(e){var i=this.options.incremental;return i?t.isFunction(i)?i(e):Math.floor(e*e*e/5e4-e*e/500+17*e/200+1):1},_precision:function(){var t=this._precisionOf(this.options.step);return null!==this.options.min&&(t=Math.max(t,this._precisionOf(this.options.min))),t},_precisionOf:function(t){var e=""+t,i=e.indexOf(".");return-1===i?0:e.length-i-1},_adjustValue:function(t){var e,i,s=this.options;return e=null!==s.min?s.min:0,i=t-e,i=Math.round(i/s.step)*s.step,t=e+i,t=parseFloat(t.toFixed(this._precision())),null!==s.max&&t>s.max?s.max:null!==s.min&&s.min>t?s.min:t},_stop:function(t){this.spinning&&(clearTimeout(this.timer),clearTimeout(this.mousewheelTimer),this.counter=0,this.spinning=!1,this._trigger("stop",t))},_setOption:function(t,e){var i,s,n;return"culture"===t||"numberFormat"===t?(i=this._parse(this.element.val()),this.options[t]=e,this.element.val(this._format(i)),void 0):(("max"===t||"min"===t||"step"===t)&&"string"==typeof e&&(e=this._parse(e)),"icons"===t&&(s=this.buttons.first().find(".ui-icon"),this._removeClass(s,null,this.options.icons.up),this._addClass(s,null,e.up),n=this.buttons.last().find(".ui-icon"),this._removeClass(n,null,this.options.icons.down),this._addClass(n,null,e.down)),this._super(t,e),void 0)},_setOptionDisabled:function(t){this._super(t),this._toggleClass(this.uiSpinner,null,"ui-state-disabled",!!t),this.element.prop("disabled",!!t),this.buttons.button(t?"disable":"enable")},_setOptions:r(function(t){this._super(t)}),_parse:function(t){return"string"==typeof t&&""!==t&&(t=window.Globalize&&this.options.numberFormat?Globalize.parseFloat(t,10,this.options.culture):+t),""===t||isNaN(t)?null:t},_format:function(t){return""===t?"":window.Globalize&&this.options.numberFormat?Globalize.format(t,this.options.numberFormat,this.options.culture):t},_refresh:function(){this.element.attr({"aria-valuemin":this.options.min,"aria-valuemax":this.options.max,"aria-valuenow":this._parse(this.element.val())})},isValid:function(){var t=this.value();return null===t?!1:t===this._adjustValue(t)},_value:function(t,e){var i;""!==t&&(i=this._parse(t),null!==i&&(e||(i=this._adjustValue(i)),t=this._format(i))),this.element.val(t),this._refresh()},_destroy:function(){this.element.prop("disabled",!1).removeAttr("autocomplete role aria-valuemin aria-valuemax aria-valuenow"),this.uiSpinner.replaceWith(this.element)},stepUp:r(function(t){this._stepUp(t)}),_stepUp:function(t){this._start()&&(this._spin((t||1)*this.options.step),this._stop())},stepDown:r(function(t){this._stepDown(t)}),_stepDown:function(t){this._start()&&(this._spin((t||1)*-this.options.step),this._stop())},pageUp:r(function(t){this._stepUp((t||1)*this.options.page)}),pageDown:r(function(t){this._stepDown((t||1)*this.options.page)}),value:function(t){return arguments.length?(r(this._value).call(this,t),void 0):this._parse(this.element.val())},widget:function(){return this.uiSpinner}}),t.uiBackCompat!==!1&&t.widget("ui.spinner",t.ui.spinner,{_enhance:function(){this.uiSpinner=this.element.attr("autocomplete","off").wrap(this._uiSpinnerHtml()).parent().append(this._buttonHtml())},_uiSpinnerHtml:function(){return""},_buttonHtml:function(){return""}}),t.ui.spinner,t.widget("ui.tabs",{version:"1.12.1",delay:300,options:{active:null,classes:{"ui-tabs":"ui-corner-all","ui-tabs-nav":"ui-corner-all","ui-tabs-panel":"ui-corner-bottom","ui-tabs-tab":"ui-corner-top"},collapsible:!1,event:"click",heightStyle:"content",hide:null,show:null,activate:null,beforeActivate:null,beforeLoad:null,load:null},_isLocal:function(){var t=/#.*$/;return function(e){var i,s;i=e.href.replace(t,""),s=location.href.replace(t,"");try{i=decodeURIComponent(i)}catch(n){}try{s=decodeURIComponent(s)}catch(n){}return e.hash.length>1&&i===s}}(),_create:function(){var e=this,i=this.options;this.running=!1,this._addClass("ui-tabs","ui-widget ui-widget-content"),this._toggleClass("ui-tabs-collapsible",null,i.collapsible),this._processTabs(),i.active=this._initialActive(),t.isArray(i.disabled)&&(i.disabled=t.unique(i.disabled.concat(t.map(this.tabs.filter(".ui-state-disabled"),function(t){return e.tabs.index(t)}))).sort()),this.active=this.options.active!==!1&&this.anchors.length?this._findActive(i.active):t(),this._refresh(),this.active.length&&this.load(i.active)},_initialActive:function(){var e=this.options.active,i=this.options.collapsible,s=location.hash.substring(1);return null===e&&(s&&this.tabs.each(function(i,n){return t(n).attr("aria-controls")===s?(e=i,!1):void 0}),null===e&&(e=this.tabs.index(this.tabs.filter(".ui-tabs-active"))),(null===e||-1===e)&&(e=this.tabs.length?0:!1)),e!==!1&&(e=this.tabs.index(this.tabs.eq(e)),-1===e&&(e=i?!1:0)),!i&&e===!1&&this.anchors.length&&(e=0),e},_getCreateEventData:function(){return{tab:this.active,panel:this.active.length?this._getPanelForTab(this.active):t()}},_tabKeydown:function(e){var i=t(t.ui.safeActiveElement(this.document[0])).closest("li"),s=this.tabs.index(i),n=!0;if(!this._handlePageNav(e)){switch(e.keyCode){case t.ui.keyCode.RIGHT:case t.ui.keyCode.DOWN:s++;break;case t.ui.keyCode.UP:case t.ui.keyCode.LEFT:n=!1,s--;break;case t.ui.keyCode.END:s=this.anchors.length-1;break;case t.ui.keyCode.HOME:s=0;break;case t.ui.keyCode.SPACE:return e.preventDefault(),clearTimeout(this.activating),this._activate(s),void 0;case t.ui.keyCode.ENTER:return e.preventDefault(),clearTimeout(this.activating),this._activate(s===this.options.active?!1:s),void 0;default:return}e.preventDefault(),clearTimeout(this.activating),s=this._focusNextTab(s,n),e.ctrlKey||e.metaKey||(i.attr("aria-selected","false"),this.tabs.eq(s).attr("aria-selected","true"),this.activating=this._delay(function(){this.option("active",s)},this.delay))}},_panelKeydown:function(e){this._handlePageNav(e)||e.ctrlKey&&e.keyCode===t.ui.keyCode.UP&&(e.preventDefault(),this.active.trigger("focus"))},_handlePageNav:function(e){return e.altKey&&e.keyCode===t.ui.keyCode.PAGE_UP?(this._activate(this._focusNextTab(this.options.active-1,!1)),!0):e.altKey&&e.keyCode===t.ui.keyCode.PAGE_DOWN?(this._activate(this._focusNextTab(this.options.active+1,!0)),!0):void 0},_findNextTab:function(e,i){function s(){return e>n&&(e=0),0>e&&(e=n),e}for(var n=this.tabs.length-1;-1!==t.inArray(s(),this.options.disabled);)e=i?e+1:e-1;return e},_focusNextTab:function(t,e){return t=this._findNextTab(t,e),this.tabs.eq(t).trigger("focus"),t},_setOption:function(t,e){return"active"===t?(this._activate(e),void 0):(this._super(t,e),"collapsible"===t&&(this._toggleClass("ui-tabs-collapsible",null,e),e||this.options.active!==!1||this._activate(0)),"event"===t&&this._setupEvents(e),"heightStyle"===t&&this._setupHeightStyle(e),void 0)},_sanitizeSelector:function(t){return t?t.replace(/[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g,"\\$&"):""},refresh:function(){var e=this.options,i=this.tablist.children(":has(a[href])");e.disabled=t.map(i.filter(".ui-state-disabled"),function(t){return i.index(t)}),this._processTabs(),e.active!==!1&&this.anchors.length?this.active.length&&!t.contains(this.tablist[0],this.active[0])?this.tabs.length===e.disabled.length?(e.active=!1,this.active=t()):this._activate(this._findNextTab(Math.max(0,e.active-1),!1)):e.active=this.tabs.index(this.active):(e.active=!1,this.active=t()),this._refresh()},_refresh:function(){this._setOptionDisabled(this.options.disabled),this._setupEvents(this.options.event),this._setupHeightStyle(this.options.heightStyle),this.tabs.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}),this.panels.not(this._getPanelForTab(this.active)).hide().attr({"aria-hidden":"true"}),this.active.length?(this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}),this._addClass(this.active,"ui-tabs-active","ui-state-active"),this._getPanelForTab(this.active).show().attr({"aria-hidden":"false"})):this.tabs.eq(0).attr("tabIndex",0)},_processTabs:function(){var e=this,i=this.tabs,s=this.anchors,n=this.panels;this.tablist=this._getList().attr("role","tablist"),this._addClass(this.tablist,"ui-tabs-nav","ui-helper-reset ui-helper-clearfix ui-widget-header"),this.tablist.on("mousedown"+this.eventNamespace,"> li",function(e){t(this).is(".ui-state-disabled")&&e.preventDefault()}).on("focus"+this.eventNamespace,".ui-tabs-anchor",function(){t(this).closest("li").is(".ui-state-disabled")&&this.blur()}),this.tabs=this.tablist.find("> li:has(a[href])").attr({role:"tab",tabIndex:-1}),this._addClass(this.tabs,"ui-tabs-tab","ui-state-default"),this.anchors=this.tabs.map(function(){return t("a",this)[0]}).attr({role:"presentation",tabIndex:-1}),this._addClass(this.anchors,"ui-tabs-anchor"),this.panels=t(),this.anchors.each(function(i,s){var n,o,a,r=t(s).uniqueId().attr("id"),h=t(s).closest("li"),l=h.attr("aria-controls");e._isLocal(s)?(n=s.hash,a=n.substring(1),o=e.element.find(e._sanitizeSelector(n))):(a=h.attr("aria-controls")||t({}).uniqueId()[0].id,n="#"+a,o=e.element.find(n),o.length||(o=e._createPanel(a),o.insertAfter(e.panels[i-1]||e.tablist)),o.attr("aria-live","polite")),o.length&&(e.panels=e.panels.add(o)),l&&h.data("ui-tabs-aria-controls",l),h.attr({"aria-controls":a,"aria-labelledby":r}),o.attr("aria-labelledby",r)}),this.panels.attr("role","tabpanel"),this._addClass(this.panels,"ui-tabs-panel","ui-widget-content"),i&&(this._off(i.not(this.tabs)),this._off(s.not(this.anchors)),this._off(n.not(this.panels)))},_getList:function(){return this.tablist||this.element.find("ol, ul").eq(0)},_createPanel:function(e){return t("
    ").attr("id",e).data("ui-tabs-destroy",!0)},_setOptionDisabled:function(e){var i,s,n;for(t.isArray(e)&&(e.length?e.length===this.anchors.length&&(e=!0):e=!1),n=0;s=this.tabs[n];n++)i=t(s),e===!0||-1!==t.inArray(n,e)?(i.attr("aria-disabled","true"),this._addClass(i,null,"ui-state-disabled")):(i.removeAttr("aria-disabled"),this._removeClass(i,null,"ui-state-disabled"));this.options.disabled=e,this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,e===!0)},_setupEvents:function(e){var i={};e&&t.each(e.split(" "),function(t,e){i[e]="_eventHandler"}),this._off(this.anchors.add(this.tabs).add(this.panels)),this._on(!0,this.anchors,{click:function(t){t.preventDefault()}}),this._on(this.anchors,i),this._on(this.tabs,{keydown:"_tabKeydown"}),this._on(this.panels,{keydown:"_panelKeydown"}),this._focusable(this.tabs),this._hoverable(this.tabs)},_setupHeightStyle:function(e){var i,s=this.element.parent();"fill"===e?(i=s.height(),i-=this.element.outerHeight()-this.element.height(),this.element.siblings(":visible").each(function(){var e=t(this),s=e.css("position");"absolute"!==s&&"fixed"!==s&&(i-=e.outerHeight(!0))}),this.element.children().not(this.panels).each(function(){i-=t(this).outerHeight(!0)}),this.panels.each(function(){t(this).height(Math.max(0,i-t(this).innerHeight()+t(this).height()))}).css("overflow","auto")):"auto"===e&&(i=0,this.panels.each(function(){i=Math.max(i,t(this).height("").height())}).height(i))},_eventHandler:function(e){var i=this.options,s=this.active,n=t(e.currentTarget),o=n.closest("li"),a=o[0]===s[0],r=a&&i.collapsible,h=r?t():this._getPanelForTab(o),l=s.length?this._getPanelForTab(s):t(),c={oldTab:s,oldPanel:l,newTab:r?t():o,newPanel:h};e.preventDefault(),o.hasClass("ui-state-disabled")||o.hasClass("ui-tabs-loading")||this.running||a&&!i.collapsible||this._trigger("beforeActivate",e,c)===!1||(i.active=r?!1:this.tabs.index(o),this.active=a?t():o,this.xhr&&this.xhr.abort(),l.length||h.length||t.error("jQuery UI Tabs: Mismatching fragment identifier."),h.length&&this.load(this.tabs.index(o),e),this._toggle(e,c))},_toggle:function(e,i){function s(){o.running=!1,o._trigger("activate",e,i)}function n(){o._addClass(i.newTab.closest("li"),"ui-tabs-active","ui-state-active"),a.length&&o.options.show?o._show(a,o.options.show,s):(a.show(),s())}var o=this,a=i.newPanel,r=i.oldPanel;this.running=!0,r.length&&this.options.hide?this._hide(r,this.options.hide,function(){o._removeClass(i.oldTab.closest("li"),"ui-tabs-active","ui-state-active"),n()}):(this._removeClass(i.oldTab.closest("li"),"ui-tabs-active","ui-state-active"),r.hide(),n()),r.attr("aria-hidden","true"),i.oldTab.attr({"aria-selected":"false","aria-expanded":"false"}),a.length&&r.length?i.oldTab.attr("tabIndex",-1):a.length&&this.tabs.filter(function(){return 0===t(this).attr("tabIndex")}).attr("tabIndex",-1),a.attr("aria-hidden","false"),i.newTab.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_activate:function(e){var i,s=this._findActive(e);s[0]!==this.active[0]&&(s.length||(s=this.active),i=s.find(".ui-tabs-anchor")[0],this._eventHandler({target:i,currentTarget:i,preventDefault:t.noop}))},_findActive:function(e){return e===!1?t():this.tabs.eq(e)},_getIndex:function(e){return"string"==typeof e&&(e=this.anchors.index(this.anchors.filter("[href$='"+t.ui.escapeSelector(e)+"']"))),e},_destroy:function(){this.xhr&&this.xhr.abort(),this.tablist.removeAttr("role").off(this.eventNamespace),this.anchors.removeAttr("role tabIndex").removeUniqueId(),this.tabs.add(this.panels).each(function(){t.data(this,"ui-tabs-destroy")?t(this).remove():t(this).removeAttr("role tabIndex aria-live aria-busy aria-selected aria-labelledby aria-hidden aria-expanded")}),this.tabs.each(function(){var e=t(this),i=e.data("ui-tabs-aria-controls");i?e.attr("aria-controls",i).removeData("ui-tabs-aria-controls"):e.removeAttr("aria-controls")}),this.panels.show(),"content"!==this.options.heightStyle&&this.panels.css("height","")},enable:function(e){var i=this.options.disabled;i!==!1&&(void 0===e?i=!1:(e=this._getIndex(e),i=t.isArray(i)?t.map(i,function(t){return t!==e?t:null}):t.map(this.tabs,function(t,i){return i!==e?i:null})),this._setOptionDisabled(i))},disable:function(e){var i=this.options.disabled;if(i!==!0){if(void 0===e)i=!0;else{if(e=this._getIndex(e),-1!==t.inArray(e,i))return;i=t.isArray(i)?t.merge([e],i).sort():[e]}this._setOptionDisabled(i)}},load:function(e,i){e=this._getIndex(e);var s=this,n=this.tabs.eq(e),o=n.find(".ui-tabs-anchor"),a=this._getPanelForTab(n),r={tab:n,panel:a},h=function(t,e){"abort"===e&&s.panels.stop(!1,!0),s._removeClass(n,"ui-tabs-loading"),a.removeAttr("aria-busy"),t===s.xhr&&delete s.xhr};this._isLocal(o[0])||(this.xhr=t.ajax(this._ajaxSettings(o,i,r)),this.xhr&&"canceled"!==this.xhr.statusText&&(this._addClass(n,"ui-tabs-loading"),a.attr("aria-busy","true"),this.xhr.done(function(t,e,n){setTimeout(function(){a.html(t),s._trigger("load",i,r),h(n,e)},1)}).fail(function(t,e){setTimeout(function(){h(t,e)},1)})))},_ajaxSettings:function(e,i,s){var n=this;return{url:e.attr("href").replace(/#.*$/,""),beforeSend:function(e,o){return n._trigger("beforeLoad",i,t.extend({jqXHR:e,ajaxSettings:o},s))}}},_getPanelForTab:function(e){var i=t(e).attr("aria-controls");return this.element.find(this._sanitizeSelector("#"+i))}}),t.uiBackCompat!==!1&&t.widget("ui.tabs",t.ui.tabs,{_processTabs:function(){this._superApply(arguments),this._addClass(this.tabs,"ui-tab")}}),t.ui.tabs,t.widget("ui.tooltip",{version:"1.12.1",options:{classes:{"ui-tooltip":"ui-corner-all ui-widget-shadow"},content:function(){var e=t(this).attr("title")||"";return t("").text(e).html()},hide:!0,items:"[title]:not([disabled])",position:{my:"left top+15",at:"left bottom",collision:"flipfit flip"},show:!0,track:!1,close:null,open:null},_addDescribedBy:function(e,i){var s=(e.attr("aria-describedby")||"").split(/\s+/);s.push(i),e.data("ui-tooltip-id",i).attr("aria-describedby",t.trim(s.join(" ")))},_removeDescribedBy:function(e){var i=e.data("ui-tooltip-id"),s=(e.attr("aria-describedby")||"").split(/\s+/),n=t.inArray(i,s);-1!==n&&s.splice(n,1),e.removeData("ui-tooltip-id"),s=t.trim(s.join(" ")),s?e.attr("aria-describedby",s):e.removeAttr("aria-describedby")},_create:function(){this._on({mouseover:"open",focusin:"open"}),this.tooltips={},this.parents={},this.liveRegion=t("
    ").attr({role:"log","aria-live":"assertive","aria-relevant":"additions"}).appendTo(this.document[0].body),this._addClass(this.liveRegion,null,"ui-helper-hidden-accessible"),this.disabledTitles=t([])},_setOption:function(e,i){var s=this;this._super(e,i),"content"===e&&t.each(this.tooltips,function(t,e){s._updateContent(e.element)})},_setOptionDisabled:function(t){this[t?"_disable":"_enable"]()},_disable:function(){var e=this;t.each(this.tooltips,function(i,s){var n=t.Event("blur");n.target=n.currentTarget=s.element[0],e.close(n,!0)}),this.disabledTitles=this.disabledTitles.add(this.element.find(this.options.items).addBack().filter(function(){var e=t(this);return e.is("[title]")?e.data("ui-tooltip-title",e.attr("title")).removeAttr("title"):void 0}))},_enable:function(){this.disabledTitles.each(function(){var e=t(this);e.data("ui-tooltip-title")&&e.attr("title",e.data("ui-tooltip-title"))}),this.disabledTitles=t([])},open:function(e){var i=this,s=t(e?e.target:this.element).closest(this.options.items);s.length&&!s.data("ui-tooltip-id")&&(s.attr("title")&&s.data("ui-tooltip-title",s.attr("title")),s.data("ui-tooltip-open",!0),e&&"mouseover"===e.type&&s.parents().each(function(){var e,s=t(this);s.data("ui-tooltip-open")&&(e=t.Event("blur"),e.target=e.currentTarget=this,i.close(e,!0)),s.attr("title")&&(s.uniqueId(),i.parents[this.id]={element:this,title:s.attr("title")},s.attr("title",""))}),this._registerCloseHandlers(e,s),this._updateContent(s,e))},_updateContent:function(t,e){var i,s=this.options.content,n=this,o=e?e.type:null;return"string"==typeof s||s.nodeType||s.jquery?this._open(e,t,s):(i=s.call(t[0],function(i){n._delay(function(){t.data("ui-tooltip-open")&&(e&&(e.type=o),this._open(e,t,i))})}),i&&this._open(e,t,i),void 0)},_open:function(e,i,s){function n(t){l.of=t,a.is(":hidden")||a.position(l)}var o,a,r,h,l=t.extend({},this.options.position);if(s){if(o=this._find(i))return o.tooltip.find(".ui-tooltip-content").html(s),void 0;i.is("[title]")&&(e&&"mouseover"===e.type?i.attr("title",""):i.removeAttr("title")),o=this._tooltip(i),a=o.tooltip,this._addDescribedBy(i,a.attr("id")),a.find(".ui-tooltip-content").html(s),this.liveRegion.children().hide(),h=t("
    ").html(a.find(".ui-tooltip-content").html()),h.removeAttr("name").find("[name]").removeAttr("name"),h.removeAttr("id").find("[id]").removeAttr("id"),h.appendTo(this.liveRegion),this.options.track&&e&&/^mouse/.test(e.type)?(this._on(this.document,{mousemove:n}),n(e)):a.position(t.extend({of:i},this.options.position)),a.hide(),this._show(a,this.options.show),this.options.track&&this.options.show&&this.options.show.delay&&(r=this.delayedShow=setInterval(function(){a.is(":visible")&&(n(l.of),clearInterval(r))},t.fx.interval)),this._trigger("open",e,{tooltip:a})}},_registerCloseHandlers:function(e,i){var s={keyup:function(e){if(e.keyCode===t.ui.keyCode.ESCAPE){var s=t.Event(e);s.currentTarget=i[0],this.close(s,!0)}}};i[0]!==this.element[0]&&(s.remove=function(){this._removeTooltip(this._find(i).tooltip)}),e&&"mouseover"!==e.type||(s.mouseleave="close"),e&&"focusin"!==e.type||(s.focusout="close"),this._on(!0,i,s)},close:function(e){var i,s=this,n=t(e?e.currentTarget:this.element),o=this._find(n);return o?(i=o.tooltip,o.closing||(clearInterval(this.delayedShow),n.data("ui-tooltip-title")&&!n.attr("title")&&n.attr("title",n.data("ui-tooltip-title")),this._removeDescribedBy(n),o.hiding=!0,i.stop(!0),this._hide(i,this.options.hide,function(){s._removeTooltip(t(this))}),n.removeData("ui-tooltip-open"),this._off(n,"mouseleave focusout keyup"),n[0]!==this.element[0]&&this._off(n,"remove"),this._off(this.document,"mousemove"),e&&"mouseleave"===e.type&&t.each(this.parents,function(e,i){t(i.element).attr("title",i.title),delete s.parents[e] +}),o.closing=!0,this._trigger("close",e,{tooltip:i}),o.hiding||(o.closing=!1)),void 0):(n.removeData("ui-tooltip-open"),void 0)},_tooltip:function(e){var i=t("
    ").attr("role","tooltip"),s=t("
    ").appendTo(i),n=i.uniqueId().attr("id");return this._addClass(s,"ui-tooltip-content"),this._addClass(i,"ui-tooltip","ui-widget ui-widget-content"),i.appendTo(this._appendTo(e)),this.tooltips[n]={element:e,tooltip:i}},_find:function(t){var e=t.data("ui-tooltip-id");return e?this.tooltips[e]:null},_removeTooltip:function(t){t.remove(),delete this.tooltips[t.attr("id")]},_appendTo:function(t){var e=t.closest(".ui-front, dialog");return e.length||(e=this.document[0].body),e},_destroy:function(){var e=this;t.each(this.tooltips,function(i,s){var n=t.Event("blur"),o=s.element;n.target=n.currentTarget=o[0],e.close(n,!0),t("#"+i).remove(),o.data("ui-tooltip-title")&&(o.attr("title")||o.attr("title",o.data("ui-tooltip-title")),o.removeData("ui-tooltip-title"))}),this.liveRegion.remove()}}),t.uiBackCompat!==!1&&t.widget("ui.tooltip",t.ui.tooltip,{options:{tooltipClass:null},_tooltip:function(){var t=this._superApply(arguments);return this.options.tooltipClass&&t.tooltip.addClass(this.options.tooltipClass),t}}),t.ui.tooltip;var f="ui-effects-",g="ui-effects-style",m="ui-effects-animated",_=t;t.effects={effect:{}},function(t,e){function i(t,e,i){var s=u[e.type]||{};return null==t?i||!e.def?null:e.def:(t=s.floor?~~t:parseFloat(t),isNaN(t)?e.def:s.mod?(t+s.mod)%s.mod:0>t?0:t>s.max?s.max:t)}function s(i){var s=l(),n=s._rgba=[];return i=i.toLowerCase(),f(h,function(t,o){var a,r=o.re.exec(i),h=r&&o.parse(r),l=o.space||"rgba";return h?(a=s[l](h),s[c[l].cache]=a[c[l].cache],n=s._rgba=a._rgba,!1):e}),n.length?("0,0,0,0"===n.join()&&t.extend(n,o.transparent),s):o[i]}function n(t,e,i){return i=(i+1)%1,1>6*i?t+6*(e-t)*i:1>2*i?e:2>3*i?t+6*(e-t)*(2/3-i):t}var o,a="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",r=/^([\-+])=\s*(\d+\.?\d*)/,h=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[t[1],t[2],t[3],t[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[2.55*t[1],2.55*t[2],2.55*t[3],t[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(t){return[t[1],t[2]/100,t[3]/100,t[4]]}}],l=t.Color=function(e,i,s,n){return new t.Color.fn.parse(e,i,s,n)},c={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},u={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},d=l.support={},p=t("

    ")[0],f=t.each;p.style.cssText="background-color:rgba(1,1,1,.5)",d.rgba=p.style.backgroundColor.indexOf("rgba")>-1,f(c,function(t,e){e.cache="_"+t,e.props.alpha={idx:3,type:"percent",def:1}}),l.fn=t.extend(l.prototype,{parse:function(n,a,r,h){if(n===e)return this._rgba=[null,null,null,null],this;(n.jquery||n.nodeType)&&(n=t(n).css(a),a=e);var u=this,d=t.type(n),p=this._rgba=[];return a!==e&&(n=[n,a,r,h],d="array"),"string"===d?this.parse(s(n)||o._default):"array"===d?(f(c.rgba.props,function(t,e){p[e.idx]=i(n[e.idx],e)}),this):"object"===d?(n instanceof l?f(c,function(t,e){n[e.cache]&&(u[e.cache]=n[e.cache].slice())}):f(c,function(e,s){var o=s.cache;f(s.props,function(t,e){if(!u[o]&&s.to){if("alpha"===t||null==n[t])return;u[o]=s.to(u._rgba)}u[o][e.idx]=i(n[t],e,!0)}),u[o]&&0>t.inArray(null,u[o].slice(0,3))&&(u[o][3]=1,s.from&&(u._rgba=s.from(u[o])))}),this):e},is:function(t){var i=l(t),s=!0,n=this;return f(c,function(t,o){var a,r=i[o.cache];return r&&(a=n[o.cache]||o.to&&o.to(n._rgba)||[],f(o.props,function(t,i){return null!=r[i.idx]?s=r[i.idx]===a[i.idx]:e})),s}),s},_space:function(){var t=[],e=this;return f(c,function(i,s){e[s.cache]&&t.push(i)}),t.pop()},transition:function(t,e){var s=l(t),n=s._space(),o=c[n],a=0===this.alpha()?l("transparent"):this,r=a[o.cache]||o.to(a._rgba),h=r.slice();return s=s[o.cache],f(o.props,function(t,n){var o=n.idx,a=r[o],l=s[o],c=u[n.type]||{};null!==l&&(null===a?h[o]=l:(c.mod&&(l-a>c.mod/2?a+=c.mod:a-l>c.mod/2&&(a-=c.mod)),h[o]=i((l-a)*e+a,n)))}),this[n](h)},blend:function(e){if(1===this._rgba[3])return this;var i=this._rgba.slice(),s=i.pop(),n=l(e)._rgba;return l(t.map(i,function(t,e){return(1-s)*n[e]+s*t}))},toRgbaString:function(){var e="rgba(",i=t.map(this._rgba,function(t,e){return null==t?e>2?1:0:t});return 1===i[3]&&(i.pop(),e="rgb("),e+i.join()+")"},toHslaString:function(){var e="hsla(",i=t.map(this.hsla(),function(t,e){return null==t&&(t=e>2?1:0),e&&3>e&&(t=Math.round(100*t)+"%"),t});return 1===i[3]&&(i.pop(),e="hsl("),e+i.join()+")"},toHexString:function(e){var i=this._rgba.slice(),s=i.pop();return e&&i.push(~~(255*s)),"#"+t.map(i,function(t){return t=(t||0).toString(16),1===t.length?"0"+t:t}).join("")},toString:function(){return 0===this._rgba[3]?"transparent":this.toRgbaString()}}),l.fn.parse.prototype=l.fn,c.hsla.to=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e,i,s=t[0]/255,n=t[1]/255,o=t[2]/255,a=t[3],r=Math.max(s,n,o),h=Math.min(s,n,o),l=r-h,c=r+h,u=.5*c;return e=h===r?0:s===r?60*(n-o)/l+360:n===r?60*(o-s)/l+120:60*(s-n)/l+240,i=0===l?0:.5>=u?l/c:l/(2-c),[Math.round(e)%360,i,u,null==a?1:a]},c.hsla.from=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e=t[0]/360,i=t[1],s=t[2],o=t[3],a=.5>=s?s*(1+i):s+i-s*i,r=2*s-a;return[Math.round(255*n(r,a,e+1/3)),Math.round(255*n(r,a,e)),Math.round(255*n(r,a,e-1/3)),o]},f(c,function(s,n){var o=n.props,a=n.cache,h=n.to,c=n.from;l.fn[s]=function(s){if(h&&!this[a]&&(this[a]=h(this._rgba)),s===e)return this[a].slice();var n,r=t.type(s),u="array"===r||"object"===r?s:arguments,d=this[a].slice();return f(o,function(t,e){var s=u["object"===r?t:e.idx];null==s&&(s=d[e.idx]),d[e.idx]=i(s,e)}),c?(n=l(c(d)),n[a]=d,n):l(d)},f(o,function(e,i){l.fn[e]||(l.fn[e]=function(n){var o,a=t.type(n),h="alpha"===e?this._hsla?"hsla":"rgba":s,l=this[h](),c=l[i.idx];return"undefined"===a?c:("function"===a&&(n=n.call(this,c),a=t.type(n)),null==n&&i.empty?this:("string"===a&&(o=r.exec(n),o&&(n=c+parseFloat(o[2])*("+"===o[1]?1:-1))),l[i.idx]=n,this[h](l)))})})}),l.hook=function(e){var i=e.split(" ");f(i,function(e,i){t.cssHooks[i]={set:function(e,n){var o,a,r="";if("transparent"!==n&&("string"!==t.type(n)||(o=s(n)))){if(n=l(o||n),!d.rgba&&1!==n._rgba[3]){for(a="backgroundColor"===i?e.parentNode:e;(""===r||"transparent"===r)&&a&&a.style;)try{r=t.css(a,"backgroundColor"),a=a.parentNode}catch(h){}n=n.blend(r&&"transparent"!==r?r:"_default")}n=n.toRgbaString()}try{e.style[i]=n}catch(h){}}},t.fx.step[i]=function(e){e.colorInit||(e.start=l(e.elem,i),e.end=l(e.end),e.colorInit=!0),t.cssHooks[i].set(e.elem,e.start.transition(e.end,e.pos))}})},l.hook(a),t.cssHooks.borderColor={expand:function(t){var e={};return f(["Top","Right","Bottom","Left"],function(i,s){e["border"+s+"Color"]=t}),e}},o=t.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(_),function(){function e(e){var i,s,n=e.ownerDocument.defaultView?e.ownerDocument.defaultView.getComputedStyle(e,null):e.currentStyle,o={};if(n&&n.length&&n[0]&&n[n[0]])for(s=n.length;s--;)i=n[s],"string"==typeof n[i]&&(o[t.camelCase(i)]=n[i]);else for(i in n)"string"==typeof n[i]&&(o[i]=n[i]);return o}function i(e,i){var s,o,a={};for(s in i)o=i[s],e[s]!==o&&(n[s]||(t.fx.step[s]||!isNaN(parseFloat(o)))&&(a[s]=o));return a}var s=["add","remove","toggle"],n={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};t.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(e,i){t.fx.step[i]=function(t){("none"!==t.end&&!t.setAttr||1===t.pos&&!t.setAttr)&&(_.style(t.elem,i,t.end),t.setAttr=!0)}}),t.fn.addBack||(t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.effects.animateClass=function(n,o,a,r){var h=t.speed(o,a,r);return this.queue(function(){var o,a=t(this),r=a.attr("class")||"",l=h.children?a.find("*").addBack():a;l=l.map(function(){var i=t(this);return{el:i,start:e(this)}}),o=function(){t.each(s,function(t,e){n[e]&&a[e+"Class"](n[e])})},o(),l=l.map(function(){return this.end=e(this.el[0]),this.diff=i(this.start,this.end),this}),a.attr("class",r),l=l.map(function(){var e=this,i=t.Deferred(),s=t.extend({},h,{queue:!1,complete:function(){i.resolve(e)}});return this.el.animate(this.diff,s),i.promise()}),t.when.apply(t,l.get()).done(function(){o(),t.each(arguments,function(){var e=this.el;t.each(this.diff,function(t){e.css(t,"")})}),h.complete.call(a[0])})})},t.fn.extend({addClass:function(e){return function(i,s,n,o){return s?t.effects.animateClass.call(this,{add:i},s,n,o):e.apply(this,arguments)}}(t.fn.addClass),removeClass:function(e){return function(i,s,n,o){return arguments.length>1?t.effects.animateClass.call(this,{remove:i},s,n,o):e.apply(this,arguments)}}(t.fn.removeClass),toggleClass:function(e){return function(i,s,n,o,a){return"boolean"==typeof s||void 0===s?n?t.effects.animateClass.call(this,s?{add:i}:{remove:i},n,o,a):e.apply(this,arguments):t.effects.animateClass.call(this,{toggle:i},s,n,o)}}(t.fn.toggleClass),switchClass:function(e,i,s,n,o){return t.effects.animateClass.call(this,{add:i,remove:e},s,n,o)}})}(),function(){function e(e,i,s,n){return t.isPlainObject(e)&&(i=e,e=e.effect),e={effect:e},null==i&&(i={}),t.isFunction(i)&&(n=i,s=null,i={}),("number"==typeof i||t.fx.speeds[i])&&(n=s,s=i,i={}),t.isFunction(s)&&(n=s,s=null),i&&t.extend(e,i),s=s||i.duration,e.duration=t.fx.off?0:"number"==typeof s?s:s in t.fx.speeds?t.fx.speeds[s]:t.fx.speeds._default,e.complete=n||i.complete,e}function i(e){return!e||"number"==typeof e||t.fx.speeds[e]?!0:"string"!=typeof e||t.effects.effect[e]?t.isFunction(e)?!0:"object"!=typeof e||e.effect?!1:!0:!0}function s(t,e){var i=e.outerWidth(),s=e.outerHeight(),n=/^rect\((-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto)\)$/,o=n.exec(t)||["",0,i,s,0];return{top:parseFloat(o[1])||0,right:"auto"===o[2]?i:parseFloat(o[2]),bottom:"auto"===o[3]?s:parseFloat(o[3]),left:parseFloat(o[4])||0}}t.expr&&t.expr.filters&&t.expr.filters.animated&&(t.expr.filters.animated=function(e){return function(i){return!!t(i).data(m)||e(i)}}(t.expr.filters.animated)),t.uiBackCompat!==!1&&t.extend(t.effects,{save:function(t,e){for(var i=0,s=e.length;s>i;i++)null!==e[i]&&t.data(f+e[i],t[0].style[e[i]])},restore:function(t,e){for(var i,s=0,n=e.length;n>s;s++)null!==e[s]&&(i=t.data(f+e[s]),t.css(e[s],i))},setMode:function(t,e){return"toggle"===e&&(e=t.is(":hidden")?"show":"hide"),e},createWrapper:function(e){if(e.parent().is(".ui-effects-wrapper"))return e.parent();var i={width:e.outerWidth(!0),height:e.outerHeight(!0),"float":e.css("float")},s=t("

    ").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),n={width:e.width(),height:e.height()},o=document.activeElement;try{o.id}catch(a){o=document.body}return e.wrap(s),(e[0]===o||t.contains(e[0],o))&&t(o).trigger("focus"),s=e.parent(),"static"===e.css("position")?(s.css({position:"relative"}),e.css({position:"relative"})):(t.extend(i,{position:e.css("position"),zIndex:e.css("z-index")}),t.each(["top","left","bottom","right"],function(t,s){i[s]=e.css(s),isNaN(parseInt(i[s],10))&&(i[s]="auto")}),e.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),e.css(n),s.css(i).show()},removeWrapper:function(e){var i=document.activeElement;return e.parent().is(".ui-effects-wrapper")&&(e.parent().replaceWith(e),(e[0]===i||t.contains(e[0],i))&&t(i).trigger("focus")),e}}),t.extend(t.effects,{version:"1.12.1",define:function(e,i,s){return s||(s=i,i="effect"),t.effects.effect[e]=s,t.effects.effect[e].mode=i,s},scaledDimensions:function(t,e,i){if(0===e)return{height:0,width:0,outerHeight:0,outerWidth:0};var s="horizontal"!==i?(e||100)/100:1,n="vertical"!==i?(e||100)/100:1;return{height:t.height()*n,width:t.width()*s,outerHeight:t.outerHeight()*n,outerWidth:t.outerWidth()*s}},clipToBox:function(t){return{width:t.clip.right-t.clip.left,height:t.clip.bottom-t.clip.top,left:t.clip.left,top:t.clip.top}},unshift:function(t,e,i){var s=t.queue();e>1&&s.splice.apply(s,[1,0].concat(s.splice(e,i))),t.dequeue()},saveStyle:function(t){t.data(g,t[0].style.cssText)},restoreStyle:function(t){t[0].style.cssText=t.data(g)||"",t.removeData(g)},mode:function(t,e){var i=t.is(":hidden");return"toggle"===e&&(e=i?"show":"hide"),(i?"hide"===e:"show"===e)&&(e="none"),e},getBaseline:function(t,e){var i,s;switch(t[0]){case"top":i=0;break;case"middle":i=.5;break;case"bottom":i=1;break;default:i=t[0]/e.height}switch(t[1]){case"left":s=0;break;case"center":s=.5;break;case"right":s=1;break;default:s=t[1]/e.width}return{x:s,y:i}},createPlaceholder:function(e){var i,s=e.css("position"),n=e.position();return e.css({marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()),/^(static|relative)/.test(s)&&(s="absolute",i=t("<"+e[0].nodeName+">").insertAfter(e).css({display:/^(inline|ruby)/.test(e.css("display"))?"inline-block":"block",visibility:"hidden",marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight"),"float":e.css("float")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()).addClass("ui-effects-placeholder"),e.data(f+"placeholder",i)),e.css({position:s,left:n.left,top:n.top}),i},removePlaceholder:function(t){var e=f+"placeholder",i=t.data(e);i&&(i.remove(),t.removeData(e))},cleanUp:function(e){t.effects.restoreStyle(e),t.effects.removePlaceholder(e)},setTransition:function(e,i,s,n){return n=n||{},t.each(i,function(t,i){var o=e.cssUnit(i);o[0]>0&&(n[i]=o[0]*s+o[1])}),n}}),t.fn.extend({effect:function(){function i(e){function i(){r.removeData(m),t.effects.cleanUp(r),"hide"===s.mode&&r.hide(),a()}function a(){t.isFunction(h)&&h.call(r[0]),t.isFunction(e)&&e()}var r=t(this);s.mode=c.shift(),t.uiBackCompat===!1||o?"none"===s.mode?(r[l](),a()):n.call(r[0],s,i):(r.is(":hidden")?"hide"===l:"show"===l)?(r[l](),a()):n.call(r[0],s,a)}var s=e.apply(this,arguments),n=t.effects.effect[s.effect],o=n.mode,a=s.queue,r=a||"fx",h=s.complete,l=s.mode,c=[],u=function(e){var i=t(this),s=t.effects.mode(i,l)||o;i.data(m,!0),c.push(s),o&&("show"===s||s===o&&"hide"===s)&&i.show(),o&&"none"===s||t.effects.saveStyle(i),t.isFunction(e)&&e()};return t.fx.off||!n?l?this[l](s.duration,h):this.each(function(){h&&h.call(this)}):a===!1?this.each(u).each(i):this.queue(r,u).queue(r,i)},show:function(t){return function(s){if(i(s))return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="show",this.effect.call(this,n)}}(t.fn.show),hide:function(t){return function(s){if(i(s))return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="hide",this.effect.call(this,n)}}(t.fn.hide),toggle:function(t){return function(s){if(i(s)||"boolean"==typeof s)return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="toggle",this.effect.call(this,n)}}(t.fn.toggle),cssUnit:function(e){var i=this.css(e),s=[];return t.each(["em","px","%","pt"],function(t,e){i.indexOf(e)>0&&(s=[parseFloat(i),e])}),s},cssClip:function(t){return t?this.css("clip","rect("+t.top+"px "+t.right+"px "+t.bottom+"px "+t.left+"px)"):s(this.css("clip"),this)},transfer:function(e,i){var s=t(this),n=t(e.to),o="fixed"===n.css("position"),a=t("body"),r=o?a.scrollTop():0,h=o?a.scrollLeft():0,l=n.offset(),c={top:l.top-r,left:l.left-h,height:n.innerHeight(),width:n.innerWidth()},u=s.offset(),d=t("
    ").appendTo("body").addClass(e.className).css({top:u.top-r,left:u.left-h,height:s.innerHeight(),width:s.innerWidth(),position:o?"fixed":"absolute"}).animate(c,e.duration,e.easing,function(){d.remove(),t.isFunction(i)&&i()})}}),t.fx.step.clip=function(e){e.clipInit||(e.start=t(e.elem).cssClip(),"string"==typeof e.end&&(e.end=s(e.end,e.elem)),e.clipInit=!0),t(e.elem).cssClip({top:e.pos*(e.end.top-e.start.top)+e.start.top,right:e.pos*(e.end.right-e.start.right)+e.start.right,bottom:e.pos*(e.end.bottom-e.start.bottom)+e.start.bottom,left:e.pos*(e.end.left-e.start.left)+e.start.left})}}(),function(){var e={};t.each(["Quad","Cubic","Quart","Quint","Expo"],function(t,i){e[i]=function(e){return Math.pow(e,t+2)}}),t.extend(e,{Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t){return 0===t||1===t?t:-Math.pow(2,8*(t-1))*Math.sin((80*(t-1)-7.5)*Math.PI/15)},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var e,i=4;((e=Math.pow(2,--i))-1)/11>t;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*e-2)/22-t,2)}}),t.each(e,function(e,i){t.easing["easeIn"+e]=i,t.easing["easeOut"+e]=function(t){return 1-i(1-t)},t.easing["easeInOut"+e]=function(t){return.5>t?i(2*t)/2:1-i(-2*t+2)/2}})}();var v=t.effects;t.effects.define("blind","hide",function(e,i){var s={up:["bottom","top"],vertical:["bottom","top"],down:["top","bottom"],left:["right","left"],horizontal:["right","left"],right:["left","right"]},n=t(this),o=e.direction||"up",a=n.cssClip(),r={clip:t.extend({},a)},h=t.effects.createPlaceholder(n);r.clip[s[o][0]]=r.clip[s[o][1]],"show"===e.mode&&(n.cssClip(r.clip),h&&h.css(t.effects.clipToBox(r)),r.clip=a),h&&h.animate(t.effects.clipToBox(r),e.duration,e.easing),n.animate(r,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("bounce",function(e,i){var s,n,o,a=t(this),r=e.mode,h="hide"===r,l="show"===r,c=e.direction||"up",u=e.distance,d=e.times||5,p=2*d+(l||h?1:0),f=e.duration/p,g=e.easing,m="up"===c||"down"===c?"top":"left",_="up"===c||"left"===c,v=0,b=a.queue().length;for(t.effects.createPlaceholder(a),o=a.css(m),u||(u=a["top"===m?"outerHeight":"outerWidth"]()/3),l&&(n={opacity:1},n[m]=o,a.css("opacity",0).css(m,_?2*-u:2*u).animate(n,f,g)),h&&(u/=Math.pow(2,d-1)),n={},n[m]=o;d>v;v++)s={},s[m]=(_?"-=":"+=")+u,a.animate(s,f,g).animate(n,f,g),u=h?2*u:u/2;h&&(s={opacity:0},s[m]=(_?"-=":"+=")+u,a.animate(s,f,g)),a.queue(i),t.effects.unshift(a,b,p+1)}),t.effects.define("clip","hide",function(e,i){var s,n={},o=t(this),a=e.direction||"vertical",r="both"===a,h=r||"horizontal"===a,l=r||"vertical"===a;s=o.cssClip(),n.clip={top:l?(s.bottom-s.top)/2:s.top,right:h?(s.right-s.left)/2:s.right,bottom:l?(s.bottom-s.top)/2:s.bottom,left:h?(s.right-s.left)/2:s.left},t.effects.createPlaceholder(o),"show"===e.mode&&(o.cssClip(n.clip),n.clip=s),o.animate(n,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("drop","hide",function(e,i){var s,n=t(this),o=e.mode,a="show"===o,r=e.direction||"left",h="up"===r||"down"===r?"top":"left",l="up"===r||"left"===r?"-=":"+=",c="+="===l?"-=":"+=",u={opacity:0};t.effects.createPlaceholder(n),s=e.distance||n["top"===h?"outerHeight":"outerWidth"](!0)/2,u[h]=l+s,a&&(n.css(u),u[h]=c+s,u.opacity=1),n.animate(u,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("explode","hide",function(e,i){function s(){b.push(this),b.length===u*d&&n()}function n(){p.css({visibility:"visible"}),t(b).remove(),i()}var o,a,r,h,l,c,u=e.pieces?Math.round(Math.sqrt(e.pieces)):3,d=u,p=t(this),f=e.mode,g="show"===f,m=p.show().css("visibility","hidden").offset(),_=Math.ceil(p.outerWidth()/d),v=Math.ceil(p.outerHeight()/u),b=[];for(o=0;u>o;o++)for(h=m.top+o*v,c=o-(u-1)/2,a=0;d>a;a++)r=m.left+a*_,l=a-(d-1)/2,p.clone().appendTo("body").wrap("
    ").css({position:"absolute",visibility:"visible",left:-a*_,top:-o*v}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:_,height:v,left:r+(g?l*_:0),top:h+(g?c*v:0),opacity:g?0:1}).animate({left:r+(g?0:l*_),top:h+(g?0:c*v),opacity:g?1:0},e.duration||500,e.easing,s)}),t.effects.define("fade","toggle",function(e,i){var s="show"===e.mode;t(this).css("opacity",s?0:1).animate({opacity:s?1:0},{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("fold","hide",function(e,i){var s=t(this),n=e.mode,o="show"===n,a="hide"===n,r=e.size||15,h=/([0-9]+)%/.exec(r),l=!!e.horizFirst,c=l?["right","bottom"]:["bottom","right"],u=e.duration/2,d=t.effects.createPlaceholder(s),p=s.cssClip(),f={clip:t.extend({},p)},g={clip:t.extend({},p)},m=[p[c[0]],p[c[1]]],_=s.queue().length;h&&(r=parseInt(h[1],10)/100*m[a?0:1]),f.clip[c[0]]=r,g.clip[c[0]]=r,g.clip[c[1]]=0,o&&(s.cssClip(g.clip),d&&d.css(t.effects.clipToBox(g)),g.clip=p),s.queue(function(i){d&&d.animate(t.effects.clipToBox(f),u,e.easing).animate(t.effects.clipToBox(g),u,e.easing),i()}).animate(f,u,e.easing).animate(g,u,e.easing).queue(i),t.effects.unshift(s,_,4)}),t.effects.define("highlight","show",function(e,i){var s=t(this),n={backgroundColor:s.css("backgroundColor")};"hide"===e.mode&&(n.opacity=0),t.effects.saveStyle(s),s.css({backgroundImage:"none",backgroundColor:e.color||"#ffff99"}).animate(n,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("size",function(e,i){var s,n,o,a=t(this),r=["fontSize"],h=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],l=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],c=e.mode,u="effect"!==c,d=e.scale||"both",p=e.origin||["middle","center"],f=a.css("position"),g=a.position(),m=t.effects.scaledDimensions(a),_=e.from||m,v=e.to||t.effects.scaledDimensions(a,0);t.effects.createPlaceholder(a),"show"===c&&(o=_,_=v,v=o),n={from:{y:_.height/m.height,x:_.width/m.width},to:{y:v.height/m.height,x:v.width/m.width}},("box"===d||"both"===d)&&(n.from.y!==n.to.y&&(_=t.effects.setTransition(a,h,n.from.y,_),v=t.effects.setTransition(a,h,n.to.y,v)),n.from.x!==n.to.x&&(_=t.effects.setTransition(a,l,n.from.x,_),v=t.effects.setTransition(a,l,n.to.x,v))),("content"===d||"both"===d)&&n.from.y!==n.to.y&&(_=t.effects.setTransition(a,r,n.from.y,_),v=t.effects.setTransition(a,r,n.to.y,v)),p&&(s=t.effects.getBaseline(p,m),_.top=(m.outerHeight-_.outerHeight)*s.y+g.top,_.left=(m.outerWidth-_.outerWidth)*s.x+g.left,v.top=(m.outerHeight-v.outerHeight)*s.y+g.top,v.left=(m.outerWidth-v.outerWidth)*s.x+g.left),a.css(_),("content"===d||"both"===d)&&(h=h.concat(["marginTop","marginBottom"]).concat(r),l=l.concat(["marginLeft","marginRight"]),a.find("*[width]").each(function(){var i=t(this),s=t.effects.scaledDimensions(i),o={height:s.height*n.from.y,width:s.width*n.from.x,outerHeight:s.outerHeight*n.from.y,outerWidth:s.outerWidth*n.from.x},a={height:s.height*n.to.y,width:s.width*n.to.x,outerHeight:s.height*n.to.y,outerWidth:s.width*n.to.x};n.from.y!==n.to.y&&(o=t.effects.setTransition(i,h,n.from.y,o),a=t.effects.setTransition(i,h,n.to.y,a)),n.from.x!==n.to.x&&(o=t.effects.setTransition(i,l,n.from.x,o),a=t.effects.setTransition(i,l,n.to.x,a)),u&&t.effects.saveStyle(i),i.css(o),i.animate(a,e.duration,e.easing,function(){u&&t.effects.restoreStyle(i)})})),a.animate(v,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){var e=a.offset();0===v.opacity&&a.css("opacity",_.opacity),u||(a.css("position","static"===f?"relative":f).offset(e),t.effects.saveStyle(a)),i()}})}),t.effects.define("scale",function(e,i){var s=t(this),n=e.mode,o=parseInt(e.percent,10)||(0===parseInt(e.percent,10)?0:"effect"!==n?0:100),a=t.extend(!0,{from:t.effects.scaledDimensions(s),to:t.effects.scaledDimensions(s,o,e.direction||"both"),origin:e.origin||["middle","center"]},e);e.fade&&(a.from.opacity=1,a.to.opacity=0),t.effects.effect.size.call(this,a,i)}),t.effects.define("puff","hide",function(e,i){var s=t.extend(!0,{},e,{fade:!0,percent:parseInt(e.percent,10)||150});t.effects.effect.scale.call(this,s,i)}),t.effects.define("pulsate","show",function(e,i){var s=t(this),n=e.mode,o="show"===n,a="hide"===n,r=o||a,h=2*(e.times||5)+(r?1:0),l=e.duration/h,c=0,u=1,d=s.queue().length;for((o||!s.is(":visible"))&&(s.css("opacity",0).show(),c=1);h>u;u++)s.animate({opacity:c},l,e.easing),c=1-c;s.animate({opacity:c},l,e.easing),s.queue(i),t.effects.unshift(s,d,h+1)}),t.effects.define("shake",function(e,i){var s=1,n=t(this),o=e.direction||"left",a=e.distance||20,r=e.times||3,h=2*r+1,l=Math.round(e.duration/h),c="up"===o||"down"===o?"top":"left",u="up"===o||"left"===o,d={},p={},f={},g=n.queue().length;for(t.effects.createPlaceholder(n),d[c]=(u?"-=":"+=")+a,p[c]=(u?"+=":"-=")+2*a,f[c]=(u?"-=":"+=")+2*a,n.animate(d,l,e.easing);r>s;s++)n.animate(p,l,e.easing).animate(f,l,e.easing);n.animate(p,l,e.easing).animate(d,l/2,e.easing).queue(i),t.effects.unshift(n,g,h+1)}),t.effects.define("slide","show",function(e,i){var s,n,o=t(this),a={up:["bottom","top"],down:["top","bottom"],left:["right","left"],right:["left","right"]},r=e.mode,h=e.direction||"left",l="up"===h||"down"===h?"top":"left",c="up"===h||"left"===h,u=e.distance||o["top"===l?"outerHeight":"outerWidth"](!0),d={};t.effects.createPlaceholder(o),s=o.cssClip(),n=o.position()[l],d[l]=(c?-1:1)*u+n,d.clip=o.cssClip(),d.clip[a[h][1]]=d.clip[a[h][0]],"show"===r&&(o.cssClip(d.clip),o.css(l,d[l]),d.clip=s,d[l]=n),o.animate(d,{queue:!1,duration:e.duration,easing:e.easing,complete:i})});var v;t.uiBackCompat!==!1&&(v=t.effects.define("transfer",function(e,i){t(this).transfer(e,i)}))}); \ No newline at end of file diff --git a/js/jquery.min.js b/js/jquery.min.js index ab28a247..4d9b3a25 100644 --- a/js/jquery.min.js +++ b/js/jquery.min.js @@ -1,4 +1,2 @@ -/*! jQuery v1.11.1 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ -!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.1",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b=a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+-new Date,v=a.document,w=0,x=0,y=gb(),z=gb(),A=gb(),B=function(a,b){return a===b&&(l=!0),0},C="undefined",D=1<<31,E={}.hasOwnProperty,F=[],G=F.pop,H=F.push,I=F.push,J=F.slice,K=F.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},L="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",N="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=N.replace("w","w#"),P="\\["+M+"*("+N+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+O+"))|)"+M+"*\\]",Q=":("+N+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+P+")*)|.*)\\)|)",R=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),S=new RegExp("^"+M+"*,"+M+"*"),T=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),V=new RegExp(Q),W=new RegExp("^"+O+"$"),X={ID:new RegExp("^#("+N+")"),CLASS:new RegExp("^\\.("+N+")"),TAG:new RegExp("^("+N.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+Q),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{I.apply(F=J.call(v.childNodes),v.childNodes),F[v.childNodes.length].nodeType}catch(eb){I={apply:F.length?function(a,b){H.apply(a,J.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],!a||"string"!=typeof a)return d;if(1!==(k=b.nodeType)&&9!==k)return[];if(p&&!e){if(f=_.exec(a))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return I.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return I.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=9===k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+qb(o[l]);w=ab.test(a)&&ob(b.parentNode)||b,x=o.join(",")}if(x)try{return I.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function gb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function hb(a){return a[u]=!0,a}function ib(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function jb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function kb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||D)-(~a.sourceIndex||D);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function lb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function nb(a){return hb(function(b){return b=+b,hb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function ob(a){return a&&typeof a.getElementsByTagName!==C&&a}c=fb.support={},f=fb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fb.setDocument=function(a){var b,e=a?a.ownerDocument||a:v,g=e.defaultView;return e!==n&&9===e.nodeType&&e.documentElement?(n=e,o=e.documentElement,p=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){m()},!1):g.attachEvent&&g.attachEvent("onunload",function(){m()})),c.attributes=ib(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ib(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(e.getElementsByClassName)&&ib(function(a){return a.innerHTML="
    ",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=ib(function(a){return o.appendChild(a).id=u,!e.getElementsByName||!e.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==C&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c=typeof a.getAttributeNode!==C&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==C?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==C&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(e.querySelectorAll))&&(ib(function(a){a.innerHTML="",a.querySelectorAll("[msallowclip^='']").length&&q.push("[*^$]="+M+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+M+"*(?:value|"+L+")"),a.querySelectorAll(":checked").length||q.push(":checked")}),ib(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+M+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ib(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",Q)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===v&&t(v,a)?-1:b===e||b.ownerDocument===v&&t(v,b)?1:k?K.call(k,a)-K.call(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],i=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:k?K.call(k,a)-K.call(k,b):0;if(f===g)return kb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?kb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},e):n},fb.matches=function(a,b){return fb(a,null,null,b)},fb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fb(b,n,null,[a]).length>0},fb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&E.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fb.selectors={cacheLength:50,createPseudo:hb,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+M+")"+a+"("+M+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==C&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?hb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=K.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:hb(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?hb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:hb(function(a){return function(b){return fb(a,b).length>0}}),contains:hb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:hb(function(a){return W.test(a||"")||fb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:nb(function(){return[0]}),last:nb(function(a,b){return[b-1]}),eq:nb(function(a,b,c){return[0>c?c+b:c]}),even:nb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:nb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:nb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:nb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)),hb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||tb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ub(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ub(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?K.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ub(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):I.apply(g,r)})}function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=rb(function(a){return a===b},h,!0),l=rb(function(a){return K.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>i;i++)if(c=d.relative[a[i].type])m=[rb(sb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return vb(i>1&&sb(m),i>1&&qb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&wb(a.slice(i,e)),f>e&&wb(a=a.slice(e)),f>e&&qb(a))}m.push(c)}return sb(m)}function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=G.call(i));s=ub(s)}I.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&fb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?hb(f):f}return h=fb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xb(e,d)),f.selector=a}return f},i=fb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&ob(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qb(j),!a)return I.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&ob(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ib(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ib(function(a){return a.innerHTML="
    ","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h; -if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML="
    a",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function ab(){return!0}function bb(){return!1}function cb(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),hb=/^\s+/,ib=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,jb=/<([\w:]+)/,kb=/\s*$/g,rb={option:[1,""],legend:[1,"
    ","
    "],area:[1,"",""],param:[1,"",""],thead:[1,"","
    "],tr:[2,"","
    "],col:[2,"","
    "],td:[3,"","
    "],_default:k.htmlSerialize?[0,"",""]:[1,"X
    ","
    "]},sb=db(y),tb=sb.appendChild(y.createElement("div"));rb.optgroup=rb.option,rb.tbody=rb.tfoot=rb.colgroup=rb.caption=rb.thead,rb.th=rb.td;function ub(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ub(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function vb(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wb(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xb(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function yb(a){var b=pb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function zb(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Ab(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Bb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xb(b).text=a.text,yb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!gb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(tb.innerHTML=a.outerHTML,tb.removeChild(f=tb.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ub(f),h=ub(a),g=0;null!=(e=h[g]);++g)d[g]&&Bb(e,d[g]);if(b)if(c)for(h=h||ub(a),d=d||ub(f),g=0;null!=(e=h[g]);g++)Ab(e,d[g]);else Ab(a,f);return d=ub(f,"script"),d.length>0&&zb(d,!i&&ub(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=db(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(lb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(jb.exec(f)||["",""])[1].toLowerCase(),l=rb[i]||rb._default,h.innerHTML=l[1]+f.replace(ib,"<$1>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&hb.test(f)&&p.push(b.createTextNode(hb.exec(f)[0])),!k.tbody){f="table"!==i||kb.test(f)?""!==l[1]||kb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ub(p,"input"),vb),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ub(o.appendChild(f),"script"),g&&zb(h),c)){e=0;while(f=h[e++])ob.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ub(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&zb(ub(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ub(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fb,""):void 0;if(!("string"!=typeof a||mb.test(a)||!k.htmlSerialize&&gb.test(a)||!k.leadingWhitespace&&hb.test(a)||rb[(jb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ib,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ub(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ub(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&nb.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ub(i,"script"),xb),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ub(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,yb),j=0;f>j;j++)d=g[j],ob.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qb,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Cb,Db={};function Eb(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fb(a){var b=y,c=Db[a];return c||(c=Eb(a,b),"none"!==c&&c||(Cb=(Cb||m("