A fix for issue #47.
authorNik Okuntseff <support@anuko.com>
Sat, 27 Jan 2018 18:14:58 +0000 (18:14 +0000)
committerNik Okuntseff <support@anuko.com>
Sat, 27 Jan 2018 18:14:58 +0000 (18:14 +0000)
WEB-INF/lib/ttTimeHelper.class.php
WEB-INF/templates/footer.tpl
dbinstall.php
mysql.sql
plugins/MonthlyQuota.class.php
quotas.php
time.php
week.php

index e144010..fdb5a3a 100644 (file)
@@ -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;
   }
 
index 8c2a6ae..e6b8c40 100644 (file)
@@ -12,7 +12,7 @@
       <br>
       <table cellspacing="0" cellpadding="4" width="100%" border="0">
         <tr>
-          <td align="center">&nbsp;Anuko Time Tracker 1.17.6.3797 | Copyright &copy; <a href="https://www.anuko.com/lp/tt_3.htm" target="_blank">Anuko</a> |
+          <td align="center">&nbsp;Anuko Time Tracker 1.17.7.3798 | Copyright &copy; <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>
index 6cf5a80..e8c6884 100755 (executable)
@@ -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) {
 <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>
@@ -795,8 +796,8 @@ if ($_POST) {
     <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>
 
index 1e415c1..96e1492 100644 (file)
--- 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`)
 );
 
index 8fff596..13cbd9d 100644 (file)
@@ -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')) {
index 2e64e42..0336d44 100644 (file)
@@ -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);
index caab2bd..25cee96 100644 (file)
--- 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);
index 16fd79b..b70f20b 100644 (file)
--- 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);