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; ?>

+ + getPlanMailboxQuota() ): ?> + + + + + +
getDomainName() ) ?>
getPlanName() ) ?>
getPlanMailboxes() ?>
getPlanMailforwardings() ?>
getPlanFTPUsers() ?>
getPlanMailboxQuota() ) ?>
getPlanYearlyPrice() ?>
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; + } +} ?>

getMailboxLastSizeBytes() !== null ): ?> + + + - + -
getMailboxLastSizeBytes() ) ?>
+ + + + getPlanMailboxQuota() ): ?> + + + getPlanMailboxQuota() ) ?> + + + - + + + + + % + + + + + + 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();