add domain-bulk-import, fixes #1452

Signed-off-by: Michael Kaufmann (d00p) <d00p@froxlor.org>
This commit is contained in:
Michael Kaufmann (d00p)
2014-12-15 14:49:34 +01:00
parent 7dd6f9b97c
commit cd5e8801e4
14 changed files with 710 additions and 2 deletions

View File

@@ -1881,5 +1881,60 @@ if ($page == 'domains'
eval("echo \"" . getTemplate("domains/domains_edit") . "\";");
}
}
} elseif($action == 'import') {
if (isset($_POST['send'])
&& $_POST['send'] == 'send'
) {
$customerid = intval($_POST['customerid']);
$separator = validate($_POST['separator'], 'separator');
$offset = intval($_POST['offset']);
$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);
$result_str = $result['imported'] . ' / ' . $result['all'];
standard_success('domain_import_successfully', $result_str, array('filename' => $filename, 'action' => '', 'page' => 'domains'));
} 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 `name` 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']);
}
$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") . "\";");
}
}
}

View File

@@ -0,0 +1,445 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* For the full copyright and license information, please view the COPYING
* file that was distributed with this source code. You can also view the
* COPYING file online at http://files.froxlor.org/misc/COPYING.txt
*
* @copyright (c) the authors
* @author Michael Kaufmann <mkaufmann@nutime.de>
* @author Froxlor team <team@froxlor.org> (2010-)
* @license GPLv2 http://files.froxlor.org/misc/COPYING.txt
* @package Cron
*
* @since 0.9.33
*
*/
/**
* Class DomainBulkAction to mass-import domains for a given customer
*
* @author Michael Kaufmann (d00p) <d00p@froxlor.org>
*
*/
class DomainBulkAction {
/**
* complete path including filename of file to be imported
*
* @var string
*/
private $_impFile = null;
/**
* customer id of the user the domains are being added to
*
* @var int
*/
private $_custId = null;
/**
* array of customer data read from the database
*
* @var array
*/
private $_custData = null;
/**
* array of already known domains from the database
*
* @var array
*/
private $_knownDomains = null;
/**
* array of known ip/port combinations
*
* @var array
*/
private $_knownIpPort = null;
/**
* array of fields to import to panel_domains
*
* @var array
*/
private $_required_fields = array (
'domain',
'documentroot',
'isbinddomain',
'isemaildomain',
'email_only',
'iswildcarddomain',
'subcanemaildomain',
'caneditdomain',
'wwwserveralias',
'specialsettings',
'ssl_redirect',
'registration_date',
'ips',
'adminid',
'customerid',
'add_date'
);
/**
* prepared statements for each domain
*
* @var PDOStatement
*/
private $_ins_stmt = null;
private $_ipp_ins_stmt = null;
/**
* class constructor, optionally sets file and customer-id
*
* @param string $import_file
* @param int $customer_id
*
* @return object DomainBulkAction instance
*/
public function __construct($import_file = null, $customer_id = 0) {
$this->_impFile = makeCorrectFile($import_file);
$this->_custId = $customer_id;
}
/**
* import the parsed import file data with an optional separator other then semicolon
* and offset (maybe for header-line in csv or similar)
*
* @param string $separator
* @param int $offset
*
* @return array 'all' => amount of records processed, 'imported' => number of imported records
*/
public function doImport($separator = ";", $offset = 0) {
// get the admins userinfo to check for domains_used, etc.
global $userinfo;
if ($userinfo['domains'] == "-1") {
$dom_unlimited = true;
} else {
$domains_used = (int)$userinfo['domains_used'];
$domains_avail = (int)$userinfo['domains'];
$dom_unlimited = false;
}
if (empty($separator) || strlen($separator) != 1) {
throw new Exception("Invalid separator specified: '" . $separator . "'");
}
if (! is_numeric($offset) || $offset < 0) {
throw new Exception("Invalid offset specified");
}
$this->_readCustomerData();
if (is_null($this->_custData)) {
throw new Exception("Failed to read customer data");
}
$this->_readIpPortData();
$this->_readDomainData();
try {
$domain_array = $this->_parseImportFile($separator);
} catch (Exception $e) {
throw $e;
}
if (count($domain_array) <= 0) {
throw new Exception("No domains were read from the file.");
}
// preapre insert statement as it is used a few times
$this->_ins_stmt = Database::prepare("
INSERT INTO `" . TABLE_PANEL_DOMAINS . "` SET
`domain` = :domain,
`adminid` = :adminid,
`customerid` = :customerid,
`documentroot` = :documentroot,
`isbinddomain` = :isbinddomain,
`isemaildomain` = :isemaildomain,
`email_only` = :email_only,
`iswildcarddomain` = :iswildcarddomain,
`subcanemaildomain` = :subcanemaildomain,
`caneditdomain` = :caneditdomain,
`wwwserveralias` = :wwwserveralias,
`specialsettings` = :specialsettings,
`ssl_redirect` = :ssl_redirect,
`registration_date` = :registration_date,
`add_date` = :add_date
");
// prepare insert statement for ip/port <> domain
$this->_ipp_ins_stmt = Database::prepare("
INSERT INTO `" . TABLE_DOMAINTOIP . "` SET
`id_domain` = :domid,
`id_ipandports` = :ipid
");
$global_counter = 0;
$import_counter = 0;
$note = '';
foreach ($domain_array as $idx => $dom) {
if ($idx >= $offset) {
if ($dom_unlimited || (! $dom_unlimited && $domains_used < $domains_avail)) {
$ins_id = $this->_addSingleDomainToDatabase($dom);
if ($ins_id !== false) {
$import_counter ++;
$domains_used ++;
}
} else {
$note = 'You have reached your maximum allocation of domains (' . $domains_avail . ').';
break;
}
}
$global_counter ++;
}
return array (
'all' => $global_counter,
'imported' => $import_counter,
'notice' => $note
);
}
/**
* setter for import-file
*
* @param string $import_file
*
* @return void
*/
public function setImportFile($import_file = null) {
$this->_impFile = makeCorrectFile($import_file);
}
/**
* setter for customer-id
*
* @param int $customer_id
*
* @return void
*/
public function setCustomer($customer_id = 0) {
$this->_custId = $customer_id;
}
/**
* adds a single domain to the database using the given array
*
* @param array $domain_data
* @param object $ins_stmt prepared PDO-statement to insert into panel_domains
* @param object $ipp_ins_stmt prepared PDO-statement to insert into panel_domaintoip
*
* @return int last-inserted id or false on error
*/
private function _addSingleDomainToDatabase($domain_data = array()) {
// format domain
$idna_convert = new idna_convert_wrapper();
$domain_data['domain'] = $idna_convert->encode(preg_replace(array (
'/\:(\d)+$/',
'/^https?\:\/\//'
), '', $domain_data['domain']));
// check if it is a valid domain
if (! validateDomain($domain_data['domain'])) {
return false;
}
// no system-hostname can be added
if ($domain_data['domain'] == Settings::Get('system.hostname')) {
return false;
}
// no existing domains
if (in_array($domain_data['domain'], $this->_knownDomains)) {
return false;
}
// add to known domains
$this->_knownDomains[] = $domain_data['domain'];
// docroot (URL allowed, will lead to redirect)
if (! preg_match('/^https?\:\/\//', $domain_data['documentroot'])) {
$domain_data['documentroot'] = makeCorrectDir($this->_custData['documentroot'] . "/" . $domain_data['documentroot']);
}
// is bind domain?
if (! isset($domain_data['isbinddomain'])) {
$domain_data['isbinddomain'] = (Settings::Get('system.bind_enable') == '1') ? 1 : 0;
} elseif ($domain_data['isbinddomain'] != 1) {
$domain_data['isbinddomain'] = 0;
}
/*
* automatically set values (not from the file)
*/
// add date
$domain_data['add_date'] = time();
// set adminid
$domain_data['adminid'] = $this->_custData['adminid'];
// set customerid
$domain_data['customerid'] = $this->_custId;
// check for required fields
foreach ($this->_required_fields as $rfld) {
if (! isset($domain_data[$rfld])) {
return false;
}
}
// clean all fields that do not belong to the required fields
$domain_data_tmp = $domain_data;
foreach ($domain_data_tmp as $fld => $val) {
if (! in_array($fld, $this->_required_fields)) {
unset($domain_data[$fld]);
}
}
// save iplist
$iplist = $domain_data['ips'];
// dont need that for the domain-insert-statement
unset($domain_data['ips']);
// finally ADD the domain to panel_domains
Database::pexecute($this->_ins_stmt, $domain_data);
// get the newly inserted domain-id
$domain_id = Database::lastInsertId();
// insert domain <-> ip/port reference
if (empty($iplist)) {
$iplist = Settings::Get('system.ipaddress');
}
// split ip-list and remove duplicates
$iplist_arr = array_unique(explode(",", $iplist));
foreach ($iplist_arr as $ip) {
// if we know the ip, at all variants (different ports, ssl and non-ssl) of it!
if (isset($this->_knownIpPort[$ip])) {
foreach ($this->_knownIpPort[$ip] as $ipdata) {
// add domain->ip reference
Database::pexecute($this->_ipp_ins_stmt, array (
'domid' => $domain_id,
'ipid' => $ipdata['id']
));
}
}
}
return $domain_id;
}
/**
* reads in the csv import file and returns an array with
* all the domains to be imported
*
* @param string $separator
*
* @return array
*/
private function _parseImportFile($separator = ";") {
if (empty($this->_impFile)) {
throw new Exception("No file was given for import");
}
if (! file_exists($this->_impFile)) {
throw new Exception("The file '" . $this->_impFile . "' could not be found");
}
if (! is_readable($this->_impFile)) {
throw new Exception("Unable to read file '" . $this->_impFile . "'");
}
$file_data = array ();
$fh = @fopen($this->_impFile, "r");
if ($fh) {
while (($line = fgets($fh)) !== false) {
$tmp_arr = explode($separator, $line);
$data_arr = array ();
foreach ($tmp_arr as $idx => $data) {
// dont include more fields that the 13 we use
if ($idx > 12)
break;
$data_arr[$this->_required_fields[$idx]] = $data;
}
$file_data[] = array_map("trim", $data_arr);
}
} else {
throw new Exception("Unable to open file '" . $this->_impFile . "'");
}
fclose($fh);
return $file_data;
}
/**
* reads customer data from panel_customer by $_custId
*
* @return bool
*/
private function _readCustomerData() {
$cust_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `customerid` = :cid");
$this->_custData = Database::pexecute_first($cust_stmt, array (
'cid' => $this->_custId
));
if (is_array($this->_custData) && isset($this->_custData['customerid']) && $this->_custData['customerid'] == $this->_custId) {
return true;
}
$this->_custData = null;
return false;
}
/**
* reads domain data from panel_domain
*
* @return void
*/
private function _readDomainData() {
$knowndom_stmt = Database::prepare("SELECT `domain` FROM `" . TABLE_PANEL_DOMAINS . "` ORDER BY `domain` ASC");
Database::pexecute($knowndom_stmt);
$this->_knownDomains = array ();
while ($dom = $knowndom_stmt->fetch()) {
$this->_knownDomains[] = $dom['domain'];
}
}
/**
* reads ip/port data from panel_ipsandports
*
* @return void
*/
private function _readIpPortData() {
$knownip_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_IPSANDPORTS . "`");
Database::pexecute($knownip_stmt);
$this->_knownIpPort = array ();
while ($ipp = $knownip_stmt->fetch()) {
$this->_knownIpPort[$ipp['ip']][] = $ipp;
}
}
}

View File

@@ -123,6 +123,8 @@ class htmlform
return self::_textArea($fieldname, $data); break;
case 'checkbox':
return self::_checkbox($fieldname, $data); break;
case 'file':
return self::_file($fieldname, $data); break;
}
}
@@ -288,4 +290,30 @@ class htmlform
return $output;
}
private static function _file($fieldname = '', $data = array())
{
$return = '';
$extras = '';
if(isset($data['maxlength'])) {
$extras .= ' maxlength="'.$data['maxlength'].'"';
}
// add support to save reloaded forms
if (isset($data['value'])) {
$value = $data['value'];
} elseif (isset($_SESSION['requestData'][$fieldname])) {
$value = $_SESSION['requestData'][$fieldname];
} else {
$value = '';
}
if(isset($data['display']) && $data['display'] != '')
{
$ulfield = '<strong>'.$data['display'].'</strong>';
}
eval("\$return = \"" . getTemplate("misc/form/input_file", "1") . "\";");
return $return;
}
}

View File

@@ -0,0 +1,56 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* For the full copyright and license information, please view the COPYING
* file that was distributed with this source code. You can also view the
* COPYING file online at http://files.froxlor.org/misc/COPYING.txt
*
* @copyright (c) the authors
* @author Froxlor team <team@froxlor.org> (2010-)
* @license GPLv2 http://files.froxlor.org/misc/COPYING.txt
* @package Formfields
*
*/
return array(
'domain_import' => array(
'title' => $lng['domains']['domain_import'],
'image' => 'icons/domain_add.png',
'sections' => array(
'section_a' => array(
'title' => $lng['domains']['domain_import'],
'image' => 'icons/domain_add.png',
'fields' => array(
'customerid' => array(
'label' => $lng['admin']['customer'],
'type' => 'select',
'select_var' => $customers,
'mandatory' => true,
),
'separator' => array(
'label' => $lng['domains']['import_separator'],
'type' => 'text',
'mandatory' => true,
'size' => 5,
'value' => ';'
),
'offset' => array(
'label' => $lng['domains']['import_offset'],
'type' => 'text',
'mandatory' => true,
'size' => 10,
'value' => '0'
),
'file' => array(
'label' => $lng['domains']['import_file'],
'type' => 'file',
'mandatory' => true
)
)
)
)
)
);

View File

@@ -0,0 +1,24 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* For the full copyright and license information, please view the COPYING
* file that was distributed with this source code. You can also view the
* COPYING file online at http://files.froxlor.org/misc/COPYING.txt
*
* @copyright (c) the authors
* @author Froxlor team <team@froxlor.org> (2010-)
* @license GPLv2 http://files.froxlor.org/misc/COPYING.txt
* @package Functions
*
*/
function getFormFieldOutputFile($fieldname, $fielddata)
{
$label = $fielddata['label'];
$value = htmlentities($fielddata['value']);
eval("\$returnvalue = \"" . getTemplate("formfields/text", true) . "\";");
return $returnvalue;
}

View File

@@ -0,0 +1,21 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* For the full copyright and license information, please view the COPYING
* file that was distributed with this source code. You can also view the
* COPYING file online at http://files.froxlor.org/misc/COPYING.txt
*
* @copyright (c) the authors
* @author Froxlor team <team@froxlor.org> (2010-)
* @license GPLv2 http://files.froxlor.org/misc/COPYING.txt
* @package Functions
*
*/
function validateFormFieldFile($fieldname, $fielddata, $newfieldvalue)
{
return true;
}

View File

@@ -1840,3 +1840,12 @@ $lng['error']['no_phpinfo'] = 'Sorry, unable to read phpinfo()';
$lng['admin']['movetoadmin'] = 'Move customer';
$lng['admin']['movecustomertoadmin'] = 'Move customer to the selected admin/reseller<br /><small>Leave this empty for no change.<br />If the desired admin does not show up in the list, his customer-limit has been reached.</small>';
$lng['error']['moveofcustomerfailed'] = 'Moving the customer to the selected admin/reseller failed. Keep in mind that all other changes to the customer were applied successfully at this stage.<br><br>Error-message: %s';
$lng['domains']['domain_import'] = 'Import Domains';
$lng['domains']['import_separator'] = 'Separator';
$lng['domains']['import_offset'] = 'Offset';
$lng['domains']['import_file'] = 'CSV-File';
$lng['success']['domain_import_successfully'] = 'Successfully imported %s domains.';
$lng['error']['domain_import_error'] = 'Following error occurred while importing domains: %s';
$lng['admin']['note'] = 'Note';
$lng['domains']['import_description'] = 'Detailed information about the structure of the import-file and how to import successfully, please visit <a href="http://redmine.froxlor.org/projects/froxlor/wiki/DomainBulkActionDoc">http://redmine.froxlor.org/projects/froxlor/wiki/DomainBulkActionDoc</a>';

View File

@@ -1560,3 +1560,16 @@ $lng['serversettings']['panel_password_special_char']['description'] = 'Mindeste
$lng['phpfpm']['use_mod_proxy']['title'] = 'Verwende mod_proxy / mod_proxy_fcgi';
$lng['phpfpm']['use_mod_proxy']['description'] = 'Diese Option kann aktiviert werden, um php-fpm via mod_proxy_fcgi einzubinden. Dies setzt mindestens apache-2.4.9 voraus';
$lng['error']['no_phpinfo'] = 'Entschuldigung, es ist nicht möglich die phpinfo() auszulesen.';
$lng['admin']['movetoadmin'] = 'Kunde verschieben';
$lng['admin']['movecustomertoadmin'] = 'Verschiebe den Kunden zum angegebenen Admin/Reseller<br /><small>Leerlassen für keine Änderung.<br />Wird der gewünschte Admin/Reseller hier nicht aufgelistet, hat er sein Kunden-Kontigent erreicht.</small>';
$lng['error']['moveofcustomerfailed'] = 'Das Verschieben des Kunden ist fehlgeschlagen. Alle übrigen Änderungen wurden durchgeführt und gespeichert.<br><br>Fehlermeldung: %s';
$lng['domains']['domain_import'] = 'Domains importieren';
$lng['domains']['import_separator'] = 'Trennzeichen';
$lng['domains']['import_offset'] = 'Versatz (offset)';
$lng['domains']['import_file'] = 'CSV-Datei';
$lng['success']['domain_import_successfully'] = 'Erfolgreich %s Domains importiert.';
$lng['error']['domain_import_error'] = 'Der folgende Fehler trat beim Importieren der Domains auf: %s';
$lng['admin']['note'] = 'Hinweis';
$lng['domains']['import_description'] = 'Detaillierte Informationen über den Aufbau der Importdatei und einen erfolgreichen Import gibt es hier: <a href="http://redmine.froxlor.org/projects/froxlor/wiki/DomainBulkActionDoc">http://redmine.froxlor.org/projects/froxlor/wiki/DomainBulkActionDoc</a>';

View File

@@ -21,6 +21,9 @@
<div class="overviewadd">
<img src="templates/{$theme}/assets/img/icons/add.png" alt="" />&nbsp;
<a href="{$linker->getLink(array('section' => 'domains', 'page' => $page, 'action' => 'add'))}">{$lng['admin']['domain_add']}</a>
&nbsp;
<img src="templates/{$theme}/assets/img/icons/archive.png" alt="" />&nbsp;
<a href="{$linker->getLink(array('section' => 'domains', 'page' => $page, 'action' => 'import'))}">{$lng['domains']['domain_import']}</a>
</div>
</if>

View File

@@ -0,0 +1,38 @@
$header
<article>
<header>
<h2>
<img src="templates/{$theme}/assets/img/icons/domain_add_big.png" alt="{$title}" />&nbsp;
{$title}
</h2>
</header>
<div class="messagewrapperfull">
<div class="warningcontainer bradius">
<div class="warningtitle">{$lng['admin']['note']}</div>
<div class="warning">{$lng['domains']['import_description']}</div>
</div>
</div>
<section>
<form action="{$linker->getLink(array('section' => 'domains'))}" method="post" enctype="multipart/form-data">
<input type="hidden" name="s" value="$s" />
<input type="hidden" name="page" value="$page" />
<input type="hidden" name="action" value="$action" />
<input type="hidden" name="send" value="send" />
<table class="full">
{$domain_import_form}
</table>
</form>
</section>
<br />
<section>
<p>
<span class="red">*</span>: {$lng['admin']['valuemandatory']}
</p>
</section>
</article>
$footer

View File

@@ -157,7 +157,7 @@ input {
/*
* BUTTONS
*/
input[type="button"],input[type="submit"],input[type="reset"] {
input[type="button"],input[type="submit"],input[type="reset"],input[type="file"] {
padding:4px 10px;
margin:0 30px 0 0;
height:30px;
@@ -185,6 +185,12 @@ input[class="nobutton"],input[type="reset"],input[class="nobutton"]:hover,input[
padding-left:25px;
}
input[type="file"] {
color:blue!important;
background:#ccc url('../img/icons/button_ok.png') no-repeat 4px 8px!important;
padding-left:25px;
}
select {
color:#000;
background-color:#dae7ee;

View File

@@ -590,7 +590,7 @@ input[type="password"] {
/*
* BUTTONS
*/
input[type="button"],input[type="submit"],input[type="reset"] {
input[type="button"],input[type="submit"],input[type="reset"],input[type="file"] {
margin:0 5px;
padding:5px 14px;
outline:0;
@@ -637,6 +637,11 @@ input[class="nobutton"],input[type="reset"] {
background-color:#d84a38;
}
input[type="file"] {
background-color:#FFFFFF;
padding-left:0px;
}
input[class="nobutton"]:hover,input[type="reset"]:hover {
color:#fff;
background-color:#c53727;

4
templates/Sparkle/formfields/file.tpl vendored Normal file
View File

@@ -0,0 +1,4 @@
<tr>
<td>{$label}</td>
<td><input type="file" class="file" name="{$fieldname}" /></td>
</tr>

View File

@@ -0,0 +1 @@
<input type="file" name="{$fieldname}" id="{$fieldname}" class="file" />