From a84f7509ea5462f08ca01c5435e0217045391a45 Mon Sep 17 00:00:00 2001 From: Nik Okuntseff Date: Sat, 27 Jan 2018 18:14:58 +0000 Subject: [PATCH] A fix for issue #47. --- WEB-INF/lib/ttTimeHelper.class.php | 56 ++++++++++++++++++++++++++++-- WEB-INF/templates/footer.tpl | 2 +- dbinstall.php | 9 ++--- mysql.sql | 2 +- plugins/MonthlyQuota.class.php | 17 +++++---- quotas.php | 53 +++++++++++++++++----------- time.php | 2 +- week.php | 2 +- 8 files changed, 105 insertions(+), 38 deletions(-) diff --git a/WEB-INF/lib/ttTimeHelper.class.php b/WEB-INF/lib/ttTimeHelper.class.php index e1440105..fdb5a3a9 100644 --- a/WEB-INF/lib/ttTimeHelper.class.php +++ b/WEB-INF/lib/ttTimeHelper.class.php @@ -82,7 +82,7 @@ class ttTimeHelper { // 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; @@ -102,6 +102,53 @@ class ttTimeHelper { 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; @@ -162,11 +209,14 @@ class ttTimeHelper { // 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; } diff --git a/WEB-INF/templates/footer.tpl b/WEB-INF/templates/footer.tpl index 8c2a6ae1..e6b8c40c 100644 --- a/WEB-INF/templates/footer.tpl +++ b/WEB-INF/templates/footer.tpl @@ -12,7 +12,7 @@
- - - + +
 Anuko Time Tracker 1.17.6.3797 | Copyright © Anuko | +  Anuko Time Tracker 1.17.7.3798 | Copyright © Anuko | {$i18n.footer.credits} | {$i18n.footer.license} | {$i18n.footer.improve} diff --git a/dbinstall.php b/dbinstall.php index 6cf5a801..e8c68848 100755 --- a/dbinstall.php +++ b/dbinstall.php @@ -709,12 +709,13 @@ if ($_POST) { 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"]) { @@ -759,7 +760,7 @@ if ($_POST) {

DB Install

-
Create database structure (v1.17.0) + Create database structure (v1.17.7)
(applies only to new installations, do not execute when updating)
@@ -795,8 +796,8 @@ if ($_POST) {

Update database structure (v1.14 to v1.17)
Update database structure (v1.14 to v1.17.7)
diff --git a/mysql.sql b/mysql.sql index 1e415c14..96e14924 100644 --- a/mysql.sql +++ b/mysql.sql @@ -374,7 +374,7 @@ CREATE TABLE `tt_monthly_quotas` ( `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`) ); diff --git a/plugins/MonthlyQuota.class.php b/plugins/MonthlyQuota.class.php index 8fff596b..13cbd9d4 100644 --- a/plugins/MonthlyQuota.class.php +++ b/plugins/MonthlyQuota.class.php @@ -26,6 +26,8 @@ // | https://www.anuko.com/time_tracker/credits.htm // +----------------------------------------------------------------------+ +import('ttTimeHelper'); + // MontlyQuota class implements handling of work hour quotas. class MonthlyQuota { @@ -41,11 +43,12 @@ 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')); } @@ -63,8 +66,8 @@ class MonthlyQuota { // 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; @@ -82,8 +85,8 @@ class MonthlyQuota { // 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')) { diff --git a/quotas.php b/quotas.php index 2e64e425..0336d44c 100644 --- a/quotas.php +++ b/quotas.php @@ -30,6 +30,7 @@ require_once('initialize.php'); require_once('plugins/MonthlyQuota.class.php'); import('form.Form'); import('ttTeamHelper'); +import('ttTimeHelper'); // Access check. if (!ttAccessCheck(right_manage_team) || !$user->isPluginEnabled('mq')) { @@ -69,27 +70,38 @@ $months = $i18n->monthNames; $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')); } } @@ -103,9 +115,10 @@ for ($i=0; $i < count($months); $i++) { $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); diff --git a/time.php b/time.php index caab2bd7..25cee96c 100644 --- a/time.php +++ b/time.php @@ -68,7 +68,7 @@ if ($user->isPluginEnabled('mq')){ $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); diff --git a/week.php b/week.php index 16fd79ba..b70f20b9 100644 --- a/week.php +++ b/week.php @@ -80,7 +80,7 @@ if ($user->isPluginEnabled('mq')){ $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); -- 2.20.1