diff --git a/documentation/database/patches/patch-0004-add-mailbox-quota.sql b/documentation/database/patches/patch-0004-add-mailbox-quota.sql
new file mode 100644
index 0000000..4c58529
--- /dev/null
+++ b/documentation/database/patches/patch-0004-add-mailbox-quota.sql
@@ -0,0 +1 @@
+ALTER TABLE `{$prefix}plan` ADD COLUMN `plan_mailboxquota` INT(10) unsigned COMMENT 'Max mailbox quota in bytes';
diff --git a/include/class-Plan.php b/include/class-Plan.php
index 7e451cf..58b15ea 100644
--- a/include/class-Plan.php
+++ b/include/class-Plan.php
@@ -1,173 +1,213 @@
.
// load Plan trait
class_exists( 'Plan' );
/**
* Methods for a Plan class
*/
trait PlanTrait {
/**
* Get plan ID
*
* @return int
*/
public function getPlanID() {
return $this->get( 'plan_ID' );
}
/*
* Get plan name
*
* @return string
*/
public function getPlanName() {
return $this->get( 'plan_name' );
}
/*
* Get the plan UID
*
* @return string
*/
public function getPlanUID() {
return $this->get( 'plan_uid' );
}
/**
* Get the number of FTP users of this Plan
*
* @return int|null
*/
public function getPlanFTPUsers() {
return $this->get( 'plan_ftpusers' );
}
/**
* Get the number of Databases of this Plan
*
* @return int|null
*/
public function getPlanDatabases() {
return $this->get( 'plan_databases' );
}
/**
* Get the number of Mailboxes of this Plan
*
* @return int|null
*/
public function getPlanMailboxes() {
return $this->get( 'plan_mailboxes' );
}
/**
* Get the number of Mailforwardings of this Plan
*
* @return int|null
*/
public function getPlanMailforwardings() {
return $this->get( 'plan_mailforwards' );
}
/**
* Get the Plan yearly price
*
* @return float
*/
public function getPlanYearlyPrice() {
return $this->get( 'plan_yearlyprice' );
}
+ /**
+ * Get the Plan mailbox quota in bytes
+ *
+ * @return int
+ */
+ public function getPlanMailboxQuota() {
+ return $this->get( 'plan_mailboxquota' );
+ }
+
/**
* Get the plan edit URl
*
* @param boolean $absolute True for an absolute URL
* @return string
*/
public function getPlanPermalink( $absolute = false ) {
return Plan::permalink( $this->get( 'plan_name' ), $absolute );
}
/**
* Normalize a Plan object after being retrieved from database
*/
protected function normalizePlan() {
$this->integers(
'plan_ID',
'plan_ftpusers',
'plan_databases',
'plan_mailboxes',
- 'plan_mailforwards'
+ 'plan_mailforwards',
+ 'plan_mailboxquota',
);
$this->floats(
'plan_yearlyprice'
);
}
}
/**
* Describe the 'plan' table
*/
class Plan extends Queried {
use PlanTrait;
/**
* Table name
*/
const T = 'plan';
/**
* Constructor
*/
public function __construct() {
$this->normalizePlan();
}
/**
* Get the plan permalink
*
* @param string $plan_name Plan name
* @param boolean $absolute True for an absolute URL
*/
public static function permalink( $plan_name = null, $absolute = false ) {
$url = 'plan.php';
if( $plan_name ) {
$url .= "/$plan_name";
}
return site_page( $url, $absolute );
}
/**
* Get the Domain's Plan permalink
*
* @param string $plan_name Plan name
* @param boolean $absolute True for an absolute URL
*/
public static function domainPermalink( $domain_name = null, $absolute = false ) {
$url = 'domain-plan.php';
if( $domain_name ) {
$url .= "/$domain_name";
}
return site_page( $url, $absolute );
}
+ /**
+ * Get a percentage from two values
+ *
+ * It's always between 0 and 100, or NULL if cannot be calculated.
+ *
+ * @param $current int Current amount
+ * @param $max int Maximum amount
+ * @return mixed Percentage or NULL
+ */
+ public static function percentage( $current, $max ) {
+
+ // no args no party
+ if( !$current || !$max ) {
+ return null;
+ }
+
+ // cannot be negative
+ if( $current < 0 ) {
+ return 0;
+ }
+
+ $percentage = $current * 100 / $max;
+
+ // cannot be too much
+ if( $percentage > 100 ) {
+ return 100;
+ }
+
+ return (int) $percentage;
+ }
}
diff --git a/load-post.php b/load-post.php
index 125b8e7..45c99bb 100644
--- a/load-post.php
+++ b/load-post.php
@@ -1,129 +1,129 @@
.
/**
* This is your versioned configuration file
*
* It does not contains secrets.
*
* This file is required after loading your
* unversioned configuration file:
*
* load.php
*/
// database version
//
// you can increase your database version if you added some patches in:
// documentation/database/patches
-define( 'DATABASE_VERSION', 3 );
+define( 'DATABASE_VERSION', 4 );
// include path
define_default( 'INCLUDE_PATH', ABSPATH . __ . 'include' );
// template path
define_default( 'TEMPLATE_PATH', ABSPATH . __ . 'template' );
// 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;
}
} );
// override default user class
define( 'SESSIONUSER_CLASS', 'User' );
// 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' );
// base directory for your virtualhosts
// e.g. you may have /var/www/example.com/index.html
// do NOT end with a slash
// TODO: support multiple hosts
define_default( 'VIRTUALHOSTS_DIR', '/var/www' );
// 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', "KISS Libre Hosting Panel" );
define_default( 'CONTACT_EMAIL', 'support@' . DOMAIN );
define_default( 'REPO_URL', 'https://gitpull.it/project/profile/15/' );
// limit session duration to 5 minutes (60s * 100m)
define_default( 'SESSION_DURATION', 6000 );
/**
* 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' );
// register web pages
add_menu_entries( [
new MenuEntry( 'index', '/', __( "Dashboard" ), null, 'backend' ),
new MenuEntry( 'login', 'login.php', __( "Login" ) ),
new MenuEntry( 'profile', 'profile.php', __( "Profile" ) ),
new MenuEntry( 'logout', 'logout.php', __( "Logout" ), null, 'read' ),
new MenuEntry( 'user-list', 'user-list.php', __( "Users" ), null, 'edit-user-all' ),
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',
] );
diff --git a/template/domain-plan-overview.php b/template/domain-plan-overview.php
index efc9a04..e3d16fc 100644
--- a/template/domain-plan-overview.php
+++ b/template/domain-plan-overview.php
@@ -1,67 +1,75 @@
.
/*
* This is the template to see Domain Plan info
*
* Called from:
* www/domain-plan.php
* template/domain-plan-page.php
*
* Available variables:
* $domain Domain object
* $plan Plan object
*/
// unuseful when load directly
defined( 'BOZ_PHP' ) or die;
?>
= esc_html( __( "Information" ) ) ?>
= esc_html( __( "Domain" ) ) ?> |
= esc_html( $domain->getDomainName() ) ?> |
= esc_html( __( "Plan" ) ) ?> |
= esc_html( $plan->getPlanName() ) ?> |
= esc_html( __( "Mailboxes" ) ) ?> |
= $plan->getPlanMailboxes() ?> |
= esc_html( __( "Mail Forwardings" ) ) ?> |
= $plan->getPlanMailforwardings() ?> |
= esc_html( __( "FTP Users" ) ) ?> |
= $plan->getPlanFTPUsers() ?> |
+
+ getPlanMailboxQuota() ): ?>
+
+ = __( "Max Mailbox Size" ) ?> |
+ = human_filesize( $plan->getPlanMailboxQuota() ) ?> |
+
+
+
= esc_html( __( "Yearly Price" ) ) ?> |
= $plan->getPlanYearlyPrice() ?> = DEFAULT_CURRENCY_SYMBOL ?> |
diff --git a/template/mailbox-stats.php b/template/mailbox-stats.php
index 2a650bd..bcec7b1 100644
--- a/template/mailbox-stats.php
+++ b/template/mailbox-stats.php
@@ -1,54 +1,85 @@
.
/*
* This is the template for some mailbox stats
*
* Called from:
* template/mailbox.php
*
* Available variables:
* $mailbox object
* $plan object
*/
// avoid to be load directly
defined( 'BOZ_PHP' ) or die;
+// calculate the remaining Mailbox quota percentage
+$remaining_quota_percentage = null;
+if( $plan ) {
+ $remaining_quota_percentage = Plan::percentage(
+ $mailbox->getMailboxLastSizeBytes(),
+ $plan->getPlanMailboxQuota()
+ );
+
+ if( $remaining_quota_percentage ) {
+ $remaining_quota_percentage -= 100;
+ }
+}
?>
= esc_html( __( "Stats" ) ) ?>
getMailboxLastSizeBytes() !== null ): ?>
+
+
+
- = esc_html( __( "Size" ) ) ?> |
+ = esc_html( __( "Current Size" ) ) ?> |
= human_filesize( $mailbox->getMailboxLastSizeBytes() ) ?> |
-
+
+
+
+ getPlanMailboxQuota() ): ?>
+
+ = esc_html( __( "Allowed Size" ) ) ?> |
+ = human_filesize( $mailbox->getPlanMailboxQuota() ) ?> |
+
+
+
-
+
+
+
+ = esc_html( __( "Free" ) ) ?> |
+ = $remaining_quota_percentage ?> % |
+
+
+
+
+
+
= esc_html( __( "No stats available" ) ) ?>
diff --git a/www/domain-plan.php b/www/domain-plan.php
index 7404f18..6e84ea9 100644
--- a/www/domain-plan.php
+++ b/www/domain-plan.php
@@ -1,106 +1,107 @@
.
/*
* This is the single e-mail forwarding edit page
*/
// load framework
require '../load.php';
// wanted informations from the templates
$domain = null;
$plan = null;
// URL paramenters (just domain)
list( $domain_name ) = url_parts( 1 );
// eventually retrieve domain from database
if( !$domain ) {
$domain = ( new DomainAPI() )
->select( [
'domain.domain_ID',
'domain_name',
'plan.plan_ID',
'plan_name',
'plan_mailboxes',
'plan_mailforwards',
'plan_databases',
'plan_ftpusers',
+ 'plan_mailboxquota',
'plan_yearlyprice',
] )
->whereDomainName( $domain_name )
->joinPlan( 'LEFT' )
->queryRow();
// no domain no party
if( !$domain ) {
PageNotFound::spawn();
}
}
// save destination action
if( is_action( 'domain-plan-save' ) ) {
// check privileges
require_permission( 'edit-domain-all' );
// check if the submitted request has sense
$new_plan_uid = $_POST['plan_uid'] ?? null;
if( !$new_plan_uid || !is_string( $new_plan_uid ) ) {
BadRequest::spawn();
}
// check if the new Plan exists
$new_plan = ( new PlanAPI() )
->wherePlanUID( $new_plan_uid )
->queryRow();
// no Plan no party
if( !$new_plan ) {
BadRequest::spawn( __( "Missing Plan" ) );
}
// finally update the Plan
( new DomainAPI() )
->whereDomain( $domain )
->update( [
'plan_ID' => $new_plan->getPlanID(),
] );
// POST -> REDIRECT -> GET
http_redirect( $domain->getDomainPlanPermalink() );
}
// spawn header
Header::spawn( [
'uid' => false,
'title' => __( "Domain Plan" ),
'breadcrumb' => [
new MenuEntry( null, $domain->getDomainPermalink(), $domain->getDomainName() ),
],
] );
// spawn the page content
template( 'domain-plan-page', [
'domain' => $domain,
'plan' => $domain,
] );
// spawn the footer
Footer::spawn();