File: platform/plugins/Users/classes/Users/Quota.php
<?php
/**
* @module Users
*/
/**
* Class representing 'Quota' rows in the 'Users' database
* You can create an object of this class either to
* access its non-static methods, or to actually
* represent a quota row in the Users database.
*
* @class Users_Quota
* @extends Base_Users_Quota
*/
class Users_Quota extends Base_Users_Quota
{
/**
* The setUp() method is called the first time
* an object of this class is constructed.
* @method setUp
*/
function setUp()
{
parent::setUp();
// INSERT YOUR CODE HERE
// e.g. $this->hasMany(...) and stuff like that.
}
/**
* Check whether the given user can use some more units of the quota.
* For APIs, you might call this once for a client app user,
* and once for a client person user.
* It will look in the config under "Users"/"quotas"/$name
* and expect to find {"duration": info} where info can be either an integer quota,
* or a hash of {privilegeName: quotaForPrivilege} which should have at least the key ""
* for default quota with no privileges.
* @param {string} $userId the user that will be using the quota
* @param {string} $resourceId pass an empty string for global resource check, or for example a string beginning with Users::communityId()
* @param {string} $name the name of the quota to check
* @param {boolean} [$throwIfQuota=false] pass true to throw an exception if one or more of the quotas have been exceeded
* @param {integer} [$units=1] how many units of the quota you expect the user will use
* @param {array} [$privileges=array()] any strings naming privileges the user has with respect to the resourceId that might increase the max of the quota
* @param {boolean} [$begin=true] whether to begin a database transaction, in which case you must call $quota->used($used) to commit the transaction.
* @return {Users_Quota|array} if no quotas were exceeded, this object is returned and you can call ->used($units) on it later. But if some quotas were exceeded, the function returns the array of corresponding durations, sorted by smallest first. So use is_array() with the return value to find out if any quotas were exceeded.
*/
static function check(
$userId,
$resourceId,
$name,
$throwIfQuota = false,
$units = 1,
$privileges = array(),
$begin = true)
{
$quota = new Users_Quota(compact('userId', 'resourceId', 'name'));
$durations = array();
$quotas = Q_Config::get('Users', 'quotas', array());
if (!isset($quotas[$name])) {
$quota->set('begun', false);
return $quota;
}
$q = $quotas[$name];
if (!is_array($q)) {
throw new Q_Exception_BadValue(array(
'internal' => "Users/quotas/$name",
'problem' => "must be an array"
));
}
$title = isset($q['title']) ? $q['title'] : $name;
foreach ($q as $duration => $info) {
if (!is_numeric($duration)) {
continue;
}
if (!in_array($duration, $durations)) {
$durations[] = (int)$duration;
}
}
if (empty($durations)) {
return true;
}
$condition = '';
rsort($durations);
$condition = $largest = reset($durations);
$time = Users_Quota::db()->getCurrentTimestamp();
for ($i=1, $c=count($durations); $i < $c; ++$i) {
$duration = $durations[$i];
$startTime = $time - $duration;
$condition = "IF (insertedTime >= FROM_UNIXTIME($startTime), $duration, $condition)";
}
$startTime = $time - $largest;
$expr = new Db_Expression("FROM_UNIXTIME($startTime)");
$query = Users_Quota::select("$condition c, SUM(units) si")->where(array(
'userId' => $userId,
'resourceId' => $resourceId,
'insertedTime' => new Db_Range($expr, true, false, null)
))->groupBy('c')->orderBy('c', false);
$queries = $query->shard();
if ($begin) {
$query = $query->begin();
}
$shards = array_keys($queries);
$quota->set(array(
'begun' => $begin,
'shards' => $shards
));
$results = $query->fetchAll(PDO::FETCH_ASSOC);
if (!isset($privileges)) {
$privileges = array();
}
$exceeded = array();
sort($durations);
foreach ($durations as $d) {
$info = $q[$d];
if (is_array($info)) {
if (!isset($info[''])) {
throw new Q_Exception_MissingConfig(array(
'fieldpath' => "Users/quotas/$name/$d/" . '""'
));
}
$max = $info[''];
foreach ($info as $k => $v) {
if (!is_numeric($v)) {
throw new Q_Exception_BadValue(array(
'internal' => "Users/quotas/$name/$d/$k",
'problem' => "must be an integer"
));
}
if (in_array($k, $privileges)) {
$max = max($max, $v);
}
}
} else {
$max = $info;
if (!is_numeric($max)) {
throw new Q_Exception_BadValue(array(
'internal' => "Users/quotas/$name/$d",
'problem' => "must be an integer"
));
}
}
$si = 0;
foreach ($results as $r) {
if ($r['c'] <= $duration) {
$si += $r['si'];
}
}
if ($si + $units > $max) {
$exceeded[] = $d;
}
}
if ($exceeded) {
Users_Quota::rollback()->execute(false, $shards);
if ($throwIfQuota) {
throw new Users_Exception_Quota(compact('title'));
}
return $exceeded;
}
return $quota;
}
/**
* Insert a record of the user using some units of the quota,
* after preparing this object and its fields.
* For APIs, you might call this once for a client app user,
* and once for a client person user.
* You must call this to commit the transaction.
* @param {integer} [$units=1] how many units of the quota the user has used
* @return {boolean} whether the quota record was inserted successfully
*/
function used($units = 1)
{
$this->units = $units;
$this->save();
if ($this->get('begun')) {
$shards = array_keys($this->get('shards'));
Users_Quota::commit()->execute(false, $shards);
}
return true;
}
/*
* Add any Users_Quota methods here, whether public or not
*/
/**
* Implements the __set_state method, so it can work with
* with var_export and be re-imported successfully.
* @method __set_state
* @static
* @param {array} $array
* @return {Users_Quota} Class instance
*/
static function __set_state(array $array) {
$result = new Users_Quota();
foreach($array as $k => $v)
$result->$k = $v;
return $result;
}
};