~snip
I'm not a big php coder but here's what I could try to do
The bug arises when `$sendableUserMerit - $excessSent` results in a negative value for merit sources which actually causes a negative spendable merit balance. So I thought the easiest fix is to cap `$sendableUserMerit` at 0 after subtracting `$excessSent` in the source branch. I may be wrong though and I don't mind an hint.
function calculate_smerit($user){
$sendableUserMerit = db_query("select sum(amount) from merit_ledger where ID_MEMBER_TO=$user", __FILE__, __LINE__);
if(mysql_num_rows($sendableUserMerit))
$sendableUserMerit = (int)(mysql_fetch_array($sendableUserMerit)[0]/2);
else
$sendableUserMerit = 0;
// merit_sources is a log of when users' source merit was changed: "source merit becomes 'amount' at timestamp 'time'"
$q = db_query("select time, amount from merit_sources where ID_MEMBER=$user order by time asc", __FILE__, __LINE__);
if(!mysql_num_rows($q)) {
$sent = db_query("select sum(amount) from merit_ledger where ID_MEMBER_FROM=$user", __FILE__, __LINE__);
if(mysql_num_rows($sent))
$sent = (int)mysql_fetch_array($sent)[0];
else
$sent = 0;
return array(max(0, $sendableUserMerit - $sent), 0);
}
// this user is/was a source, so the calculation is more complex
$sourceAmtLog = array();
while($row=mysql_fetch_array($q))
$sourceAmtLog[] = $row;
mysql_free_result($q);
$timeWindow = 60*60*24*30;
list($excessSent) = mysql_fetch_array(db_query("
select sum(amount) from merit_ledger where ID_MEMBER_FROM=$user and time<{$sourceAmtLog[0][0]}", __FILE__, __LINE__));
if(empty($excessSent))
$excessSent = 0;
$sourceMeritSendsRange = array();
$sourceMeritSendsRangeSum = 0;
$sourcePos = 0;
$q = db_query("select time, amount from merit_ledger where ID_MEMBER_FROM=$user and time>={$sourceAmtLog[0][0]}", __FILE__, __LINE__);
$sends = array();
while($row = mysql_fetch_array($q))
$sends[] = $row;
mysql_free_result($q);
foreach($sends as $send) {
if($sourcePos < count($sourceAmtLog)-1 && $send[0] >= $sourceAmtLog[$sourcePos+1][0]) {
$sourcePos++;
$sourceMeritSendsRangeSum=0;
$sourceMeritSendsRange = array();
}
while(count($sourceMeritSendsRange)>0 && ($send[0] - $sourceMeritSendsRange[0][0] > $timeWindow)) {
$sourceMeritSendsRangeSum -= array_shift($sourceMeritSendsRange)[1];
}
$remainingSource = max($sourceAmtLog[$sourcePos][1]-$sourceMeritSendsRangeSum, 0);
$excessSent += max($send[1]-$remainingSource, 0);
$sourceMeritSendAmount = min($remainingSource, $send[1]);
if($sourceMeritSendAmount>0) {
$sourceMeritSendsRange[] = array($send[0], $sourceMeritSendAmount);
$sourceMeritSendsRangeSum += $sourceMeritSendAmount;
}
}
// FIX: Cap at 0 to prevent negative values
$sendableUserMerit = max(0, $sendableUserMerit - $excessSent);
if($sourcePos != count($sourceAmtLog)-1) {
$sourcePos = count($sourceAmtLog)-1;
$sourceMeritSendsRangeSum=0;
$sourceMeritSendsRange = array();
}
while(count($sourceMeritSendsRange) > 0 && (time() - $sourceMeritSendsRange[0][0] > $timeWindow)) {
$sourceMeritSendsRangeSum -= array_shift($sourceMeritSendsRange)[1];
}
$sendableSourceMerit = max($sourceAmtLog[$sourcePos][1] - $sourceMeritSendsRangeSum, 0);
return array($sendableUserMerit, $sendableSourceMerit);
}