diff --git a/include/class-Domain.php b/include/class-Domain.php index 12578ad..5238d68 100644 --- a/include/class-Domain.php +++ b/include/class-Domain.php @@ -1,317 +1,320 @@ . // load Plan trait -class_exists( 'Plan' ); +class_exists( Plan::class, true ); +class_exists( MTA::class, true ); /** * Methods for a Domain class */ trait DomainTrait { use PlanTrait; /** * Count of the Domain's Mailboxes * * This is a kind of cache * * @var int */ private $domainMailboxCount = null; /** * Count of the Domain's FTP accounts * * This is a kind of cache * * @var int */ private $domainFTPAccountCount = null; /** * Get domain ID * * @return int */ public function getDomainID() { return $this->get( 'domain_ID' ); } /* * Get domain name * * @return string */ public function getDomainName() { return $this->get( 'domain_name' ); } /** * Get a printable Domain firm * * It may be a link if you are allowed to edit this Domain. * * @return string */ public function getDomainFirm() { return HTML::a( $this->getDomainPermalink(), esc_html( $this->getDomainName() ) ); } /** * Get the sanitized relative directory name of this domain name * * Actually this should be valid for both the MTA and for the webserver. * * @return string */ public function getDomainDirname() { $dir = $this->getDomainName(); // it was validated during creation time, but validate also now // to prevent malicious actions over hacked databases require_safe_dirname( $dir ); return $dir; } /** * Get the domain edit URL * * @param boolean $absolute True for an absolute URL * @return string */ public function getDomainPermalink( $absolute = false ) { return Domain::permalink( $this->getDomainName(), $absolute ); } /** * Get the permalink to the edit plan page * * @param boolean $absolute True for an absolute URL * @return string */ public function getDomainPlanPermalink( $absolute = false ) { return Plan::domainPermalink( $this->getDomainName(), $absolute ); } /** * Check if you can create a new Mailbox for this Domain * * The Domain must have Plan informations. * * @return boolean */ public function canCreateMailboxInDomain() { return $this->getPlanMailboxes() > $this->getDomainMailboxCount() || has_permission( 'edit-email-all' ); } /** * Check if you can create a new FTP account for this Domain * * The Domain must have Plan informations. * * The Domain must have Plan informations. */ public function canCreateFTPAccountForDomain() { return $this->getPlanFTPUsers() > $this->getDomainFTPAccountCount() || has_permission( 'edit-ftp-all' ); } /** * Factory mailbox from this domain * * @return MailboxFullAPI */ public function factoryMailbox() { return ( new MailboxFullAPI() )->whereDomain( $this ); } /** * Factory e-mail forward from this domain * * @return MailforwardFullAPI */ public function factoryMailforwardfrom() { return ( new MailforwardfromQuery() )->whereDomain( $this ); } /** * Set a count of Domain's Mailboxes * * This method should not be used directly. * * @param $count int * @return self */ public function setDomainMailboxCount( $count ) { $this->domainMailboxCount = $count; } /** * Get the number of Mailboxes of this Domain * * This method has a layer of cache. * * @return int */ public function getDomainMailboxCount() { // check if we already know the count if( !isset( $this->domainMailboxCount ) ) { // count the number of mailboxes associated to this Domain $count = $this->factoryMailbox() ->select( 'COUNT(*) count' ) ->queryValue( 'count' ); // save in cache $this->domainMailboxCount = (int) $count; } return $this->domainMailboxCount; } /** * Get the number of FTP accounts of this Domain * * This method has a layer of cache. * * @return int */ public function getDomainFTPAccountCount() { // check if we already know the count if( !isset( $this->domainFTPAccountCount ) ) { // count the number of mailboxes associated to this Domain $count = $this->factoryFTP() ->select( 'COUNT(*) count' ) ->queryValue( 'count' ); // save in cache $this->domainFTPAccountCount = (int) $count; } return $this->domainFTPAccountCount; } /** * Get the expected MTA directory containing Domain's mailboxes * * This pathname should be considered true for the MTA host. * * TODO: actually all the mailbox are on the same host. * Then, we should support multiple hosts. * * @return string */ public function getDomainMailboxesPath() { // mailboxes are stored under a $BASE/domain/username filesystem structure return MAILBOX_BASE_PATH . __ . $this->getDomainDirname(); } /** * Get the expected and sanitized base domain directory containing its directories * * This pathname should be considered true both for the webserver serving * that domain and for the related FTP server. * * TODO: actually all the domains are on the same host. * Then, we should support multiple hosts. * * @return string */ public function getDomainBasePath() { // mailboxes are stored under a $BASE/domain/username filesystem structure return VIRTUALHOST_BASE_PATH . __ . $this->getDomainDirname(); } /** * Factory FTP users from this domain * * @return FTPAPI */ public function factoryFTP() { return ( new FTPAPI() )->whereDomain( $this ); } /** * Normalize a Domain object after being retrieved from database */ protected function normalizeDomain() { $this->integers( 'domain_ID' ); $this->booleans( 'domain_active' ); $this->dates( 'domain_born', 'domain_expiration' ); $this->normalizePlan(); } } /** * Describe the 'domain' table */ class Domain extends Queried { use DomainTrait; + use MTATrait; /** * Table name */ const T = 'domain'; const UID = 'domain_name'; /** * Constructor */ public function __construct() { $this->normalizeDomain(); + $this->normalizeMTA(); } /** * Get the domain permalink * * @param string $domain_name Domain name * @param boolean $absolute True for an absolute URL */ public static function permalink( $domain_name = null, $absolute = false ) { $url = 'domain.php'; if( $domain_name ) { $url .= _ . $domain_name; } return site_page( $url, $absolute ); } /** * Force to get a Domain ID, whatever is passed * * @param mixed $domain Domain object or Domain ID * @return int */ public static function getID( $domain ) { return is_object( $domain ) ? $domain->getDomainID() : (int)$domain; } } diff --git a/include/class-DomainAPI.php b/include/class-DomainAPI.php index 4b64ea6..615b0f2 100644 --- a/include/class-DomainAPI.php +++ b/include/class-DomainAPI.php @@ -1,169 +1,174 @@ . // load dependent traits class_exists( 'PlanAPI' ); /** * Methods related to a Domain class */ trait DomainAPITrait { use PlanAPITrait; /** * Where the domains are editable by me * * @return self */ public function whereDomainIsEditable() { if( !has_permission( 'edit-domain-all' ) ) { $this->whereDomainUser(); } return $this; } /** * Where the domains are visible by me * * @return self */ public function whereDomainIsVisible() { return $this->whereDomainIsEditable(); } /** * Where the Domain is Active (or not) * * @param boolean $active If you want the active, or the inactive * @return self */ public function whereDomainIsActive( $active = true ) { return $this->whereInt( 'domain_active', $active ); } /** * Limit to a certain user (or yourself) * * @param $user_ID int * @return self */ public function whereDomainUser( $user_ID = false ) { if( $user_ID === false ) { $user_ID = get_user( 'user_ID' ); } $this->joinDomainUser(); return $this->whereInt( 'domain_user.user_ID', $user_ID ); } /** * Limit to a certian domain name * * @param $domain_name string * @return self */ public function whereDomainName( $domain_name ) { return $this->whereStr( 'domain_name', $domain_name ); } /** * Constructor from a domain ID * * @param $domain_ID int * @return self */ public function whereDomainID( $domain_ID ) { return $this->whereInt( static::DOMAIN_ID, $domain_ID ); } /** * Constructor from a Domain object * * @param $domain object * @return self */ public function whereDomain( $domain ) { return $this->whereDomainID( $domain->getDomainID() ); } /** * Order by the Domain name * * @param string $direction DESC|ASC * @return self */ public function orderByDomainName( $direction = null ) { return $this->orderBy( 'domain_name', $direction ); } /** * Join domain and users (once) * * @return self */ public function joinDomainUser() { if( empty( $this->joinedDomainUser ) ) { $this->from( 'domain_user' ); $this->equals( 'domain_user.domain_ID', 'domain.domain_ID' ); $this->joinedDomainUser = true; } return $this; } /** * Join whatever table with the domain table * * @return self */ public function joinDomain() { return $this->joinOn( 'INNER', 'domain', static::DOMAIN_ID, 'domain.domain_ID' ); } } /** * Domain API */ class DomainAPI extends Query { use DomainAPITrait; /** * Univoque Domain ID column name */ const DOMAIN_ID = 'domain.domain_ID'; + /** + * External MTA ID key + */ + protected $MTA_ID = 'mta.mta_ID'; + /** * Univoque Plan ID column name */ protected $PLAN_ID = 'domain.plan_ID'; /** * Constructor * * @param object $db Database (or NULL for the current one) */ public function __construct( $db = null ) { // set database and class name parent::__construct( $db, 'Domain' ); // set database table $this->from( Domain::T ); } } diff --git a/include/class-Host.php b/include/class-Host.php new file mode 100644 index 0000000..015f3ec --- /dev/null +++ b/include/class-Host.php @@ -0,0 +1,110 @@ +. + +/** + * Methods for a Host class + */ +trait HostTrait { + + use HostTrait; + + /** + * Get the Host's ID + * + * @return int + */ + public function getHostID() { + return $this->get( 'host_ID' ); + } + + /** + * Get the Host's IPv4 in a numeric expression + * + * @return int + */ + public function getHostIPv4Long() { + return $this->get( 'host_ipv4' ); + } + + /** + * Get the Host's IPv4 in the expected dotted format + * + * @return string + */ + public function getHostIPv4() { + return long2ip( $this->getHostIPv4Long() ); + } + + /** + * Get the host's hostname + * + * @return string + */ + public function getHostName() { + return $this->get( 'host_hostname' ); + } + + /** + * Get the Host's description + * + * @return string + */ + public function getHostDescription() { + return $this->get( 'host_description' ); + } + + /** + * Normalize an Host object + */ + protected function normalizeHost() { + $this->integers( + 'host_ID', + 'host_ipv4' + ); + } + +} + +/** + * Describe the 'host' database table + */ +class Host extends Queried { + + use HostTrait; + + /** + * Table name + */ + const T = 'host'; + + /** + * Constructor + */ + public function __construct() { + $this->normalizeHost(); + } + + /** + * Force to get a Host ID, whatever is passed + * + * @param mixed $host Host object or Host ID + * @return int + */ + public static function getID( $host ) { + return is_object( $host ) ? $host->getHostID() : (int)$host; + } +} diff --git a/include/class-HostAPI.php b/include/class-HostAPI.php new file mode 100644 index 0000000..5f1d74c --- /dev/null +++ b/include/class-HostAPI.php @@ -0,0 +1,65 @@ +. + +// load dependent traits +class_exists( HostAPI::class, true ); + +/** + * Methods for an HostAPI class. + */ +trait HostAPITrait { + + /** + * Join whatever table with the Host table + * + * @param string $type Join type + * @return self + */ + public function joinHost( $type = 'INNER' ) { + return $this->joinOn( $type, Host::T, 'host.host_ID', $this->HOST_ID ); + } + +} + +/** + * Class to retrieve Host objects. + */ +class HostAPI extends Query { + + use HostAPITrait; + + /** + * External Host ID key + */ + protected $HOST_ID = 'host.host_ID'; + + /** + * Constructor + * + * @param $db DB Database connection + */ + public function __construct( $db = null ) { + + // set database and class name + parent::__construct( $db, Host::class ); + + // set database table + $this->from( Host::T ); + + } + +} diff --git a/include/class-MTA.php b/include/class-MTA.php new file mode 100644 index 0000000..c487a28 --- /dev/null +++ b/include/class-MTA.php @@ -0,0 +1,112 @@ +. + +// load the dependent traits +class_exists( Host::class, true ); + +/** + * Methods for a MTA class + */ +trait MTATrait { + + use HostTrait; + + /** + * Get the MTA's ID + * + * @return int + */ + public function getMTAID() { + return $this->get( 'mta_ID' ); + } + + /** + * Get the name of this MTA + * + * @return string + */ + public function getMTAName() { + return $this->getHostName(); + } + + /** + * Normalize an MTA object + */ + protected function normalizeMTA() { + $this->integers( 'mta_ID' ); + } + +} + +/** + * Describe the 'mta' database table + * + * This rappresents a single Mail Transfer Agent + * + * https://en.wikipedia.org/wiki/Message_transfer_agent + * + * Actually the KISS Libre Hosting Panel supports + * multiple MTA for a single host. That's why an MTA + * is a different entity than an Host. + * + * Anyway, in the current implementation it's just + * a pointer to the Host so, trust me, it's not + * a brainfuck but just a no-cost feature to describe + * whatever weird infrastructure. + */ +class MTA extends Queried { + + use MTATrait; + use HostTrait; + + /** + * Table name + */ + const T = 'mta'; + + /** + * Constructor + */ + public function __construct() { + $this->normalizeMTA(); + $this->normalizeHost(); + } + + /** + * Get the Domain-MTA permalink + * + * @param string $domain_name Domain name + * @param boolean $absolute True for an absolute URL + */ + public static function domainPermalink( $domain_name = null, $absolute = false ) { + $url = 'domain-mta.php'; + if( $domain_name ) { + $url .= _ . $domain_name; + } + return site_page( $url, $absolute ); + } + + /** + * Force to get a MTA ID, whatever is passed + * + * @param mixed $mta MTA object or MTA ID + * @return int + */ + public static function getID( $mta ) { + return is_object( $mta ) ? $mta->getMTAID() : (int)$mta; + } +} diff --git a/include/class-MTAAPI.php b/include/class-MTAAPI.php new file mode 100644 index 0000000..9693bf5 --- /dev/null +++ b/include/class-MTAAPI.php @@ -0,0 +1,71 @@ +. + +// load dependent traits +class_exists( HostAPI::class, true ); + +/** + * Methods for an MTAAPI class. + */ +trait MTAAPITrait { + + /** + * Limit to a specific MTA ID + * + * @param int $id + * @return self + */ + public function whereMTAID( $id ) { + return $this->whereInt( $this->MTA_ID, $id ); + } + +} + +/** + * Class to retrieve MTA objects. + */ +class MTAAPI extends Query { + + use MTAAPITrait; + use HostAPITrait; + + /** + * External MTA ID key + */ + protected $MTA_ID = 'mta.mta_ID'; + + /** + * External Host ID key + */ + protected $HOST_ID = 'mta.host_ID'; + + /** + * Constructor + * + * @param $db DB Database connection + */ + public function __construct( $db = null ) { + + // set database and class name + parent::__construct( $db, MTA::class ); + + // set database table + $this->from( MTA::T ); + + } + +} diff --git a/load-post.php b/load-post.php index 16429ab..c91623c 100644 --- a/load-post.php +++ b/load-post.php @@ -1,136 +1,137 @@ . /** * This is your versioned configuration file * * It does not contains secrets. * * This file is required after loading your * unversioned configuration file: * * load.php */ // Database vesion // // Do not touch if not sure. // // the maintainer increases this database version // each version is related to a patch here: // documentation/database/patches define( 'DATABASE_VERSION', 7 ); /** * VirtualHost(s) base path * * e.g. you may have /var/www/example.com/index.html * do NOT end with a slash */ define_default( 'VIRTUALHOST_BASE_PATH', '/var/www' ); /** * Mailbox base path * * Used by CLI scripts to calculate the current quotas. * * The mailboxes should have paths like: * MAILBOX_BASE_PATH/domain_name/user_name/ */ define_default( 'MAILBOX_BASE_PATH', '/home/vmail' ); // include path define_default( 'INCLUDE_PATH', ABSPATH . __ . 'include' ); // template path define_default( 'TEMPLATE_PATH', ABSPATH . __ . 'template' ); // override default user class define_default( 'SESSIONUSER_CLASS', 'User' ); // autoload classes from the /include directory spl_autoload_register( function( $name ) { // TODO: autoload classes and create DomainTrait and use in Mailbox $path = INCLUDE_PATH . __ . "class-$name.php"; if( is_file( $path ) ) { require $path; } } ); // load common functions require INCLUDE_PATH . __ . 'functions.php'; // jquery URL // provided by the libjs-jquery package as default define_default( 'JQUERY_URL', '/javascript/jquery/jquery.min.js' ); // Bootstrap CSS/JavaScript files without trailing slash // provided by the libjs-bootstrap package as default define_default( 'BOOTSTRAP_DIR_URL', '/javascript/bootstrap' ); // path to the Net SMTP class // provided by the php-net-smtp package as default define_default( 'NET_SMTP', '/usr/share/php/Net/SMTP.php' ); // default currency simbol define_default( 'DEFAULT_CURRENCY_SYMBOL', '€' ); // register JavaScript/CSS files register_js( 'jquery', JQUERY_URL ); register_js( 'bootstrap', BOOTSTRAP_DIR_URL . '/js/bootstrap.min.js' ); register_css( 'bootstrap', BOOTSTRAP_DIR_URL . '/css/bootstrap.min.css' ); register_css( 'custom-css', ROOT . '/content/style.css' ); // GNU Gettext i18n define( 'GETTEXT_DOMAIN', 'reyboz-hosting-panel' ); define( 'GETTEXT_DIRECTORY', 'l10n' ); define( 'GETTEXT_DEFAULT_ENCODE', CHARSET ); // UTF-8 // common strings define_default( 'SITE_NAME', "Libre Hosting Panel" ); define_default( 'CONTACT_EMAIL', 'support@' . DOMAIN ); define_default( 'REPO_URL', 'https://gitpull.it/source/kiss-libre-hosting-panel/' ); // limit session duration to 5 minutes (60s * 100m) define_default( 'SESSION_DURATION', 6000 ); // register web pages add_menu_entries( [ new MenuEntry( 'index', '', __( "Dashboard" ), null, 'backend' ), new MenuEntry( 'login', 'login.php', __( "Login" ) ), new MenuEntry( 'profile', 'profile.php', __( "Profile" ), null, 'read' ), new MenuEntry( 'logout', 'logout.php', __( "Logout" ), null, 'read' ), new MenuEntry( 'user-list', 'user-list.php', __( "Users" ), null, 'edit-user-all' ), new MenuEntry( 'activity', 'activity.php', __( "Last Activity" ), null, 'monitor' ), new MenuEntry( 'password-reset', 'password-reset.php', __( "Password reset" ) ), ] ); // permissions of a normal user register_permissions( 'user', [ 'read', 'backend', ] ); // permissions of an admin inherit_permissions( 'admin', 'user', [ 'edit-user-all', 'edit-email-all', 'edit-domain-all', 'edit-plan-all', 'edit-ftp-all', + 'edit-mta-all', 'monitor', ] ); diff --git a/template/domain.php b/template/domain-mta-link.php similarity index 53% copy from template/domain.php copy to template/domain-mta-link.php index 1949d66..11ba772 100644 --- a/template/domain.php +++ b/template/domain-mta-link.php @@ -1,51 +1,40 @@ . /* - * This is the template for the website domain dashboard page + * This is the template for the website Domain MTA link + * + * See: + * https://gitpull.it/T340 * * Called from: * domain.php * * Available variables: - * $domain object Domain - * $plan object Plan + * $domain object Domain */ +?> -// unuseful when load directly -defined( 'BOZ_PHP' ) or die; - -// pass the same arguments to the sub-templates -$args = [ - 'domain' => $domain, - 'plan' => $plan, -]; - -// spawn the mailboxes list -template( 'mailboxes', $args ); +

-// spawn the mail forwardings list -template( 'mailforwards', $args ); +getMTAID() ): ?> -// spawn the ftp list -template( 'ftp-users', $args ); +

-// show some links to the plan -template( 'domain-plan-section', $args ); + -// show Domain activity -template( 'domain-activity', $args ); +getDomainName() ), __( "MTA" ) ) ?> diff --git a/template/domain-mta.php b/template/domain-mta.php new file mode 100644 index 0000000..5005b7e --- /dev/null +++ b/template/domain-mta.php @@ -0,0 +1,76 @@ +. + +/* + * This is the template for the website Domain MTA form + * + * See: + * https://gitpull.it/T340 + * + * Called from: + * domain-mta.php + * + * Available variables: + * $domain object Domain + * $mta object MTA + * $mtas generator MTAs + */ +?> + +getMTAID() ): ?> + +

+ + + +
+ + + +

getDomainName() + ) ) ?>

+ +

+ +

+ +

+ +
diff --git a/template/domain.php b/template/domain.php index 1949d66..421f8c3 100644 --- a/template/domain.php +++ b/template/domain.php @@ -1,51 +1,57 @@ . /* * This is the template for the website domain dashboard page * * Called from: * domain.php * * Available variables: * $domain object Domain * $plan object Plan */ // unuseful when load directly defined( 'BOZ_PHP' ) or die; // pass the same arguments to the sub-templates $args = [ 'domain' => $domain, 'plan' => $plan, ]; // spawn the mailboxes list template( 'mailboxes', $args ); // spawn the mail forwardings list template( 'mailforwards', $args ); // spawn the ftp list template( 'ftp-users', $args ); // show some links to the plan template( 'domain-plan-section', $args ); +if( has_permission( 'edit-mta-all' ) ) { + + // show a link to the Domain-MTA page + template( 'domain-mta-link', $args ); +} + // show Domain activity template( 'domain-activity', $args ); diff --git a/www/domain-mta.php b/www/domain-mta.php new file mode 100644 index 0000000..024bcc1 --- /dev/null +++ b/www/domain-mta.php @@ -0,0 +1,125 @@ +. + +/* + * This is the page where you can assign an MTA + * to a Domain. + * + * See: + * https://gitpull.it/T340 + */ + +// load framework +require '../load.php'; + +// this page is not public +require_permission( 'edit-mta-all' ); + +// require the Domain name +list( $domain_name ) = url_parts( 1 ); + +// retrieve the Domain +$domain = ( new DomainAPI() ) + ->whereDomainName( $domain_name ) + ->whereDomainIsEditable() + ->joinPlan( 'LEFT' ) + ->queryRow(); + +// no Domain no party +if( !$domain ) { + PageNotFound::spawn(); +} + +// this page contains a form +require_csrf(); + +// fetch Mail Transfer Agents +$mtas = ( new MTAAPI() ) + ->joinHost() + ->queryGenerator(); + +// eventually save the MTA +if( is_action( 'save-domain-mta' ) ) { + + // receive the picked MTA + $mta_ID = $_POST[ 'mta_ID'] ?? null; + + if( $mta_ID === '-' ) { + $mta_ID = null; + } else { + $mta_ID = (int) $mta_ID; + } + + // no MTA no party + if( $mta_ID !== null && !$mta_ID ) { + BadRequest::spawn(); + } + + query( 'START TRANSACTION' ); + + if( $mta_ID !== null ) { + + // check if it exists + $mta = ( new MTAAPI() ) + ->whereMTAID( $mta_ID ) + ->forUpdate() + ->queryRow(); + + // no MTA no party + if( !$mta ) { + // rollback the change (no change) + query( 'ROLLBACK' ); + + BadRequest::spawn( "missing MTA" ); + } + + $mta_ID = $mta->getMTAID(); + } + + // update the Domain + ( new DomainAPI() ) + ->whereDomain( $domain ) + ->update( [ + // this can be numeric or NULL + 'mta_ID' => $mta_ID, + ] ); + + // really save in the database + query( 'COMMIT' ); + + // POST -> redirect -> GET + http_redirect( $_SERVER['REQUEST_URI'] ); +} + +// spawn header +Header::spawn( [ + 'uid' => false, + 'title' => __( "MTA" ), + 'breadcrumb' => [ + new MenuEntry( null, $domain->getDomainPermalink(), $domain->getDomainName() ), + ], +] ); + +// spawn the Domain-MTA template +template( 'domain-mta', [ + 'domain' => $domain, + 'plan' => $domain, + 'mta' => $domain, + 'mtas' => $mtas, +] ); + +Footer::spawn(); diff --git a/www/domain.php b/www/domain.php index 5b6774b..adbfb76 100644 --- a/www/domain.php +++ b/www/domain.php @@ -1,108 +1,108 @@ . /* * This is the domain edit page */ // load framework require '../load.php'; // this page is not public require_permission( 'backend' ); // wanted domain list( $domain_name ) = url_parts( 1, 0 ); $domain = null; if( $domain_name ) { // retrieve domain $domain = ( new DomainAPI() ) ->whereDomainName( $domain_name ) ->whereDomainIsEditable() ->joinPlan( 'LEFT' ) ->queryRow(); // 404? $domain or PageNotFound::spawn(); } else { // try to create require_permission( 'edit-domain-all' ); if( is_action( 'add-domain' ) && isset( $_POST[ 'domain_name' ] ) ) { // trim and normalize to max length $domain_name = luser_input( $_POST[ 'domain_name' ], 64 ); // existing domain $existing = ( new DomainAPI() ) ->whereDomainName( $domain_name ) ->queryRow(); // go to the existing one if( $existing ) { http_redirect( $existing->getDomainPermalink() ); } query( 'START TRANSACTION' ); // insert this new domain insert_row( Domain::T, [ new DBCol( 'domain_name', $domain_name, 's' ), new DBCol( 'domain_active', 1, 'd' ), new DBCol( 'domain_born', 'NOW()', '-' ), ] ); // this Domain ID $domain_ID = last_inserted_ID(); // add this event in the log APILog::insert( [ 'family' => 'domain', 'action' => 'create', 'domain' => $domain_ID, ] ); query( 'COMMIT' ); // go to the new domain http_redirect( Domain::permalink( $domain_name, true ) ); } } // spawn header Header::spawn( [ 'uid' => false, 'title-prefix' => __( "Domain" ), 'title' => $domain_name ? $domain_name : __( "Add" ), ] ); if( $domain ) { // spawn the domain template template( 'domain', [ 'domain' => $domain, 'plan' => $domain, ] ); } else { // form to create the domain template( 'domain-create' ); } // spawn the footer Footer::spawn();