$plugins_part = '';
$config_part = '';
$lock_spec_part = '';
- $workday_hours_part = '';
+ $workday_minutes_part = '';
if (isset($fields['currency'])) $currency_part = ', currency = '.$mdb2->quote($fields['currency']);
if (isset($fields['lang'])) $lang_part = ', lang = '.$mdb2->quote($fields['lang']);
if (isset($fields['plugins'])) $plugins_part = ', plugins = '.$mdb2->quote($fields['plugins']);
if (isset($fields['config'])) $config_part = ', config = '.$mdb2->quote($fields['config']);
if (isset($fields['lock_spec'])) $lock_spec_part = ', lock_spec = '.$mdb2->quote($fields['lock_spec']);
- if (isset($fields['workday_hours'])) $workday_hours_part = ', workday_hours = '.$mdb2->quote($fields['workday_hours']);
+ if (isset($fields['workday_minutes'])) $workday_minutes_part = ', workday_minutes = '.$mdb2->quote($fields['workday_minutes']);
$sql = "update tt_teams set $name_part $currency_part $lang_part $decimal_mark_part
$date_format_part $time_format_part $week_start_part $tracking_mode_part $task_required_part $record_type_part
- $uncompleted_indicators_part $bcc_email_part $plugins_part $config_part $lock_spec_part $workday_hours_part where id = $team_id";
+ $uncompleted_indicators_part $bcc_email_part $plugins_part $config_part $lock_spec_part $workday_minutes_part where id = $team_id";
$affected = $mdb2->exec($sql);
if (is_a($affected, 'PEAR_Error')) return false;
return false;
}
+ // postedDurationToMinutes - converts a value representing a duration
+ // (usually enetered in a form by a user) to integer number of minutes.
+ //
+ // At the moment, we have 2 variations of duration types:
+ // 1) A duration within a day, such as in a time entry.
+ // These are less or equal to 24 hours.
+ //
+ // 2) A duration of a monthly quota, with max value of 31*24 hours.
+ //
+ // This function is generic to be used for both types.
+ // Other functions will be used to check for specific max values.
+ //
+ // Returns false if the value cannot be converted.
+ static function postedDurationToMinutes($duration) {
+ // Handle empty value.
+ if (!isset($duration) || strlen($duration) == 0)
+ return null; // Value is not set. Caller decides whether it is valid or not.
+
+ // Handle whole hours.
+ if (preg_match('/^\d{1,3}h?$/', $duration )) { // 0 - 999, 0h - 999h
+ $minutes = 60 * trim($duration, 'h');
+ return $minutes;
+ }
+
+ // Handle a normalized duration value.
+ if (preg_match('/^\d{1,3}:[0-5][0-9]$/', $duration )) { // 0:00 - 999:59
+ $time_array = explode(':', $duration);
+ $minutes = (int)@$time_array[1] + ((int)@$time_array[0]) * 60;
+ return $minutes;
+ }
+
+ // Handle localized fractional hours.
+ global $user;
+ $localizedPattern = '/^(\d{1,3})?['.$user->decimal_mark.'][0-9]{1,4}h?$/';
+ if (preg_match($localizedPattern, $duration )) { // decimal values like .5, 1.25h, ... .. 999.9999h (or with comma)
+ if ($user->decimal_mark == ',')
+ $duration = str_replace (',', '.', $duration);
+
+ $minutes = (int)round(60 * floatval($duration));
+ return $minutes;
+ }
+
+ // Handle minutes. Some users enter durations like 10m (meaning 10 minutes).
+ if (preg_match('/^\d{1,5}m$/', $duration )) { // 0m - 99999m
+ $minutes = (int) trim($duration, 'm');
+ return $minutes;
+ }
+
+ // Everything else is not a valid duration.
+ return false;
+ }
+
+ static function durationToMinutes($duration) {
+ $minutes = ttTimeHelper::postedDurationToMinutes($duration);
+ if (false === $minutes || $minutes > 24*60)
+ return false; // $duration is not valid for a day entry.
+ return $minutes;
+ }
+
+ static function quotaToMinutes($duration) {
+ $minutes = ttTimeHelper::postedDurationToMinutes($duration);
+ if (false === $minutes || $minutes > 31*24*60)
+ return false; // $duration is not valid for a monthly quota.
+ return $minutes;
+ }
+
// validateDuration - a future replacement of the isValidDuration above.
// Validates a passed in $value as a time duration string in hours and / or minutes.
// Returns either a normalized duration (hh:mm) or false if $value is invalid.
var $date_format = null; // Date format.
var $time_format = null; // Time format.
var $week_start = 0; // Week start day.
- var $show_holidays = 1; // Whether to show holidays in calendar.
+ var $show_holidays = 0; // Whether to show holidays in calendar.
var $tracking_mode = 0; // Tracking mode.
var $project_required = 0; // Whether project selection is required on time entires.
var $task_required = 0; // Whether task selection is required on time entires.
var $team = null; // Team name.
var $custom_logo = 0; // Whether to use a custom logo for team.
var $lock_spec = null; // Cron specification for record locking.
- var $workday_hours = 8; // Number of work hours in a regular day.
+ var $workday_minutes = 480; // Number of work minutes in a regular day.
var $rights = 0; // A mask of user rights.
// Constructor.
$sql = "SELECT u.id, u.login, u.name, u.team_id, u.role, u.client_id, u.email, t.name as team_name,
t.currency, t.lang, t.decimal_mark, t.date_format, t.time_format, t.week_start,
t.tracking_mode, t.project_required, t.task_required, t.record_type, t.uncompleted_indicators,
- t.bcc_email, t.plugins, t.config, t.lock_spec, t.workday_hours, t.custom_logo
+ t.bcc_email, t.plugins, t.config, t.lock_spec, t.workday_minutes, t.custom_logo
FROM tt_users u LEFT JOIN tt_teams t ON (u.team_id = t.id) WHERE ";
if ($id)
$sql .= "u.id = $id";
$this->currency = $val['currency'];
$this->plugins = $val['plugins'];
$this->lock_spec = $val['lock_spec'];
- $this->workday_hours = $val['workday_hours'];
+ $this->workday_minutes = $val['workday_minutes'];
$this->custom_logo = $val['custom_logo'];
// Set user config options.
'button.reset_password' => 'Reset password',
'button.send' => 'Invia',
'button.send_by_email' => 'Invia tramite e-mail',
-'button.create_team' => 'Crea team',
+'button.create_team' => 'Crea gruppo',
'button.export' => 'Esporta gruppo',
'button.import' => 'Importa gruppo',
'button.close' => 'Chiudi',
'form.import.success' => 'Importazione eseguita con successo.',
// Teams form. See example at https://timetracker.anuko.com/admin_teams.php (login as admin first).
-'form.teams.hint' => 'Crea un nuovo gruppo creando un account gruppo manager.<br>Puoi anche importare i dati di un team da un file xml esportato da un altro server Anuko Time Tracker (non sono ammessi login duplicati).',
+'form.teams.hint' => 'Crea un nuovo gruppo creando un account gruppo manager.<br>Puoi anche importare i dati di un gruppo da un file xml esportato da un altro server Anuko Time Tracker (non sono ammessi login duplicati).',
// Profile form. See example at https://timetracker.anuko.com/profile_edit.php.
'form.profile.12_hours' => '12 ore',
<br>
<table cellspacing="0" cellpadding="4" width="100%" border="0">
<tr>
- <td align="center"> Anuko Time Tracker 1.17.15.3982 | Copyright © <a href="https://www.anuko.com/lp/tt_3.htm" target="_blank">Anuko</a> |
+ <td align="center"> Anuko Time Tracker 1.17.15.3983 | 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["convert11400to11714"]) {
+ if ($_POST["convert11400to11715"]) {
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_teams` ADD `config` text default NULL AFTER `custom_logo`");
setChange("ALTER TABLE `tt_monthly_quotas` ADD `minutes` int(11) DEFAULT NULL");
setChange("ALTER TABLE `tt_teams` ADD `workday_minutes` smallint(4) DEFAULT '480' AFTER `workday_hours`");
+ setChange("UPDATE `tt_teams` SET `workday_minutes` = 60 * `workday_hours`");
+ setChange("ALTER TABLE `tt_teams` DROP `workday_hours`");
+ setChange("UPDATE `tt_monthly_quotas` SET `minutes` = 60 * `quota`");
+ setChange("ALTER TABLE `tt_monthly_quotas` DROP `quota`");
}
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.14)</b>
+ <td width="80%"><b>Create database structure (v1.17.15)</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.14)</td>
- <td><input type="submit" name="convert11400to11714" value="Update"><br></td>
+ <td>Update database structure (v1.14 to v1.17.15)</td>
+ <td><input type="submit" name="convert11400to11715" 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` decimal(5,2) NOT NULL, # number of work hours in specified month and year
`minutes` int(11) DEFAULT NULL, # quota in minutes in specified month and year
PRIMARY KEY (`team_id`,`year`,`month`)
);
var $db; // Database connection.
var $team_id; // Team id.
- // Old style constructors are DEPRECATED in PHP 7.0, and will be removed in a future version. You should always use __construct() in new code.
function __construct() {
$this->db = getConnection();
global $user;
}
// update - deletes a quota, then inserts a new one.
- public function update($year, $month, $quota) {
+ public function update($year, $month, $minutes) {
$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){
- $float_quota = $this->quotaToFloat($quota);
- $insertSql = "INSERT INTO tt_monthly_quotas (team_id, year, month, quota) values ($team_id, $year, $month, $float_quota)";
+ if ($minutes){
+ $insertSql = "INSERT INTO tt_monthly_quotas (team_id, year, month, minutes) values ($team_id, $year, $month, $minutes)";
$affected = $this->db->exec($insertSql);
return (!is_a($affected, 'PEAR_Error'));
}
// getSingle - obtains a quota for a single month.
private function getSingle($year, $month) {
$team_id = $this->team_id;
- $sql = "SELECT quota FROM tt_monthly_quotas WHERE year = $year AND month = $month AND team_id = $team_id";
+ $sql = "SELECT minutes 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;
$row = $reader->fetchRow();
if ($row)
- return $row['quota'];
+ return $row['minutes'];
// If we did not find a record, return a calculated monthly quota.
$numWorkdays = $this->getNumWorkdays($month, $year);
global $user;
- return $numWorkdays * $user->workday_hours; // TODO: fix a rounding issue for small values like 0:01
- // Possibly with a database field type change (minutes?).
+ return $numWorkdays * $user->workday_minutes;
}
// getMany - returns an array of quotas for a given year for team.
private function getMany($year){
$team_id = $this->team_id;
- $sql = "SELECT month, quota FROM tt_monthly_quotas WHERE year = $year AND team_id = $team_id";
+ $sql = "SELECT month, minutes 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')) {
}
while ($val = $res->fetchRow()) {
- $result[$val['month']] = $val['quota'];
+ $result[$val['month']] = $val['minutes'];
}
return $result;
if ($request->isPost()){
// Validate user input.
- if (!ttTimeHelper::isValidDuration($request->getParameter('workdayHours')))
+ if (false === ttTimeHelper::durationToMinutes($request->getParameter('workdayHours')))
$err->add($i18n->getKey('error.field'), $i18n->getKey('form.quota.workday_hours'));
for ($i = 0; $i < count($months); $i++){
$val = $request->getParameter($months[$i]);
- if (!$quota->isValidQuota($val))
+ if (false === ttTimeHelper::quotaToMinutes($val))
$err->add($i18n->getKey('error.field'), $months[$i]);
}
// Finished validating user input.
if ($err->no()) {
// Handle workday hours.
- $hours = $quota->quotaToFloat($request->getParameter('workdayHours'));
- if ($hours != $user->workday_hours) {
- if (!ttTeamHelper::update($user->team_id, array('name'=>$user->team,'workday_hours'=>$hours)))
+ $workday_minutes = ttTimeHelper::durationToMinutes($request->getParameter('workdayHours'));
+ if ($workday_minutes != $user->workday_minutes) {
+ if (!ttTeamHelper::update($user->team_id, array('name'=>$user->team,'workday_minutes'=>$workday_minutes)))
$err->add($i18n->getKey('error.db'));
}
// Handle monthly quotas for a selected year.
$selectedYear = (int) $request->getParameter('year');
for ($i = 0; $i < count($months); $i++){
- if (!$quota->update($selectedYear, $i+1, $request->getParameter($months[$i])))
+ $quota_in_minutes = ttTimeHelper::quotaToMinutes($request->getParameter($months[$i]));
+ if (!$quota->update($selectedYear, $i+1, $quota_in_minutes))
$err->add($i18n->getKey('error.db'));
}
// Get monthly quotas for the entire year.
$monthsData = $quota->get($selectedYear);
-$workdayHours = ttTimeHelper::toAbsDuration($user->workday_hours * 60, true);
+$workdayHours = ttTimeHelper::toAbsDuration($user->workday_minutes, true);
$form = new Form('monthlyQuotasForm');
$form->addInput(array('type'=>'text', 'name'=>'workdayHours', 'value'=>$workdayHours, 'style'=>'width:60px'));
$value = "";
if (array_key_exists($i+1, $monthsData)){
$value = $monthsData[$i+1];
- $value = ttTimeHelper::toAbsDuration($value * 60, true);
+ $value = ttTimeHelper::toAbsDuration($value, true);
}
$name = $months[$i];
$form->addInput(array('type'=>'text','name'=>$name,'maxlength'=>6,'value'=> $value,'style'=>'width:70px'));
if ($user->isPluginEnabled('mq')){
require_once('plugins/MonthlyQuota.class.php');
$quota = new MonthlyQuota();
- $month_quota = $quota->get($selected_date->mYear, $selected_date->mMonth);
+ $month_quota_minutes = $quota->get($selected_date->mYear, $selected_date->mMonth);
$month_total = ttTimeHelper::getTimeForMonth($user->getActiveUser(), $selected_date);
- $minutes_left = round(60*$month_quota) - ttTimeHelper::toMinutes($month_total);
+ $minutes_left = $month_quota_minutes - ttTimeHelper::toMinutes($month_total);
$smarty->assign('month_total', $month_total);
$smarty->assign('over_quota', $minutes_left < 0);