Merge remote-tracking branch 'origin/domain-duplicate' into 2.1.x

This commit is contained in:
Michael Kaufmann
2023-05-25 12:35:10 +02:00
8 changed files with 212 additions and 13 deletions

View File

@@ -636,6 +636,23 @@ if ($page == 'domains' || $page == 'overview') {
'alert_msg' => lng('domains.import_description') 'alert_msg' => lng('domains.import_description')
]); ]);
} }
} elseif ($action == 'duplicate') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
try {
Domains::getLocal($userinfo, $_POST)->duplicate();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
Response::redirectTo($filename, [
'page' => $page,
'searchfield' => 'd.domain_ace',
'searchtext' => $_POST['domain'] ?? ""
]);
} else {
Response::redirectTo($filename, [
'page' => 'overview'
]);
}
} }
} elseif ($page == 'domainssleditor') { } elseif ($page == 'domainssleditor') {
require_once __DIR__ . '/ssl_editor.php'; require_once __DIR__ . '/ssl_editor.php';

View File

@@ -110,7 +110,7 @@ class Domains extends ApiCommand implements ResourceEntity
* *
* @param number $domain_id * @param number $domain_id
* @param bool $ssl_only * @param bool $ssl_only
* optional, return only ssl enabled ip's, default false * optional, return only ssl enabled ips, default false
* @return array * @return array
*/ */
private function getIpsForDomain($domain_id = 0, $ssl_only = false) private function getIpsForDomain($domain_id = 0, $ssl_only = false)
@@ -207,7 +207,7 @@ class Domains extends ApiCommand implements ResourceEntity
* @param string $ssl_specialsettings * @param string $ssl_specialsettings
* optional, custom webserver vhost-content which is added to the generated ssl-vhost, default empty * optional, custom webserver vhost-content which is added to the generated ssl-vhost, default empty
* @param bool $include_specialsettings * @param bool $include_specialsettings
* optional, whether or not to include non-ssl specialsettings in the generated ssl-vhost, default false * optional, whether to include non-ssl specialsettings in the generated ssl-vhost, default false
* @param bool $notryfiles * @param bool $notryfiles
* optional, [nginx only] do not generate the default try-files directive, default 0 (false) * optional, [nginx only] do not generate the default try-files directive, default 0 (false)
* @param bool $writeaccesslog * @param bool $writeaccesslog
@@ -216,7 +216,7 @@ class Domains extends ApiCommand implements ResourceEntity
* optional, Enable writing an error-log file for this domain, default 1 (true) * optional, Enable writing an error-log file for this domain, default 1 (true)
* @param string $documentroot * @param string $documentroot
* optional, specify homedir of domain by specifying a directory (relative to customer-docroot), be * optional, specify homedir of domain by specifying a directory (relative to customer-docroot), be
* aware, if path starts with / it it considered a full path, not relative to customer-docroot. Also * aware, if path starts with / it is considered a full path, not relative to customer-docroot. Also
* specifying a URL is possible here (redirect), default empty (autogenerated) * specifying a URL is possible here (redirect), default empty (autogenerated)
* @param bool $phpenabled * @param bool $phpenabled
* optional, whether php is enabled for this domain, default 0 (false) * optional, whether php is enabled for this domain, default 0 (false)
@@ -241,7 +241,7 @@ class Domains extends ApiCommand implements ResourceEntity
* optional, do NOT set the systems default ssl ip addresses if none are given via $ssl_ipandport * optional, do NOT set the systems default ssl ip addresses if none are given via $ssl_ipandport
* parameter * parameter
* @param bool $sslenabled * @param bool $sslenabled
* optional, whether or not SSL is enabled for this domain, regardless of the assigned ssl-ips, default * optional, whether SSL is enabled for this domain, regardless of the assigned ssl-ips, default
* 1 (true) * 1 (true)
* @param bool $http2 * @param bool $http2
* optional, whether to enable http/2 for this domain (requires to be enabled in the settings), default * optional, whether to enable http/2 for this domain (requires to be enabled in the settings), default
@@ -249,9 +249,9 @@ class Domains extends ApiCommand implements ResourceEntity
* @param int $hsts_maxage * @param int $hsts_maxage
* optional max-age value for HSTS header * optional max-age value for HSTS header
* @param bool $hsts_sub * @param bool $hsts_sub
* optional whether or not to add subdomains to the HSTS header * optional whether to add subdomains to the HSTS header
* @param bool $hsts_preload * @param bool $hsts_preload
* optional whether or not to preload HSTS header value * optional whether to preload HSTS header value
* @param bool $ocsp_stapling * @param bool $ocsp_stapling
* optional whether to enable ocsp-stapling for this domain. default 0 (false), requires SSL * optional whether to enable ocsp-stapling for this domain. default 0 (false), requires SSL
* @param bool $honorcipherorder * @param bool $honorcipherorder
@@ -260,7 +260,7 @@ class Domains extends ApiCommand implements ResourceEntity
* optional whether to enable or disable TLS sessiontickets (RFC 5077) for this domain. default 1 * optional whether to enable or disable TLS sessiontickets (RFC 5077) for this domain. default 1
* (true), requires SSL * (true), requires SSL
* @param bool $override_tls * @param bool $override_tls
* optional whether or not to override system-tls settings like protocol, ssl-ciphers and if applicable * optional whether to override system-tls settings like protocol, ssl-ciphers and if applicable
* tls-1.3 ciphers, requires change_serversettings flag for the admin, default false * tls-1.3 ciphers, requires change_serversettings flag for the admin, default false
* @param array $ssl_protocols * @param array $ssl_protocols
* optional list of allowed/used ssl/tls protocols, see system.ssl_protocols setting, only used/required * optional list of allowed/used ssl/tls protocols, see system.ssl_protocols setting, only used/required
@@ -1076,7 +1076,7 @@ class Domains extends ApiCommand implements ResourceEntity
* @param string $ssl_specialsettings * @param string $ssl_specialsettings
* optional, custom webserver vhost-content which is added to the generated ssl-vhost, default empty * optional, custom webserver vhost-content which is added to the generated ssl-vhost, default empty
* @param bool $include_specialsettings * @param bool $include_specialsettings
* optional, whether or not to include non-ssl specialsettings in the generated ssl-vhost, default false * optional, whether to include non-ssl specialsettings in the generated ssl-vhost, default false
* @param bool $specialsettingsforsubdomains * @param bool $specialsettingsforsubdomains
* optional, whether to apply specialsettings to all subdomains of this domain, default is read from * optional, whether to apply specialsettings to all subdomains of this domain, default is read from
* setting system.apply_specialsettings_default * setting system.apply_specialsettings_default
@@ -1088,7 +1088,7 @@ class Domains extends ApiCommand implements ResourceEntity
* optional, Enable writing an error-log file for this domain, default 1 (true) * optional, Enable writing an error-log file for this domain, default 1 (true)
* @param string $documentroot * @param string $documentroot
* optional, specify homedir of domain by specifying a directory (relative to customer-docroot), be * optional, specify homedir of domain by specifying a directory (relative to customer-docroot), be
* aware, if path starts with / it it considered a full path, not relative to customer-docroot. Also * aware, if path starts with / it is considered a full path, not relative to customer-docroot. Also
* specifying a URL is possible here (redirect), default empty (autogenerated) * specifying a URL is possible here (redirect), default empty (autogenerated)
* @param bool $phpenabled * @param bool $phpenabled
* optional, whether php is enabled for this domain, default 0 (false) * optional, whether php is enabled for this domain, default 0 (false)
@@ -1117,7 +1117,7 @@ class Domains extends ApiCommand implements ResourceEntity
* optional, if set to true and no $ssl_ipandport value is given, the ip's get removed, otherwise, the * optional, if set to true and no $ssl_ipandport value is given, the ip's get removed, otherwise, the
* currently set value is used, default false * currently set value is used, default false
* @param bool $sslenabled * @param bool $sslenabled
* optional, whether or not SSL is enabled for this domain, regardless of the assigned ssl-ips, default * optional, whether SSL is enabled for this domain, regardless of the assigned ssl-ips, default
* 1 (true) * 1 (true)
* @param bool $http2 * @param bool $http2
* optional, whether to enable http/2 for this domain (requires to be enabled in the settings), default * optional, whether to enable http/2 for this domain (requires to be enabled in the settings), default
@@ -1125,9 +1125,9 @@ class Domains extends ApiCommand implements ResourceEntity
* @param int $hsts_maxage * @param int $hsts_maxage
* optional max-age value for HSTS header * optional max-age value for HSTS header
* @param bool $hsts_sub * @param bool $hsts_sub
* optional whether or not to add subdomains to the HSTS header * optional whether to add subdomains to the HSTS header
* @param bool $hsts_preload * @param bool $hsts_preload
* optional whether or not to preload HSTS header value * optional whether to preload HSTS header value
* @param bool $ocsp_stapling * @param bool $ocsp_stapling
* optional whether to enable ocsp-stapling for this domain. default 0 (false), requires SSL * optional whether to enable ocsp-stapling for this domain. default 0 (false), requires SSL
* @param bool $honorcipherorder * @param bool $honorcipherorder
@@ -2187,4 +2187,114 @@ class Domains extends ApiCommand implements ResourceEntity
} }
throw new Exception("Not allowed to execute given command.", 403); throw new Exception("Not allowed to execute given command.", 403);
} }
/**
* duplicate domain entry by either id or domainname. All parameters from Domains.add() can be used
* to overwrite source entity values if necessary.
*
* @param int $id
* optional, the domain-id
* @param string $domainname
* optional, the domainname
* @param string $domain
* required, name of the new domain to be added
*
* @access admin
* @return string json-encoded array
* @throws Exception
*/
public function duplicate()
{
if ($this->isAdmin()) {
// parameters
$id = $this->getParam('id', true, 0);
$dn_optional = $id > 0;
$domainname = $this->getParam('domainname', $dn_optional, '');
$p_domain = $this->getParam('domain');
// get requested domain
$result = $this->apiCall('Domains.get', [
'id' => $id,
'domainname' => $domainname,
]);
// clear some defaults
unset($result['domain_ace']);
unset($result['adminid']);
unset($result['documentroot']);
unset($result['registration_date']);
unset($result['termination_date']);
unset($result['zonefile']);
// clear auto-generated values
unset($result['bindserial']);
unset($result['dkim_privkey']);
unset($result['dkim_pubkey']);
// clear api-call generated fields
unset($result['domain_hascert']);
// set correct ip/port information
$domain_ips = $result['ipsandports'];
unset($result['ipsandports']);
$result['ipandport'] = [];
$result['ssl_ipandport'] = [];
foreach ($domain_ips as $dip) {
if ($dip['ssl'] == 1) {
$result['ssl_ipandport'][] = $dip['id'];
} else {
$result['ipandport'][] = $dip['id'];
}
}
// check whether we are changing the customer/owner
if ($this->getParam('customerid', true, 0) == 0 && $this->getParam('loginname', true, '') == '') {
$customerid = $result['customerid'];
} else {
$customer = $this->getCustomerData();
$customerid = $customer['customerid'];
}
// check for alias-domain and whether it belongs to the target user
if (!empty($result['aliasdomain']) && $customerid == $result['customerid']) {
// duplicate alias entry
$result['alias'] = $result['aliasdomain'];
}
unset($result['aliasdomain']);
// validate possible fpm configs and whether the customer is allowed to use them
if ($customerid != $result['customerid']) {
$allowed_phpconfigs = json_decode($customer['allowed_phpconfigs'] ?? '[]', true);
if (empty($allowed_phpconfigs)) {
// system defaults
unset($result['phpsettingid']);
} elseif (!in_array($result['phpsettingid'], $allowed_phpconfigs)) {
// use the first customer allowed config
$result['phpsettingid'] = array_shift($allowed_phpconfigs);
}
}
// translate serveralias values
$result['selectserveralias'] = 2;
if ((int)$result['wwwserveralias'] == 1) {
$result['selectserveralias'] = 1;
} elseif ((int)$result['iswildcarddomain'] == 1) {
$result['selectserveralias'] = 0;
}
unset($result['wwwserveralias']);
unset($result['iswildcarddomain']);
$additional_params = $this->getParamList();
// unset unneeded params from this call
unset($additional_params['id']);
unset($additional_params['domainname']);
unset($additional_params['domain']);
// set new values and merge with optional add() parameters
$new_domain = array_merge($result, $additional_params);
$new_domain['domain'] = $p_domain;
$result_new = $this->apiCall('Domains.add', $new_domain);
return $this->response($result_new);
}
throw new Exception("Not allowed to execute given command.", 403);
}
} }

View File

@@ -25,10 +25,13 @@
namespace Froxlor\UI\Callbacks; namespace Froxlor\UI\Callbacks;
use Froxlor\CurrentUser;
use Froxlor\Database\Database;
use Froxlor\Froxlor; use Froxlor\Froxlor;
use Froxlor\PhpHelper; use Froxlor\PhpHelper;
use Froxlor\UI\Panel\UI; use Froxlor\UI\Panel\UI;
use Froxlor\User; use Froxlor\User;
use PDO;
class Text class Text
{ {
@@ -105,4 +108,44 @@ class Text
'body' => $body 'body' => $body
]; ];
} }
public static function domainDuplicateModal(array $attributes): array
{
$linker = UI::getLinker();
$result = $attributes['fields'];
$customers = [
0 => lng('panel.please_choose')
];
$result_customers_stmt = Database::prepare("
SELECT `customerid`, `loginname`, `name`, `firstname`, `company`
FROM `" . TABLE_PANEL_CUSTOMERS . "` " . (CurrentUser::getField('customers_see_all') ? '' : " WHERE `adminid` = :adminid ") . "
ORDER BY COALESCE(NULLIF(`name`,''), `company`) ASC
");
$params = [];
if (CurrentUser::getField('customers_see_all') == '0') {
$params['adminid'] = CurrentUser::getField('adminid');
}
Database::pexecute($result_customers_stmt, $params);
while ($row_customer = $result_customers_stmt->fetch(PDO::FETCH_ASSOC)) {
$customers[$row_customer['customerid']] = User::getCorrectFullUserDetails($row_customer) . ' (' . $row_customer['loginname'] . ')';
}
$domdup_data = include Froxlor::getInstallDir() . '/lib/formfields/admin/domains/formfield.domains_duplicate.php';
$body = UI::twig()->render(UI::validateThemeTemplate('/user/inline-form.html.twig'), [
'formaction' => $linker->getLink(['section' => 'domains', 'page' => 'domains', 'action' => 'duplicate']),
'formdata' => $domdup_data['domain_duplicate'],
'editid' => $attributes['fields']['id'],
'nosubmit' => 0
]);
return [
'entry' => $attributes['fields']['id'],
'id' => 'ddModal' . $attributes['fields']['id'],
'title' => lng('admin.domain_duplicate_named', [$attributes['fields']['domain']]),
'action' => 'duplicate',
'body' => $body
];
}
} }

View File

@@ -161,6 +161,11 @@ return [
'id' => ':id' 'id' => ':id'
], ],
], ],
'duplicate' => [
'icon' => 'fa-solid fa-clone',
'title' => lng('admin.domain_duplicate'),
'modal' => [Text::class, 'domainDuplicateModal'],
],
'logfiles' => [ 'logfiles' => [
'icon' => 'fa-solid fa-file', 'icon' => 'fa-solid fa-file',
'title' => lng('panel.viewlogs'), 'title' => lng('panel.viewlogs'),

View File

@@ -489,6 +489,8 @@ return [
'adminguide' => 'Admin Guide', 'adminguide' => 'Admin Guide',
'userguide' => 'User Guide', 'userguide' => 'User Guide',
'apiguide' => 'API Guide', 'apiguide' => 'API Guide',
'domain_duplicate' => 'Domain duplizieren',
'domain_duplicate_named' => '%s duplizieren',
], ],
'apikeys' => [ 'apikeys' => [
'no_api_keys' => 'Keine API Keys gefunden', 'no_api_keys' => 'Keine API Keys gefunden',

View File

@@ -500,6 +500,8 @@ return [
'adminguide' => 'Admin guide', 'adminguide' => 'Admin guide',
'userguide' => 'User guide', 'userguide' => 'User guide',
'apiguide' => 'API guide', 'apiguide' => 'API guide',
'domain_duplicate' => 'Duplicate domain',
'domain_duplicate_named' => 'Duplicate %s',
], ],
'apcuinfo' => [ 'apcuinfo' => [
'clearcache' => 'Clear APCu cache', 'clearcache' => 'Clear APCu cache',

View File

@@ -1,2 +1,2 @@
{% import "Froxlor/form/form.html.twig" as form %} {% import "Froxlor/form/form.html.twig" as form %}
{{ form.form(formdata, formaction|default('#'), formdata.title, editid|default(''), true, idprefix|default('')) }} {{ form.form(formdata, formaction|default('#'), formdata.title, editid|default(''), nosubmit|default(true), idprefix|default('')) }}

View File

@@ -385,6 +385,26 @@ class DomainsTest extends TestCase
* *
* @depends testAdminDomainsMove * @depends testAdminDomainsMove
*/ */
public function testAdminDomainsDuplicate()
{
global $admin_userdata;
$data = [
'domainname' => 'test.local',
'domain' => 'test.duplicate.local',
'description' => 'duplicated domain'
];
$json_result = Domains::getLocal($admin_userdata, $data)->duplicate();
$result = json_decode($json_result, true)['data'];
$this->assertEquals('/var/customers/webs/test3/test.duplicate.local/', $result['documentroot']);
$this->assertEquals(1, $result['email_only']);
$this->assertEquals('test.duplicate.local', $result['domain']);
$this->assertEquals('duplicated domain', $result['description']);
}
/**
*
* @depends testAdminDomainsDuplicate
*/
public function testAdminDomainsDelete() public function testAdminDomainsDelete()
{ {
global $admin_userdata; global $admin_userdata;