// isValidDuration validates a value as a time duration string (in hours and minutes).
static function isValidDuration($value) {
- if (strlen($value)==0 || !isset($value)) return false;
+ if (strlen($value) == 0 || !isset($value)) return false;
if ($value == '24:00' || $value == '2400') return true;
return false;
}
+ // isValidQuota validates a localized value as an hours quota string (in hours and minutes).
+ static function isValidQuota($value) {
+
+ if (strlen($value) == 0 || !isset($value)) return true;
+
+ if (preg_match('/^[0-9]{1,3}h?$/', $value )) { // 000 - 999
+ return true;
+ }
+
+ if (preg_match('/^[0-9]{1,3}:[0-5][0-9]$/', $value )) { // 000:00 - 999:59
+ return true;
+ }
+
+ global $user;
+ $localizedPattern = '/^([0-9]{1,3})?['.$user->decimal_mark.'][0-9]{1,4}h?$/';
+ if (preg_match($localizedPattern, $value )) { // decimal values like 000.5, 999.25h, ... .. 999.9999h (or with comma)
+ return true;
+ }
+
+ return false;
+ }
+
+ // quotaToFloat converts a valid quota value to a float.
+ static function quotaToFloat($value) {
+
+ if (preg_match('/^[0-9]{1,3}h?$/', $value )) { // 000 - 999
+ return (float) $value;
+ }
+
+ if (preg_match('/^[0-9]{1,3}:[0-5][0-9]$/', $value )) { // 000:00 - 999:59
+ $minutes = ttTimeHelper::toMinutes($value);
+ return ($minutes / 60.0);
+ }
+
+ global $user;
+ $localizedPattern = '/^([0-9]{1,3})?['.$user->decimal_mark.'][0-9]{1,4}h?$/';
+ if (preg_match($localizedPattern, $value )) { // decimal values like 000.5, 999.25h, ... .. 999.9999h (or with comma)
+ // Strip optional h in the end.
+ $value = trim($value, 'h');
+ if ($user->decimal_mark == ',')
+ $value = str_replace($value, ',', '.');
+ return (float) $value;
+ }
+
+ return null;
+ }
+
// normalizeDuration - converts a valid time duration string to format 00:00.
static function normalizeDuration($value, $leadingZero = true) {
$time_value = $value;
// toAbsDuration - converts a number of minutes to format 0:00
// even if $minutes is negative.
- static function toAbsDuration($minutes){
+ static function toAbsDuration($minutes, $abbreviate = false){
$hours = (string)((int)abs($minutes / 60));
- $mins = (string)(abs($minutes % 60));
+ $mins = (string) round(abs(fmod($minutes, 60)));
if (strlen($mins) == 1)
$mins = '0' . $mins;
+ if ($abbreviate && $mins == '00')
+ return $hours;
+
return $hours.':'.$mins;
}
<br>
<table cellspacing="0" cellpadding="4" width="100%" border="0">
<tr>
- <td align="center"> Anuko Time Tracker 1.17.6.3797 | Copyright © <a href="https://www.anuko.com/lp/tt_3.htm" target="_blank">Anuko</a> |
+ <td align="center"> Anuko Time Tracker 1.17.7.3798 | Copyright © <a href="https://www.anuko.com/lp/tt_3.htm" target="_blank">Anuko</a> |
<a href="https://www.anuko.com/lp/tt_4.htm" target="_blank">{$i18n.footer.credits}</a> |
<a href="https://www.anuko.com/lp/tt_5.htm" target="_blank">{$i18n.footer.license}</a> |
<a href="https://www.anuko.com/lp/tt_7.htm" target="_blank">{$i18n.footer.improve}</a>
setChange("ALTER TABLE `tt_log` ADD `paid` tinyint(4) NULL default '0' AFTER `billable`");
}
- if ($_POST["convert11400to11700"]) {
+ if ($_POST["convert11400to11707"]) {
setChange("ALTER TABLE `tt_teams` DROP `address`");
setChange("ALTER TABLE `tt_fav_reports` ADD `report_spec` text default NULL AFTER `user_id`");
setChange("ALTER TABLE `tt_fav_reports` ADD `paid_status` tinyint(4) default NULL AFTER `invoice`");
setChange("ALTER TABLE `tt_fav_reports` ADD `show_paid` tinyint(4) NOT NULL DEFAULT '0' AFTER `show_invoice`");
setChange("ALTER TABLE `tt_expense_items` ADD `paid` tinyint(4) NULL default '0' AFTER `invoice_id`");
+ setChange("ALTER TABLE `tt_monthly_quotas` MODIFY `quota` decimal(5,2) NOT NULL");
}
if ($_POST["cleanup"]) {
<h2>DB Install</h2>
<table width="80%" border="1" cellpadding="10" cellspacing="0">
<tr>
- <td width="80%"><b>Create database structure (v1.17.0)</b>
+ <td width="80%"><b>Create database structure (v1.17.7)</b>
<br>(applies only to new installations, do not execute when updating)</br></td><td><input type="submit" name="crstructure" value="Create"></td>
</tr>
</table>
<td><input type="submit" name="convert1600to11400" value="Update"><br></td>
</tr>
<tr valign="top">
- <td>Update database structure (v1.14 to v1.17)</td>
- <td><input type="submit" name="convert11400to11700" value="Update"><br></td>
+ <td>Update database structure (v1.14 to v1.17.7)</td>
+ <td><input type="submit" name="convert11400to11707" value="Update"><br></td>
</tr>
</table>
`team_id` int(11) NOT NULL, # team id
`year` smallint(5) UNSIGNED NOT NULL, # quota year
`month` tinyint(3) UNSIGNED NOT NULL, # quota month
- `quota` smallint(5) UNSIGNED NOT NULL, # number of work hours in specified month and year
+ `quota` decimal(5,2) NOT NULL, # number of work hours in specified month and year
PRIMARY KEY (`team_id`,`year`,`month`)
);
// | https://www.anuko.com/time_tracker/credits.htm
// +----------------------------------------------------------------------+
+import('ttTimeHelper');
+
// MontlyQuota class implements handling of work hour quotas.
class MonthlyQuota {
// update - deletes a quota, then inserts a new one.
public function update($year, $month, $quota) {
- $teamId = $this->team_id;
- $deleteSql = "DELETE FROM tt_monthly_quotas WHERE year = $year AND month = $month AND team_id = $teamId";
+ $team_id = $this->team_id;
+ $deleteSql = "DELETE FROM tt_monthly_quotas WHERE year = $year AND month = $month AND team_id = $team_id";
$this->db->exec($deleteSql);
if ($quota){
- $insertSql = "INSERT INTO tt_monthly_quotas (team_id, year, month, quota) values ($teamId, $year, $month, $quota)";
+ $float_quota = ttTimeHelper::quotaToFloat($quota);
+ $insertSql = "INSERT INTO tt_monthly_quotas (team_id, year, month, quota) values ($team_id, $year, $month, $float_quota)";
$affected = $this->db->exec($insertSql);
return (!is_a($affected, 'PEAR_Error'));
}
// getSingle - obtains a quota for a single month.
private function getSingle($year, $month) {
- $teamId = $this->team_id;
- $sql = "SELECT quota FROM tt_monthly_quotas WHERE year = $year AND month = $month AND team_id = $teamId";
+ $team_id = $this->team_id;
+ $sql = "SELECT quota FROM tt_monthly_quotas WHERE year = $year AND month = $month AND team_id = $team_id";
$reader = $this->db->query($sql);
if (is_a($reader, 'PEAR_Error')) {
return false;
// getMany - returns an array of quotas for a given year for team.
private function getMany($year){
- $teamId = $this->team_id;
- $sql = "SELECT month, quota FROM tt_monthly_quotas WHERE year = $year AND team_id = $teamId";
+ $team_id = $this->team_id;
+ $sql = "SELECT month, quota FROM tt_monthly_quotas WHERE year = $year AND team_id = $team_id";
$result = array();
$res = $this->db->query($sql);
if (is_a($res, 'PEAR_Error')) {
require_once('plugins/MonthlyQuota.class.php');
import('form.Form');
import('ttTeamHelper');
+import('ttTimeHelper');
// Access check.
if (!ttAccessCheck(right_manage_team) || !$user->isPluginEnabled('mq')) {
$quota = new MonthlyQuota();
if ($request->isPost()){
- // TODO: Add parameter validation.
- $res = false;
- if ($_POST['btn_hours']){
-
- // User changed workday hours for team.
- $hours = (int)$request->getParameter('workdayHours');
- $res = ttTeamHelper::update($user->team_id, array('name'=>$user->team,'workday_hours'=>$hours));
+ // Validate user input.
+ for ($i = 0; $i < count($months); $i++){
+ $val = $request->getParameter($months[$i]);
+ if (!ttTimeHelper::isValidQuota($val))
+ $err->add($i18n->getKey('error.field'), $months[$i]);
}
- if ($_POST['btn_submit']){
- // User pressed the Save button under monthly quotas table.
- $postedYear = $request->getParameter('year');
- $selectedYear = intval($postedYear);
- for ($i = 0; $i < count($months); $i++){
- $res = $quota->update($postedYear, $i+1, $request->getParameter($months[$i]));
+ // Finished validating user input.
+
+ if ($err->no()) {
+
+ $res = false;
+ if ($_POST['btn_hours']){
+
+ // User changed workday hours for team.
+ $hours = (int)$request->getParameter('workdayHours');
+ $res = ttTeamHelper::update($user->team_id, array('name'=>$user->team,'workday_hours'=>$hours));
+ }
+ if ($_POST['btn_submit']){
+ // User pressed the Save button under monthly quotas table.
+ $postedYear = $request->getParameter('year');
+ $selectedYear = intval($postedYear);
+ for ($i = 0; $i < count($months); $i++){
+ $res = $quota->update($postedYear, $i+1, $request->getParameter($months[$i]));
+ }
+ }
+ if ($res) {
+ // header('Location: profile_edit.php');
+ header('Location: quotas.php'); // For debugging.
+ exit();
+ } else {
+ $err->add($i18n->getKey('error.db'));
}
- }
- if ($res) {
- header('Location: profile_edit.php');
- exit();
- } else {
- $err->add($i18n->getKey('error.db'));
}
}
$value = "";
if (array_key_exists($i+1, $monthsData)){
$value = $monthsData[$i+1];
+ $value = ttTimeHelper::toAbsDuration($value * 60, true);
}
$name = $months[$i];
- $form->addInput(array('type'=>'text','name'=>$name,'maxlength'=>3,'value'=> $value,'style'=>'width:50px'));
+ $form->addInput(array('type'=>'text','name'=>$name,'maxlength'=>6,'value'=> $value,'style'=>'width:70px'));
}
$smarty->assign('months', $months);
$quota = new MonthlyQuota();
$month_quota = $quota->get($selected_date->mYear, $selected_date->mMonth);
$month_total = ttTimeHelper::getTimeForMonth($user->getActiveUser(), $selected_date);
- $minutes_left = ttTimeHelper::toMinutes($month_quota) - ttTimeHelper::toMinutes($month_total);
+ $minutes_left = round(60*$month_quota) - ttTimeHelper::toMinutes($month_total);
$smarty->assign('month_total', $month_total);
$smarty->assign('over_quota', $minutes_left < 0);
$quota = new MonthlyQuota();
$month_quota = $quota->get($selected_date->mYear, $selected_date->mMonth);
$month_total = ttTimeHelper::getTimeForMonth($user->getActiveUser(), $selected_date);
- $minutes_left = ttTimeHelper::toMinutes($month_quota) - ttTimeHelper::toMinutes($month_total);
+ $minutes_left = round(60*$month_quota) - ttTimeHelper::toMinutes($month_total);
$smarty->assign('month_total', $month_total);
$smarty->assign('over_quota', $minutes_left < 0);