1
0

Initial commit of the Quotas module.

This commit is contained in:
rWatcher 2011-09-12 20:42:49 -04:00
parent 1a00e0f794
commit 21feda51e5
18 changed files with 1454 additions and 0 deletions

View File

@ -0,0 +1,152 @@
<?php defined("SYSPATH") or die("No direct script access.");
/**
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2011 Bharat Mediratta
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class Admin_Quotas_Controller extends Admin_Controller {
public function index() {
// Set up a new admin page for the quotas module.
$view = new Admin_View("admin.html");
$view->page_title = t("Users and groups");
$view->page_type = "collection";
$view->page_subtype = "admin_users_quotas";
$view->content = new View("admin_quotas.html");
$page_size = module::get_var("user", "page_size", 10);
$page = Input::instance()->get("page", "1");
$builder = db::build();
$user_count = $builder->from("users")->count_records();
$view->page = $page;
$view->page_size = $page_size;
$view->children_count = $user_count;
$view->max_pages = ceil($view->children_count / $view->page_size);
$view->content->pager = new Pagination();
$view->content->pager->initialize(
array("query_string" => "page",
"total_items" => $user_count,
"items_per_page" => $page_size,
"style" => "classic"));
if ($page < 1) {
url::redirect(url::merge(array("page" => 1)));
} else if ($page > $view->content->pager->total_pages) {
url::redirect(url::merge(array("page" => $view->content->pager->total_pages)));
}
$view->content->users = ORM::factory("user")
->order_by("users.name", "ASC")
->find_all($page_size, $view->content->pager->sql_offset);
$view->content->groups = ORM::factory("group")->order_by("name", "ASC")->find_all();
$view->content->quota_options = $this->_get_quota_settings_form();
print $view;
}
public function form_group_quota($id) {
// Display the form for setting a quota for the specified group ($id).
$group = ORM::factory("group", $id);
if (empty($group)) {
throw new Kohana_404_Exception();
}
print $this->_get_edit_group_quota($group);
}
static function _get_edit_group_quota($group) {
// Generate a form for setting a quota for the specified group ($group).
$record = ORM::factory("groups_quota")->where("group_id", "=", $group->id)->find();
$form = new Forge(
"admin/quotas/edit_quota/$group->id", "", "post", array("id" => "g-edit-quota-form"));
$group = $form->group("edit_quota")->label(t("Edit group quota"));
$group->input("group_quota")->label(t("Limit (MB)"))->id("g-group_quota")->value($record->storage_limit / 1024 / 1024)
->error_messages("required", t("A value is required"));
$group->submit("")->value(t("Save"));
return $form;
}
public function edit_quota($id) {
// Save the specified quota to the database.
access::verify_csrf();
$group = ORM::factory("group", $id);
if (empty($group)) {
throw new Kohana_404_Exception();
}
$record = ORM::factory("groups_quota")->where("group_id", "=", $group->id)->find();
$form = $this->_get_edit_group_quota($group);
try {
$valid = $form->validate();
$record->group_id = $id;
$record->storage_limit = $form->edit_quota->inputs["group_quota"]->value * 1024 * 1024;
} catch (ORM_Validation_Exception $e) {
// Translate ORM validation errors into form error messages
foreach ($e->validation->errors() as $key => $error) {
$form->edit_quota->inputs[$key]->add_error($error, 1);
}
$valid = false;
}
if ($valid) {
$record->save();
message::success(t("Limit for group %group_name set", array("group_name" => $group->name)));
json::reply(array("result" => "success"));
} else {
json::reply(array("result" => "error", "html" => (string) $form));
}
}
private function _get_quota_settings_form() {
// Make a new form to allow the admin to specify how the system should calculate a user's quota.
$form = new Forge("admin/quotas/saveprefs", "", "post",
array("id" => "g-quotas-admin-form"));
// Setup a checkbox for the form.
$quota_options["use_all_sizes"] = array(t("Count resizes and thumbnails towards a users limit?"), module::get_var("quotas", "use_all_sizes"));
$add_links = $form->group("quota_preferences");
$add_links->checklist("quota_preferences_list")
->options($quota_options);
// Add a save button to the form.
$form->submit("save_preferences")->value(t("Save"));
// Return the newly generated form.
return $form;
}
public function saveprefs() {
// Prevent Cross Site Request Forgery
access::verify_csrf();
// Figure out which boxes where checked
$checkboxes_array = Input::instance()->post("quota_preferences_list");
$use_all_sizes = false;
for ($i = 0; $i < count($checkboxes_array); $i++) {
if ($checkboxes_array[$i] == "use_all_sizes") {
$use_all_sizes = true;
}
}
// Save Settings.
module::set_var("quotas", "use_all_sizes", $use_all_sizes);
message::success(t("Your Selection Has Been Saved."));
// Load Admin page.
url::redirect("admin/quotas");
}
}

View File

@ -0,0 +1,110 @@
<?php defined("SYSPATH") or die("No direct script access.");
/**
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2011 Bharat Mediratta
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class quotas_event_Core {
static function admin_menu($menu, $theme) {
// Add a User quotas link to the admin menu.
$menu->get("content_menu")
->append(Menu::factory("link")
->id("quotas")
->label(t("User quotas"))
->url(url::site("admin/quotas")));
}
static function user_created($user) {
// Set up some default values whenever a new user is created.
$record = ORM::factory("users_space_usage")->where("owner_id", "=", $user->id)->find();
if (!$record->loaded()) {
$record->owner_id = $user->id;
$record->fullsize = 0;
$record->resize = 0;
$record->thumb = 0;
$record->save();
}
}
static function user_before_delete($user) {
// When deleting a user, all of that user's items get re-assigned to the admin account,
// so the file sizes need to be reassigned to the admin user as well.
$admin = identity::admin_user();
$admin_record = ORM::factory("users_space_usage")->where("owner_id", "=", $admin->id)->find();
$deleted_user_record = ORM::factory("users_space_usage")->where("owner_id", "=", $user->id)->find();
if ($deleted_user_record->loaded()) {
$admin_record->fullsize = $admin_record->fullsize + $deleted_user_record->fullsize;
$admin_record->resize = $admin_record->resize + $deleted_user_record->resize;
$admin_record->thumb = $admin_record->thumb + $deleted_user_record->thumb;
$admin_record->save();
$deleted_user_record->delete();
}
}
static function item_before_create($item) {
// When creating a new item, make sure it's file size won't put the user over their limit.
// If it does, throw an error, which will prevent gallery from accepting the file.
$record = ORM::factory("users_space_usage")->where("owner_id", "=", $item->owner_id)->find();
if (!$record->loaded()) {
$record->owner_id = $item->owner_id;
}
if ($record->get_usage_limit() == 0) {
return;
}
if ((filesize($item->data_file) + $record->current_usage()) > $record->get_usage_limit()) {
throw new Exception($item->name . " rejected, user #" . $item->owner_id . " over limit.");
}
}
static function item_created($item) {
// When a new item is created, add it's file size to the users_space_usage table.
$record = ORM::factory("users_space_usage")->where("owner_id", "=", $item->owner_id)->find();
if (!$record->loaded()) {
$record->owner_id = $item->owner_id;
$record->fullsize = 0;
$record->resize = 0;
$record->thumb = 0;
$record->save();
}
$record->add_item($item);
}
static function item_before_delete($item) {
// When an item is deleted, remove it's file size from the users_space_usage table.
$record = ORM::factory("users_space_usage")->where("owner_id", "=", $item->owner_id)->find();
$record->remove_item($item);
}
// I can't monitor the item_before_update / item_updated events to adjust for rotated photos,
// because they fire when a new photo is uploaded (before it's created) and cause all kinds of weirdness.
// So instead, I'm using graphics_rotate to detect a rotate and remove the existing file sizes, and
// item_updated_data_file to add in the new data file sizes.
// Does item_updated_data_file fire for any other reason? (watermarking? renaming/moving/deleting/keeporiginal do not cause updated_data_file.)
static function graphics_rotate($input_file, $output_file, $options) {
// Remove the current item's file size from the quotas table.
$item = item::find_by_path(substr(str_replace(VARPATH, "", $input_file), strpos(str_replace(VARPATH, "", $input_file), "/")+1));
if ($item->loaded()) {
$record = ORM::factory("users_space_usage")->where("owner_id", "=", $item->owner_id)->find();
$record->remove_item($item);
}
}
static function item_updated_data_file($item) {
// Add the current item's file size into the quotas table.
$record = ORM::factory("users_space_usage")->where("owner_id", "=", $item->owner_id)->find();
$record->add_item($item);
}
}

View File

@ -0,0 +1,47 @@
<?php defined("SYSPATH") or die("No direct script access.");
/**
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2011 Bharat Mediratta
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class quotas_installer {
static function install() {
// Create tables.
$db = Database::instance();
$db->query("CREATE TABLE IF NOT EXISTS {users_space_usages} (
`id` int(9) NOT NULL auto_increment,
`owner_id` int(9) NOT NULL,
`fullsize` BIGINT UNSIGNED NOT NULL,
`resize` BIGINT UNSIGNED NOT NULL,
`thumb` BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (`id`),
KEY(`owner_id`, `id`))
DEFAULT CHARSET=utf8;");
$db->query("CREATE TABLE IF NOT EXISTS {groups_quotas} (
`id` int(9) NOT NULL auto_increment,
`group_id` int(9) NOT NULL,
`storage_limit` BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (`id`),
KEY(`group_id`, `id`))
DEFAULT CHARSET=utf8;");
module::set_var("quotas", "use_all_sizes", true);
// Set the module version number.
module::set_version("quotas", 1);
}
}

View File

@ -0,0 +1,126 @@
<?php defined("SYSPATH") or die("No direct script access.");
/**
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2011 Bharat Mediratta
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class quotas_task_Core {
static function available_tasks() {
// Total up all non-guest users in the users table that don't have a corresponding record
// in the users_space_usages table.
// If the result is greater then 0, display a warning for this task so the admin knows
// a user is missing and it should be run.
$missing_users = ORM::factory("user")
->where("users.guest", "=", "0")
->join("users_space_usages", "users.id", "users_space_usages.owner_id", "LEFT OUTER")
->and_where("users_space_usages.owner_id", "IS", NULL)->count_all();
$tasks = array();
$tasks[] = Task_Definition::factory()
->callback("quotas_task::update_quotasdb")
->name(t("Rebuild user quotas table"))
->description(t("Recalculates each users space usage."))
->severity($missing_users ? log::WARNING : log::SUCCESS);
return $tasks;
}
static function update_quotasdb($task) {
// Re-create the users_space_usages table and recalculate all values.
// Retrieve the total variable. If this is the first time this function has been run,
// total will be empty.
$total = $task->get("total");
$existing_items = ORM::factory("item")->where("type", "!=", "album")->find_all();
if (empty($total)) {
// If this is the first time this function has been run,
// delete and re-create the users_space_usages table, and set up
// some initial variables.
$db = Database::instance();
$db->query("DROP TABLE IF EXISTS {users_space_usages};");
$db->query("CREATE TABLE IF NOT EXISTS {users_space_usages} (
`id` int(9) NOT NULL auto_increment,
`owner_id` int(9) NOT NULL,
`fullsize` BIGINT UNSIGNED NOT NULL,
`resize` BIGINT UNSIGNED NOT NULL,
`thumb` BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (`id`),
KEY(`owner_id`, `id`))
DEFAULT CHARSET=utf8;");
// Set the initial values for all variables.
$task->set("total", count($existing_items));
$total = $task->get("total");
$task->set("last_id", 0);
$task->set("completed_items", 0);
$task->set("total_users", ORM::factory("user")->where("guest", "=", "0")->count_all());
$task->set("completed_users", 0);
$task->set("last_user_id", 0);
}
// Retrieve the values for variables from the last time this
// function was run.
$last_id = $task->get("last_id");
$completed_items = $task->get("completed_items");
$total_users = $task->get("total_users");
$completed_users = $task->get("completed_users");
$last_user_id = $task->get("last_user_id");
// First set up default values for all non-guest users.
if ($total_users > $completed_users) {
$one_user = ORM::factory("user")
->where("guest", "=", "0")
->where("id", ">", $last_user_id)
->order_by("id")
->find_all(1);
$record = ORM::factory("users_space_usage")->where("owner_id", "=", $one_user[0]->id)->find();
if (!$record->loaded()) {
$record->owner_id = $one_user[0]->id;
$record->fullsize = 0;
$record->resize = 0;
$record->thumb = 0;
$record->save();
}
$task->set("last_user_id", $one_user[0]->id);
$task->set("completed_users", ++$completed_users);
$task->status = t("Populating quotas table...");
} else {
// Loop through each non-album item in Gallery and log its file size to its owner.
$item = ORM::factory("item")
->where("type", "!=", "album")
->where("id", ">", $last_id)
->order_by("id")
->find_all(1);
$record = ORM::factory("users_space_usage")->where("owner_id", "=", $item[0]->owner_id)->find();
$record->add_item($item[0]);
// Store the current position and update the status message.
$task->set("last_id", $item[0]->id);
$task->set("completed_items", ++$completed_items);
if ($total == $completed_items) {
$task->done = true;
$task->state = "success";
$task->percent_complete = 100;
$task->status = t("Complete");
} else {
$task->percent_complete = round(100 * $completed_items / $total);
$task->status = t("Scanning $completed_items of $total files");
}
}
}
}

View File

@ -0,0 +1,35 @@
<?php defined("SYSPATH") or die("No direct script access.");
/**
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2011 Bharat Mediratta
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class quotas_theme_Core {
static function page_bottom($theme) {
// If a user is logged in, display their space usage at the bottom of the page.
if (!identity::active_user()->guest) {
$record = ORM::factory("users_space_usage")->where("owner_id", "=", identity::active_user()->id)->find();
if ($record->get_usage_limit() == 0) {
print t("You are using %usage MB", array("usage" => number_format($record->total_usage_mb(), 2)));
} else {
print t("You are using %usage of your %limit MB limit (%percentage%)",
array("usage" => number_format($record->current_usage_mb(), 2),
"limit" => number_format($record->get_usage_limit_mb(), 2),
"percentage" => number_format((($record->current_usage() / $record->get_usage_limit()) * 100), 2)));
}
}
}
}

View File

@ -0,0 +1,21 @@
<?php defined("SYSPATH") or die("No direct script access.");
/**
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2011 Bharat Mediratta
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class Groups_Quota_Model extends ORM {
}

View File

@ -0,0 +1,131 @@
<?php defined("SYSPATH") or die("No direct script access.");
/**
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2011 Bharat Mediratta
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class Users_Space_Usage_Model_Core extends ORM {
public function partial_usage_mb($file_type) {
// Return a value (in megabytes) for the user's total usage in fullsizes, resizes, or thumbs.
// $file_type should be either fullsize, resize, or thumb.
return ($this->$file_type / 1024 / 1024);
}
public function total_usage() {
// Return the user's total usage in bytes.
return ($this->fullsize + $this->resize + $this->thumb);
}
public function total_usage_mb() {
// Return the user's total usage in megabytes.
return (($this->total_usage()) / 1024 / 1024);
}
public function current_usage() {
// Return the users relevant usage in bytes based on the use_all_sizes setting.
if (module::get_var("quotas", "use_all_sizes") == true) {
return $this->total_usage();
} else {
return $this->fullsize;
}
}
public function current_usage_mb() {
// Return the users relevant usage in megabytes based on the use_all_sizes setting.
return ($this->current_usage() / 1024 / 1024);
}
public function get_usage_limit() {
// Returns a user's maximum limit in bytes.
$user_groups = ORM::factory("group")
->join("groups_users", "groups_users.group_id", "groups.id")
->join("groups_quotas", "groups_quotas.group_id", "groups.id")
->select("groups.id")
->select("groups_quotas.storage_limit")
->where("groups_users.user_id", "=", $this->owner_id)
->order_by("groups_quotas.storage_limit", "DESC")
->find_all(1);
if (!empty($user_groups)) {
if ($user_groups[0]->storage_limit <= "0") {
return 0;
} else {
return $user_groups[0]->storage_limit;
}
}
return 0;
}
public function get_usage_limit_mb() {
// Returns a user's maximum limit in megabytes.
return ($this->get_usage_limit() / 1024 / 1024);
}
public function add_item($item) {
// Adds an item's file size to the table.
if ($item->is_album()) {
return ;
}
$item_fullsize = 0;
$item_resize = 0;
$item_thumb = 0;
if (file_exists($item->file_path())) {
$item_fullsize = filesize($item->file_path());
}
if (file_exists($item->thumb_path())) {
$item_thumb = filesize($item->thumb_path());
}
if (file_exists($item->resize_path())) {
$item_resize = filesize($item->resize_path());
}
$this->fullsize = $this->fullsize + $item_fullsize;
$this->resize = $this->resize + $item_resize;
$this->thumb = $this->thumb + $item_thumb;
$this->save();
return ;
}
public function remove_item($item) {
// Removes an item's file size from the table.
if ($item->is_album()) {
return ;
}
$item_fullsize = 0;
$item_resize = 0;
$item_thumb = 0;
if (file_exists($item->file_path())) {
$item_fullsize = filesize($item->file_path());
}
if (file_exists($item->thumb_path())) {
$item_thumb = filesize($item->thumb_path());
}
if (file_exists($item->resize_path())) {
$item_resize = filesize($item->resize_path());
}
$this->fullsize = $this->fullsize - $item_fullsize;
$this->resize = $this->resize - $item_resize;
$this->thumb = $this->thumb - $item_thumb;
$this->save();
return ;
}
}

View File

@ -0,0 +1,7 @@
name = "Quotas"
description = "Assign quotas to user groups and track each users space usage."
version = 1
author_name = "rWatcher"
author_url = "http://codex.gallery2.org/User:RWatcher"
info_url = "http://codex.gallery2.org/Gallery3:Modules:quotas"
discuss_url = "http://gallery.menalto.com/node/103606"

View File

@ -0,0 +1,98 @@
<?php defined("SYSPATH") or die("No direct script access.") ?>
<div class="g-block">
<h1> <?= t("User quotas") ?> </h1>
<div class="g-block-content">
<div id="g-user-admin" class="g-block">
<h2> <?= t("Users") ?> </h2>
<div class="g-block-content">
<table id="g-user-admin-list">
<tr>
<th><?= t("Username") ?></th>
<th><?= t("Full name") ?></th>
<th><?= t("Fullsize") ?></th>
<th><?= t("Resize") ?></th>
<th><?= t("Thumbs") ?></th>
<th><?= t("Total") ?></th>
<th><?= t("Limit") ?></th>
</tr>
<? foreach ($users as $i => $user): ?>
<? $record = ORM::factory("users_space_usage")->where("owner_id", "=", $user->id)->find(); ?>
<tr id="g-user-<?= $user->id ?>" class="<?= text::alternate("g-odd", "g-even") ?> g-user <?= $user->admin ? "g-admin" : "" ?>">
<td id="g-user-<?= $user->id ?>" class="g-core-info">
<img src="<?= $user->avatar_url(20, $theme->url("images/avatar.jpg", true)) ?>"
alt="<?= html::clean_attribute($user->name) ?>"
width="20"
height="20" />
<?= html::clean($user->name) ?>
</td>
<td>
<?= html::clean($user->full_name) ?>
</td>
<td>
<?= number_format($record->partial_usage_mb("fullsize"), 2); ?> MB
</td>
<td>
<?= number_format($record->partial_usage_mb("resize"), 2); ?> MB
</td>
<td>
<?= number_format($record->partial_usage_mb("thumb"), 2); ?> MB
</td>
<td>
<?= number_format($record->total_usage_mb(), 2) ?> MB
</td>
<td>
<?= number_format($record->get_usage_limit_mb(), 2) ?> MB
</td>
</tr>
<? endforeach ?>
</table>
<div class="g-paginator">
<?= $theme->paginator() ?>
</div>
</div>
</div>
<div id="g-group-admin" class="g-block ui-helper-clearfix">
<h2> <?= t("Groups") ?> </h2>
<table id="g-group-admin-list">
<tr>
<th><?= t("Group") ?></th>
<th><?= t("Limit") ?></th>
<th><?= t("Actions") ?></th>
</tr>
<? foreach ($groups as $i => $group): ?>
<? $record = ORM::factory("groups_quota")->where("group_id", "=", $group->id)->find(); ?>
<tr id="g-group-<?= $group->id ?>" class="<?= text::alternate("g-odd", "g-even") ?> g-user ">
<td id="g-group-<?= $group->id ?>" class="g-core-info">
<?= html::clean($group->name) ?>
</td>
<td>
<?= number_format($record->storage_limit / 1024 / 1024, 2); ?> MB
</td>
<td>
<a href="<?= url::site("admin/quotas/form_group_quota/$group->id") ?>"
open_text="<?= t("Close") ?>"
class="g-panel-link g-button ui-state-default ui-corner-all ui-icon-left">
<span class="ui-icon ui-icon-pencil"></span><span class="g-button-text"><?= t("Set limit") ?></span></a>
</td>
</tr>
<? endforeach ?>
</table>
</div>
</div>
<div class="g-block-content">
<div id="g-quota-settings-admin" class="g-block">
<h2> <?= t("Settings") ?> </h2>
<?= $quota_options ?>
</div>
</div>
</div>

View File

@ -0,0 +1,152 @@
<?php defined("SYSPATH") or die("No direct script access.");
/**
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2011 Bharat Mediratta
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class Admin_Quotas_Controller extends Admin_Controller {
public function index() {
// Set up a new admin page for the quotas module.
$view = new Admin_View("admin.html");
$view->page_title = t("Users and groups");
$view->page_type = "collection";
$view->page_subtype = "admin_users_quotas";
$view->content = new View("admin_quotas.html");
$page_size = module::get_var("user", "page_size", 10);
$page = Input::instance()->get("page", "1");
$builder = db::build();
$user_count = $builder->from("users")->count_records();
$view->page = $page;
$view->page_size = $page_size;
$view->children_count = $user_count;
$view->max_pages = ceil($view->children_count / $view->page_size);
$view->content->pager = new Pagination();
$view->content->pager->initialize(
array("query_string" => "page",
"total_items" => $user_count,
"items_per_page" => $page_size,
"style" => "classic"));
if ($page < 1) {
url::redirect(url::merge(array("page" => 1)));
} else if ($page > $view->content->pager->total_pages) {
url::redirect(url::merge(array("page" => $view->content->pager->total_pages)));
}
$view->content->users = ORM::factory("user")
->order_by("users.name", "ASC")
->find_all($page_size, $view->content->pager->sql_offset);
$view->content->groups = ORM::factory("group")->order_by("name", "ASC")->find_all();
$view->content->quota_options = $this->_get_quota_settings_form();
print $view;
}
public function form_group_quota($id) {
// Display the form for setting a quota for the specified group ($id).
$group = ORM::factory("group", $id);
if (empty($group)) {
throw new Kohana_404_Exception();
}
print $this->_get_edit_group_quota($group);
}
static function _get_edit_group_quota($group) {
// Generate a form for setting a quota for the specified group ($group).
$record = ORM::factory("groups_quota")->where("group_id", "=", $group->id)->find();
$form = new Forge(
"admin/quotas/edit_quota/$group->id", "", "post", array("id" => "g-edit-quota-form"));
$group = $form->group("edit_quota")->label(t("Edit group quota"));
$group->input("group_quota")->label(t("Limit (MB)"))->id("g-group_quota")->value($record->storage_limit / 1024 / 1024)
->error_messages("required", t("A value is required"));
$group->submit("")->value(t("Save"));
return $form;
}
public function edit_quota($id) {
// Save the specified quota to the database.
access::verify_csrf();
$group = ORM::factory("group", $id);
if (empty($group)) {
throw new Kohana_404_Exception();
}
$record = ORM::factory("groups_quota")->where("group_id", "=", $group->id)->find();
$form = $this->_get_edit_group_quota($group);
try {
$valid = $form->validate();
$record->group_id = $id;
$record->storage_limit = $form->edit_quota->inputs["group_quota"]->value * 1024 * 1024;
} catch (ORM_Validation_Exception $e) {
// Translate ORM validation errors into form error messages
foreach ($e->validation->errors() as $key => $error) {
$form->edit_quota->inputs[$key]->add_error($error, 1);
}
$valid = false;
}
if ($valid) {
$record->save();
message::success(t("Limit for group %group_name set", array("group_name" => $group->name)));
json::reply(array("result" => "success"));
} else {
json::reply(array("result" => "error", "html" => (string) $form));
}
}
private function _get_quota_settings_form() {
// Make a new form to allow the admin to specify how the system should calculate a user's quota.
$form = new Forge("admin/quotas/saveprefs", "", "post",
array("id" => "g-quotas-admin-form"));
// Setup a checkbox for the form.
$quota_options["use_all_sizes"] = array(t("Count resizes and thumbnails towards a users limit?"), module::get_var("quotas", "use_all_sizes"));
$add_links = $form->group("quota_preferences");
$add_links->checklist("quota_preferences_list")
->options($quota_options);
// Add a save button to the form.
$form->submit("save_preferences")->value(t("Save"));
// Return the newly generated form.
return $form;
}
public function saveprefs() {
// Prevent Cross Site Request Forgery
access::verify_csrf();
// Figure out which boxes where checked
$checkboxes_array = Input::instance()->post("quota_preferences_list");
$use_all_sizes = false;
for ($i = 0; $i < count($checkboxes_array); $i++) {
if ($checkboxes_array[$i] == "use_all_sizes") {
$use_all_sizes = true;
}
}
// Save Settings.
module::set_var("quotas", "use_all_sizes", $use_all_sizes);
message::success(t("Your Selection Has Been Saved."));
// Load Admin page.
url::redirect("admin/quotas");
}
}

View File

@ -0,0 +1,110 @@
<?php defined("SYSPATH") or die("No direct script access.");
/**
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2011 Bharat Mediratta
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class quotas_event_Core {
static function admin_menu($menu, $theme) {
// Add a User quotas link to the admin menu.
$menu->get("content_menu")
->append(Menu::factory("link")
->id("quotas")
->label(t("User quotas"))
->url(url::site("admin/quotas")));
}
static function user_created($user) {
// Set up some default values whenever a new user is created.
$record = ORM::factory("users_space_usage")->where("owner_id", "=", $user->id)->find();
if (!$record->loaded()) {
$record->owner_id = $user->id;
$record->fullsize = 0;
$record->resize = 0;
$record->thumb = 0;
$record->save();
}
}
static function user_before_delete($user) {
// When deleting a user, all of that user's items get re-assigned to the admin account,
// so the file sizes need to be reassigned to the admin user as well.
$admin = identity::admin_user();
$admin_record = ORM::factory("users_space_usage")->where("owner_id", "=", $admin->id)->find();
$deleted_user_record = ORM::factory("users_space_usage")->where("owner_id", "=", $user->id)->find();
if ($deleted_user_record->loaded()) {
$admin_record->fullsize = $admin_record->fullsize + $deleted_user_record->fullsize;
$admin_record->resize = $admin_record->resize + $deleted_user_record->resize;
$admin_record->thumb = $admin_record->thumb + $deleted_user_record->thumb;
$admin_record->save();
$deleted_user_record->delete();
}
}
static function item_before_create($item) {
// When creating a new item, make sure it's file size won't put the user over their limit.
// If it does, throw an error, which will prevent gallery from accepting the file.
$record = ORM::factory("users_space_usage")->where("owner_id", "=", $item->owner_id)->find();
if (!$record->loaded()) {
$record->owner_id = $item->owner_id;
}
if ($record->get_usage_limit() == 0) {
return;
}
if ((filesize($item->data_file) + $record->current_usage()) > $record->get_usage_limit()) {
throw new Exception($item->name . " rejected, user #" . $item->owner_id . " over limit.");
}
}
static function item_created($item) {
// When a new item is created, add it's file size to the users_space_usage table.
$record = ORM::factory("users_space_usage")->where("owner_id", "=", $item->owner_id)->find();
if (!$record->loaded()) {
$record->owner_id = $item->owner_id;
$record->fullsize = 0;
$record->resize = 0;
$record->thumb = 0;
$record->save();
}
$record->add_item($item);
}
static function item_before_delete($item) {
// When an item is deleted, remove it's file size from the users_space_usage table.
$record = ORM::factory("users_space_usage")->where("owner_id", "=", $item->owner_id)->find();
$record->remove_item($item);
}
// I can't monitor the item_before_update / item_updated events to adjust for rotated photos,
// because they fire when a new photo is uploaded (before it's created) and cause all kinds of weirdness.
// So instead, I'm using graphics_rotate to detect a rotate and remove the existing file sizes, and
// item_updated_data_file to add in the new data file sizes.
// Does item_updated_data_file fire for any other reason? (watermarking? renaming/moving/deleting/keeporiginal do not cause updated_data_file.)
static function graphics_rotate($input_file, $output_file, $options) {
// Remove the current item's file size from the quotas table.
$item = item::find_by_path(substr(str_replace(VARPATH, "", $input_file), strpos(str_replace(VARPATH, "", $input_file), "/")+1));
if ($item->loaded()) {
$record = ORM::factory("users_space_usage")->where("owner_id", "=", $item->owner_id)->find();
$record->remove_item($item);
}
}
static function item_updated_data_file($item) {
// Add the current item's file size into the quotas table.
$record = ORM::factory("users_space_usage")->where("owner_id", "=", $item->owner_id)->find();
$record->add_item($item);
}
}

View File

@ -0,0 +1,47 @@
<?php defined("SYSPATH") or die("No direct script access.");
/**
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2011 Bharat Mediratta
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class quotas_installer {
static function install() {
// Create tables.
$db = Database::instance();
$db->query("CREATE TABLE IF NOT EXISTS {users_space_usages} (
`id` int(9) NOT NULL auto_increment,
`owner_id` int(9) NOT NULL,
`fullsize` BIGINT UNSIGNED NOT NULL,
`resize` BIGINT UNSIGNED NOT NULL,
`thumb` BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (`id`),
KEY(`owner_id`, `id`))
DEFAULT CHARSET=utf8;");
$db->query("CREATE TABLE IF NOT EXISTS {groups_quotas} (
`id` int(9) NOT NULL auto_increment,
`group_id` int(9) NOT NULL,
`storage_limit` BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (`id`),
KEY(`group_id`, `id`))
DEFAULT CHARSET=utf8;");
module::set_var("quotas", "use_all_sizes", true);
// Set the module version number.
module::set_version("quotas", 1);
}
}

View File

@ -0,0 +1,126 @@
<?php defined("SYSPATH") or die("No direct script access.");
/**
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2011 Bharat Mediratta
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class quotas_task_Core {
static function available_tasks() {
// Total up all non-guest users in the users table that don't have a corresponding record
// in the users_space_usages table.
// If the result is greater then 0, display a warning for this task so the admin knows
// a user is missing and it should be run.
$missing_users = ORM::factory("user")
->where("users.guest", "=", "0")
->join("users_space_usages", "users.id", "users_space_usages.owner_id", "LEFT OUTER")
->and_where("users_space_usages.owner_id", "IS", NULL)->count_all();
$tasks = array();
$tasks[] = Task_Definition::factory()
->callback("quotas_task::update_quotasdb")
->name(t("Rebuild user quotas table"))
->description(t("Recalculates each users space usage."))
->severity($missing_users ? log::WARNING : log::SUCCESS);
return $tasks;
}
static function update_quotasdb($task) {
// Re-create the users_space_usages table and recalculate all values.
// Retrieve the total variable. If this is the first time this function has been run,
// total will be empty.
$total = $task->get("total");
$existing_items = ORM::factory("item")->where("type", "!=", "album")->find_all();
if (empty($total)) {
// If this is the first time this function has been run,
// delete and re-create the users_space_usages table, and set up
// some initial variables.
$db = Database::instance();
$db->query("DROP TABLE IF EXISTS {users_space_usages};");
$db->query("CREATE TABLE IF NOT EXISTS {users_space_usages} (
`id` int(9) NOT NULL auto_increment,
`owner_id` int(9) NOT NULL,
`fullsize` BIGINT UNSIGNED NOT NULL,
`resize` BIGINT UNSIGNED NOT NULL,
`thumb` BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (`id`),
KEY(`owner_id`, `id`))
DEFAULT CHARSET=utf8;");
// Set the initial values for all variables.
$task->set("total", count($existing_items));
$total = $task->get("total");
$task->set("last_id", 0);
$task->set("completed_items", 0);
$task->set("total_users", ORM::factory("user")->where("guest", "=", "0")->count_all());
$task->set("completed_users", 0);
$task->set("last_user_id", 0);
}
// Retrieve the values for variables from the last time this
// function was run.
$last_id = $task->get("last_id");
$completed_items = $task->get("completed_items");
$total_users = $task->get("total_users");
$completed_users = $task->get("completed_users");
$last_user_id = $task->get("last_user_id");
// First set up default values for all non-guest users.
if ($total_users > $completed_users) {
$one_user = ORM::factory("user")
->where("guest", "=", "0")
->where("id", ">", $last_user_id)
->order_by("id")
->find_all(1);
$record = ORM::factory("users_space_usage")->where("owner_id", "=", $one_user[0]->id)->find();
if (!$record->loaded()) {
$record->owner_id = $one_user[0]->id;
$record->fullsize = 0;
$record->resize = 0;
$record->thumb = 0;
$record->save();
}
$task->set("last_user_id", $one_user[0]->id);
$task->set("completed_users", ++$completed_users);
$task->status = t("Populating quotas table...");
} else {
// Loop through each non-album item in Gallery and log its file size to its owner.
$item = ORM::factory("item")
->where("type", "!=", "album")
->where("id", ">", $last_id)
->order_by("id")
->find_all(1);
$record = ORM::factory("users_space_usage")->where("owner_id", "=", $item[0]->owner_id)->find();
$record->add_item($item[0]);
// Store the current position and update the status message.
$task->set("last_id", $item[0]->id);
$task->set("completed_items", ++$completed_items);
if ($total == $completed_items) {
$task->done = true;
$task->state = "success";
$task->percent_complete = 100;
$task->status = t("Complete");
} else {
$task->percent_complete = round(100 * $completed_items / $total);
$task->status = t("Scanning $completed_items of $total files");
}
}
}
}

View File

@ -0,0 +1,35 @@
<?php defined("SYSPATH") or die("No direct script access.");
/**
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2011 Bharat Mediratta
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class quotas_theme_Core {
static function page_bottom($theme) {
// If a user is logged in, display their space usage at the bottom of the page.
if (!identity::active_user()->guest) {
$record = ORM::factory("users_space_usage")->where("owner_id", "=", identity::active_user()->id)->find();
if ($record->get_usage_limit() == 0) {
print t("You are using %usage MB", array("usage" => number_format($record->total_usage_mb(), 2)));
} else {
print t("You are using %usage of your %limit MB limit (%percentage%)",
array("usage" => number_format($record->current_usage_mb(), 2),
"limit" => number_format($record->get_usage_limit_mb(), 2),
"percentage" => number_format((($record->current_usage() / $record->get_usage_limit()) * 100), 2)));
}
}
}
}

View File

@ -0,0 +1,21 @@
<?php defined("SYSPATH") or die("No direct script access.");
/**
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2011 Bharat Mediratta
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class Groups_Quota_Model extends ORM {
}

View File

@ -0,0 +1,131 @@
<?php defined("SYSPATH") or die("No direct script access.");
/**
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2011 Bharat Mediratta
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class Users_Space_Usage_Model_Core extends ORM {
public function partial_usage_mb($file_type) {
// Return a value (in megabytes) for the user's total usage in fullsizes, resizes, or thumbs.
// $file_type should be either fullsize, resize, or thumb.
return ($this->$file_type / 1024 / 1024);
}
public function total_usage() {
// Return the user's total usage in bytes.
return ($this->fullsize + $this->resize + $this->thumb);
}
public function total_usage_mb() {
// Return the user's total usage in megabytes.
return (($this->total_usage()) / 1024 / 1024);
}
public function current_usage() {
// Return the users relevant usage in bytes based on the use_all_sizes setting.
if (module::get_var("quotas", "use_all_sizes") == true) {
return $this->total_usage();
} else {
return $this->fullsize;
}
}
public function current_usage_mb() {
// Return the users relevant usage in megabytes based on the use_all_sizes setting.
return ($this->current_usage() / 1024 / 1024);
}
public function get_usage_limit() {
// Returns a user's maximum limit in bytes.
$user_groups = ORM::factory("group")
->join("groups_users", "groups_users.group_id", "groups.id")
->join("groups_quotas", "groups_quotas.group_id", "groups.id")
->select("groups.id")
->select("groups_quotas.storage_limit")
->where("groups_users.user_id", "=", $this->owner_id)
->order_by("groups_quotas.storage_limit", "DESC")
->find_all(1);
if (!empty($user_groups)) {
if ($user_groups[0]->storage_limit <= "0") {
return 0;
} else {
return $user_groups[0]->storage_limit;
}
}
return 0;
}
public function get_usage_limit_mb() {
// Returns a user's maximum limit in megabytes.
return ($this->get_usage_limit() / 1024 / 1024);
}
public function add_item($item) {
// Adds an item's file size to the table.
if ($item->is_album()) {
return ;
}
$item_fullsize = 0;
$item_resize = 0;
$item_thumb = 0;
if (file_exists($item->file_path())) {
$item_fullsize = filesize($item->file_path());
}
if (file_exists($item->thumb_path())) {
$item_thumb = filesize($item->thumb_path());
}
if (file_exists($item->resize_path())) {
$item_resize = filesize($item->resize_path());
}
$this->fullsize = $this->fullsize + $item_fullsize;
$this->resize = $this->resize + $item_resize;
$this->thumb = $this->thumb + $item_thumb;
$this->save();
return ;
}
public function remove_item($item) {
// Removes an item's file size from the table.
if ($item->is_album()) {
return ;
}
$item_fullsize = 0;
$item_resize = 0;
$item_thumb = 0;
if (file_exists($item->file_path())) {
$item_fullsize = filesize($item->file_path());
}
if (file_exists($item->thumb_path())) {
$item_thumb = filesize($item->thumb_path());
}
if (file_exists($item->resize_path())) {
$item_resize = filesize($item->resize_path());
}
$this->fullsize = $this->fullsize - $item_fullsize;
$this->resize = $this->resize - $item_resize;
$this->thumb = $this->thumb - $item_thumb;
$this->save();
return ;
}
}

View File

@ -0,0 +1,7 @@
name = "Quotas"
description = "Assign quotas to user groups and track each users space usage."
version = 1
author_name = "rWatcher"
author_url = "http://codex.gallery2.org/User:RWatcher"
info_url = "http://codex.gallery2.org/Gallery3:Modules:quotas"
discuss_url = "http://gallery.menalto.com/node/103606"

View File

@ -0,0 +1,98 @@
<?php defined("SYSPATH") or die("No direct script access.") ?>
<div class="g-block">
<h1> <?= t("User quotas") ?> </h1>
<div class="g-block-content">
<div id="g-user-admin" class="g-block">
<h2> <?= t("Users") ?> </h2>
<div class="g-block-content">
<table id="g-user-admin-list">
<tr>
<th><?= t("Username") ?></th>
<th><?= t("Full name") ?></th>
<th><?= t("Fullsize") ?></th>
<th><?= t("Resize") ?></th>
<th><?= t("Thumbs") ?></th>
<th><?= t("Total") ?></th>
<th><?= t("Limit") ?></th>
</tr>
<? foreach ($users as $i => $user): ?>
<? $record = ORM::factory("users_space_usage")->where("owner_id", "=", $user->id)->find(); ?>
<tr id="g-user-<?= $user->id ?>" class="<?= text::alternate("g-odd", "g-even") ?> g-user <?= $user->admin ? "g-admin" : "" ?>">
<td id="g-user-<?= $user->id ?>" class="g-core-info">
<img src="<?= $user->avatar_url(20, $theme->url("images/avatar.jpg", true)) ?>"
alt="<?= html::clean_attribute($user->name) ?>"
width="20"
height="20" />
<?= html::clean($user->name) ?>
</td>
<td>
<?= html::clean($user->full_name) ?>
</td>
<td>
<?= number_format($record->partial_usage_mb("fullsize"), 2); ?> MB
</td>
<td>
<?= number_format($record->partial_usage_mb("resize"), 2); ?> MB
</td>
<td>
<?= number_format($record->partial_usage_mb("thumb"), 2); ?> MB
</td>
<td>
<?= number_format($record->total_usage_mb(), 2) ?> MB
</td>
<td>
<?= number_format($record->get_usage_limit_mb(), 2) ?> MB
</td>
</tr>
<? endforeach ?>
</table>
<div class="g-paginator">
<?= $theme->paginator() ?>
</div>
</div>
</div>
<div id="g-group-admin" class="g-block ui-helper-clearfix">
<h2> <?= t("Groups") ?> </h2>
<table id="g-group-admin-list">
<tr>
<th><?= t("Group") ?></th>
<th><?= t("Limit") ?></th>
<th><?= t("Actions") ?></th>
</tr>
<? foreach ($groups as $i => $group): ?>
<? $record = ORM::factory("groups_quota")->where("group_id", "=", $group->id)->find(); ?>
<tr id="g-group-<?= $group->id ?>" class="<?= text::alternate("g-odd", "g-even") ?> g-user ">
<td id="g-group-<?= $group->id ?>" class="g-core-info">
<?= html::clean($group->name) ?>
</td>
<td>
<?= number_format($record->storage_limit / 1024 / 1024, 2); ?> MB
</td>
<td>
<a href="<?= url::site("admin/quotas/form_group_quota/$group->id") ?>"
open_text="<?= t("Close") ?>"
class="g-panel-link g-button ui-state-default ui-corner-all ui-icon-left">
<span class="ui-icon ui-icon-pencil"></span><span class="g-button-text"><?= t("Set limit") ?></span></a>
</td>
</tr>
<? endforeach ?>
</table>
</div>
</div>
<div class="g-block-content">
<div id="g-quota-settings-admin" class="g-block">
<h2> <?= t("Settings") ?> </h2>
<?= $quota_options ?>
</div>
</div>
</div>