More work in progress on week view.
authoranuko <support@anuko.com>
Sun, 24 Dec 2017 16:03:25 +0000 (16:03 +0000)
committeranuko <support@anuko.com>
Sun, 24 Dec 2017 16:03:25 +0000 (16:03 +0000)
WEB-INF/lib/ttTimeHelper.class.php
WEB-INF/templates/week.tpl [new file with mode: 0644]
time.php
week.php [new file with mode: 0644]

index 02faa21..ec05537 100644 (file)
@@ -677,4 +677,89 @@ class ttTimeHelper {
     return $result;
   }
 
+  // getRecordsForInterval - returns time records for a user for a given interval of dates.
+  static function getRecordsForInterval($user_id, $start_date, $end_date) {
+    global $user;
+    $sql_time_format = "'%k:%i'"; //  24 hour format.
+    if ('%I:%M %p' == $user->time_format)
+      $sql_time_format = "'%h:%i %p'"; // 12 hour format for MySQL TIME_FORMAT function.
+
+    $result = array();
+    $mdb2 = getConnection();
+
+    $client_field = null;
+    if ($user->isPluginEnabled('cl'))
+      $client_field = ", c.id as client_id, c.name as client";
+
+    $left_joins = " left join tt_projects p on (l.project_id = p.id)".
+      " left join tt_tasks t on (l.task_id = t.id)";
+    if ($user->isPluginEnabled('cl'))
+      $left_joins .= " left join tt_clients c on (l.client_id = c.id)";
+
+    $sql = "select l.id as id, l.date as date, TIME_FORMAT(l.start, $sql_time_format) as start,
+      TIME_FORMAT(sec_to_time(time_to_sec(l.start) + time_to_sec(l.duration)), $sql_time_format) as finish,
+      TIME_FORMAT(l.duration, '%k:%i') as duration, p.id as project_id, p.name as project,
+      t.id as task_id, t.name as task, l.comment, l.billable, l.invoice_id $client_field
+      from tt_log l
+      $left_joins
+      where l.date >= '$start_date' and l.date <= '$end_date' and l.user_id = $user_id and l.status = 1
+      order by p.name, t.name, l.date, l.start, l.id";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        if($val['duration']=='0:00')
+          $val['finish'] = '';
+        $result[] = $val;
+      }
+    } else return false;
+
+    return $result;
+  }
+
+  // getGroupedRecordsForInterval - returns time records for a user for a given interval of dates grouped in an array of dates.
+  // Example: for a week view we want one row representing the same attributes to have 7 values for each day of week.
+  // We identify simlar records by a combination of client, billable, project, task, and custom field values.
+  //
+  // "cl:546,bl:0,pr:23456,ts:27464,cf_1:example text"
+  // The above means client 546, billable, project 23456, task 27464, custom field value: example text.
+  //
+  // "cl:546,bl:1,pr:23456,ts:27464,cf_1:7623"
+  // The above means client 546, not billable, project 23456, task 27464, custom field option value 7623.
+  // This will allow us to extend the feature when more custom fields are added.
+  static function getGroupedRecordsForInterval($user_id, $start_date, $end_date) {
+    // Start by obtaining all records in interval.
+    // Then, iterate through them to build an array.
+    $records = ttTimeHelper::getRecordsForInterval($user_id, $start_date, $end_date);
+    foreach ($records as $record) {
+        $record_identifier = ttTimeHelper::makeRecordIdentifier($record);
+    }
+
+    return null; // Work in progress, not implemented.
+  }
+
+  // makeRecordIdentifier - builds a string identifying a record for a grouped display (such as a week view).
+  // For example:
+  // "cl:546,bl:0,pr:23456,ts:27464,cf_1:example text"
+  // "cl:546,bl:1,pr:23456,ts:27464,cf_1:7623"
+  // See comment for getGroupedRecordsForInterval.
+  static function makeRecordIdentifier($record) {
+    global $user;
+    // Start with client.
+    if ($user->isPluginEnabled('cl')) {
+      $record_identifier = 'cl:';
+      $record_identifier .= $record['client_id'] ? $record['client_id'] : '0';
+    }
+    // Add billable flag.
+    if (!empty($record_identifier)) $record_identifier .= ',';
+    $record_identifier .= 'bl:'.$record['billable'];
+    // Add project.
+    $record_identifier .= ',pr:';
+    $record_identifier .= $record['project_id'] ? $record['project_id'] : '0';
+    // Add task.
+    $record_identifier .= ',ts:';
+    $record_identifier .= $record['task_id'] ? $record['task_id'] : '0';
+    // Add custom field 1. This requires modifying the query to get the data we need.
+
+    return $record_identifier;
+  }
 }
diff --git a/WEB-INF/templates/week.tpl b/WEB-INF/templates/week.tpl
new file mode 100644 (file)
index 0000000..fff4bfa
--- /dev/null
@@ -0,0 +1,99 @@
+{include file="time_script.tpl"}
+
+<style>
+.not_billable td {
+  color: #ff6666;
+}
+</style>
+
+{$forms.timeRecordForm.open}
+<table cellspacing="4" cellpadding="0" border="0">
+{if defined(WEEK_VIEW_DEBUG)}
+  <tr>
+    <td align="center" colspan=2">
+      <a href="time.php?date={$selected_date->toString()}">{$i18n.label.day_view}</a>&nbsp;/&nbsp;<a href="week.php?date={$selected_date->toString()}">{$i18n.label.week_view}</a>
+    </td>
+  </tr>
+{/if}
+  <tr>
+    <td valign="top">
+      <table>
+{if $on_behalf_control}
+        <tr>
+          <td align="right">{$i18n.label.user}:</td>
+          <td>{$forms.timeRecordForm.onBehalfUser.control}</td>
+        </tr>
+{/if}
+      </table>
+    </td>
+    <td valign="top">
+      <table>
+        <tr><td>{$forms.timeRecordForm.date.control}</td></tr>
+      </table>
+    </td>
+  </tr>
+</table>
+
+<table width="720">
+<tr>
+  <td valign="top">
+{if $time_records}
+      <table border="0" cellpadding="3" cellspacing="1" width="100%">
+      <tr>
+  {if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+        <td class="tableHeader">{$i18n.label.project}</td>
+  {/if}
+  {if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+        <td class="tableHeader">{$i18n.label.task}</td>
+  {/if}
+        <td class="tableHeader">{$day_header_0}</td>
+        <td class="tableHeader">{$day_header_1}</td>
+        <td class="tableHeader">{$day_header_2}</td>
+        <td class="tableHeader">{$day_header_3}</td>
+        <td class="tableHeader">{$day_header_4}</td>
+        <td class="tableHeader">{$day_header_5}</td>
+        <td class="tableHeader">{$day_header_6}</td>
+      </tr>
+  {foreach $time_records as $record}
+      <tr bgcolor="{cycle values="#f5f5f5,#ffffff"}" {if !$record.billable} class="not_billable" {/if}>
+    {if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+        <td valign="top">{$record.project|escape}</td>
+    {/if}
+    {if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+        <td valign="top">{$record.task|escape}</td>
+    {/if}
+        <td valign="top">{$record.date}</td>
+        <td align="right" valign="top">{if ($record.duration == '0:00' && $record.start <> '')}<font color="#ff0000">{$i18n.form.time.uncompleted}</font>{else}{$record.duration}{/if}</td>
+      </tr>
+  {/foreach}
+    </table>
+{/if}
+  </td>
+</tr>
+</table>
+<!--
+{if $time_records}
+<table cellpadding="3" cellspacing="1" width="720">
+  <tr>
+    <td align="left">{$i18n.label.week_total}: {$week_total}</td>
+    <td align="right">{$i18n.label.day_total}: {$day_total}</td>
+  </tr>
+  {if $user->isPluginEnabled('mq')}
+  <tr>
+    <td align="left">{$i18n.label.month_total}: {$month_total}</td>
+    {if $over_quota}
+    <td align="right">{$i18n.form.time.over_quota}: <span style="color: green;">{$quota_remaining}</span></td>
+    {else}
+    <td align="right">{$i18n.form.time.remaining_quota}: <span style="color: red;">{$quota_remaining}</span></td>
+    {/if}
+  </tr>
+  {/if}
+</table>
+{/if}
+-->
+<table>
+  <tr>
+    <td align="center" colspan="2">{$forms.timeRecordForm.btn_submit.control}</td>
+  </tr>
+</table>
+{$forms.timeRecordForm.close}
index fe3a8e3..c001f21 100644 (file)
--- a/time.php
+++ b/time.php
@@ -88,7 +88,7 @@ if ($user->isPluginEnabled('iv')) {
   if ($request->isPost()) {
     $cl_billable = $request->getParameter('billable');
     $_SESSION['billable'] = (int) $cl_billable;
-  } else 
+  } else
     if (isset($_SESSION['billable']))
       $cl_billable = $_SESSION['billable'];
 }
@@ -146,7 +146,7 @@ if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->t
   // Dropdown for clients if the clients plugin is enabled.
   if ($user->isPluginEnabled('cl')) {
     $active_clients = ttTeamHelper::getActiveClients($user->team_id, true);
-    // We need an array of assigned project ids to do some trimming. 
+    // We need an array of assigned project ids to do some trimming.
     foreach($project_list as $project)
       $projects_assigned_to_user[] = $project['id'];
 
@@ -196,7 +196,7 @@ if ((TYPE_START_FINISH == $user->record_type) || (TYPE_ALL == $user->record_type
 if ((TYPE_DURATION == $user->record_type) || (TYPE_ALL == $user->record_type))
   $form->addInput(array('type'=>'text','name'=>'duration','value'=>$cl_duration,'onchange'=>"formDisable('duration');"));
 if (!defined('NOTE_INPUT_HEIGHT'))
-       define('NOTE_INPUT_HEIGHT', 40);
+  define('NOTE_INPUT_HEIGHT', 40);
 $form->addInput(array('type'=>'textarea','name'=>'note','style'=>'width: 600px; height:'.NOTE_INPUT_HEIGHT.'px;','value'=>$cl_note));
 $form->addInput(array('type'=>'calendar','name'=>'date','value'=>$cl_date)); // calendar
 if ($user->isPluginEnabled('iv'))
diff --git a/week.php b/week.php
new file mode 100644 (file)
index 0000000..c2e8516
--- /dev/null
+++ b/week.php
@@ -0,0 +1,397 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+require_once('initialize.php');
+import('form.Form');
+import('ttUserHelper');
+import('ttTeamHelper');
+import('ttClientHelper');
+import('ttTimeHelper');
+import('DateAndTime');
+
+// Access check.
+if (!ttAccessCheck(right_data_entry)) {
+  header('Location: access_denied.php');
+  exit();
+}
+
+// TODO: redo this block to use week start date instead.
+// Initialize and store date in session.
+$cl_date = $request->getParameter('date', @$_SESSION['date']);
+$selected_date = new DateAndTime(DB_DATEFORMAT, $cl_date);
+if($selected_date->isError())
+  $selected_date = new DateAndTime(DB_DATEFORMAT);
+if(!$cl_date)
+  $cl_date = $selected_date->toString(DB_DATEFORMAT);
+$_SESSION['date'] = $cl_date;
+
+// Use custom fields plugin if it is enabled.
+if ($user->isPluginEnabled('cf')) {
+  require_once('plugins/CustomFields.class.php');
+  $custom_fields = new CustomFields($user->team_id);
+  $smarty->assign('custom_fields', $custom_fields);
+}
+
+// TODO: how is this plugin supposed to work for week view?
+if ($user->isPluginEnabled('mq')){
+  require_once('plugins/MonthlyQuota.class.php');
+  $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);
+
+  $smarty->assign('month_total', $month_total);
+  $smarty->assign('over_quota', $minutes_left < 0);
+  $smarty->assign('quota_remaining', ttTimeHelper::toAbsDuration($minutes_left));
+}
+
+// Initialize variables.
+$cl_start = trim($request->getParameter('start'));
+$cl_finish = trim($request->getParameter('finish'));
+$cl_duration = trim($request->getParameter('duration'));
+$cl_note = trim($request->getParameter('note'));
+// Custom field.
+$cl_cf_1 = trim($request->getParameter('cf_1', ($request->getMethod()=='POST'? null : @$_SESSION['cf_1'])));
+$_SESSION['cf_1'] = $cl_cf_1;
+$cl_billable = 1;
+if ($user->isPluginEnabled('iv')) {
+  if ($request->isPost()) {
+    $cl_billable = $request->getParameter('billable');
+    $_SESSION['billable'] = (int) $cl_billable;
+  } else
+    if (isset($_SESSION['billable']))
+      $cl_billable = $_SESSION['billable'];
+}
+$on_behalf_id = $request->getParameter('onBehalfUser', (isset($_SESSION['behalf_id'])? $_SESSION['behalf_id'] : $user->id));
+$cl_client = $request->getParameter('client', ($request->getMethod()=='POST'? null : @$_SESSION['client']));
+$_SESSION['client'] = $cl_client;
+$cl_project = $request->getParameter('project', ($request->getMethod()=='POST'? null : @$_SESSION['project']));
+$_SESSION['project'] = $cl_project;
+$cl_task = $request->getParameter('task', ($request->getMethod()=='POST'? null : @$_SESSION['task']));
+$_SESSION['task'] = $cl_task;
+
+// Elements of timeRecordForm.
+$form = new Form('timeRecordForm');
+
+if ($user->canManageTeam()) {
+  $user_list = ttTeamHelper::getActiveUsers(array('putSelfFirst'=>true));
+  if (count($user_list) > 1) {
+    $form->addInput(array('type'=>'combobox',
+      'onchange'=>'this.form.submit();',
+      'name'=>'onBehalfUser',
+      'style'=>'width: 250px;',
+      'value'=>$on_behalf_id,
+      'data'=>$user_list,
+      'datakeys'=>array('id','name')));
+    $smarty->assign('on_behalf_control', 1);
+  }
+}
+
+// Dropdown for clients in MODE_TIME. Use all active clients.
+if (MODE_TIME == $user->tracking_mode && $user->isPluginEnabled('cl')) {
+  $active_clients = ttTeamHelper::getActiveClients($user->team_id, true);
+  $form->addInput(array('type'=>'combobox',
+    'onchange'=>'fillProjectDropdown(this.value);',
+    'name'=>'client',
+    'style'=>'width: 250px;',
+    'value'=>$cl_client,
+    'data'=>$active_clients,
+    'datakeys'=>array('id', 'name'),
+    'empty'=>array(''=>$i18n->getKey('dropdown.select'))));
+  // Note: in other modes the client list is filtered to relevant clients only. See below.
+}
+
+if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) {
+  // Dropdown for projects assigned to user.
+  $project_list = $user->getAssignedProjects();
+  $form->addInput(array('type'=>'combobox',
+    'onchange'=>'fillTaskDropdown(this.value);',
+    'name'=>'project',
+    'style'=>'width: 250px;',
+    'value'=>$cl_project,
+    'data'=>$project_list,
+    'datakeys'=>array('id','name'),
+    'empty'=>array(''=>$i18n->getKey('dropdown.select'))));
+
+  // Dropdown for clients if the clients plugin is enabled.
+  if ($user->isPluginEnabled('cl')) {
+    $active_clients = ttTeamHelper::getActiveClients($user->team_id, true);
+    // We need an array of assigned project ids to do some trimming.
+    foreach($project_list as $project)
+      $projects_assigned_to_user[] = $project['id'];
+
+    // Build a client list out of active clients. Use only clients that are relevant to user.
+    // Also trim their associated project list to only assigned projects (to user).
+    foreach($active_clients as $client) {
+      $projects_assigned_to_client = explode(',', $client['projects']);
+      if (is_array($projects_assigned_to_client) && is_array($projects_assigned_to_user))
+        $intersection = array_intersect($projects_assigned_to_client, $projects_assigned_to_user);
+      if ($intersection) {
+        $client['projects'] = implode(',', $intersection);
+        $client_list[] = $client;
+      }
+    }
+    $form->addInput(array('type'=>'combobox',
+      'onchange'=>'fillProjectDropdown(this.value);',
+      'name'=>'client',
+      'style'=>'width: 250px;',
+      'value'=>$cl_client,
+      'data'=>$client_list,
+      'datakeys'=>array('id', 'name'),
+      'empty'=>array(''=>$i18n->getKey('dropdown.select'))));
+  }
+}
+
+if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) {
+  $task_list = ttTeamHelper::getActiveTasks($user->team_id);
+  $form->addInput(array('type'=>'combobox',
+    'name'=>'task',
+    'style'=>'width: 250px;',
+    'value'=>$cl_task,
+    'data'=>$task_list,
+    'datakeys'=>array('id','name'),
+    'empty'=>array(''=>$i18n->getKey('dropdown.select'))));
+}
+
+// Add other controls.
+if ((TYPE_START_FINISH == $user->record_type) || (TYPE_ALL == $user->record_type)) {
+  $form->addInput(array('type'=>'text','name'=>'start','value'=>$cl_start,'onchange'=>"formDisable('start');"));
+  $form->addInput(array('type'=>'text','name'=>'finish','value'=>$cl_finish,'onchange'=>"formDisable('finish');"));
+  if (!$user->canManageTeam() && defined('READONLY_START_FINISH') && isTrue(READONLY_START_FINISH)) {
+    // Make the start and finish fields read-only.
+    $form->getElement('start')->setEnabled(false);
+    $form->getElement('finish')->setEnabled(false);
+  }
+}
+if ((TYPE_DURATION == $user->record_type) || (TYPE_ALL == $user->record_type))
+  $form->addInput(array('type'=>'text','name'=>'duration','value'=>$cl_duration,'onchange'=>"formDisable('duration');"));
+if (!defined('NOTE_INPUT_HEIGHT'))
+       define('NOTE_INPUT_HEIGHT', 40);
+$form->addInput(array('type'=>'textarea','name'=>'note','style'=>'width: 600px; height:'.NOTE_INPUT_HEIGHT.'px;','value'=>$cl_note));
+$form->addInput(array('type'=>'calendar','name'=>'date','value'=>$cl_date)); // calendar
+if ($user->isPluginEnabled('iv'))
+  $form->addInput(array('type'=>'checkbox','name'=>'billable','value'=>$cl_billable));
+$form->addInput(array('type'=>'hidden','name'=>'browser_today','value'=>'')); // User current date, which gets filled in on btn_submit click.
+$form->addInput(array('type'=>'submit','name'=>'btn_submit','onclick'=>'browser_today.value=get_date()','value'=>$i18n->getKey('button.submit')));
+
+// If we have custom fields - add controls for them.
+if ($custom_fields && $custom_fields->fields[0]) {
+  // Only one custom field is supported at this time.
+  if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT) {
+    $form->addInput(array('type'=>'text','name'=>'cf_1','value'=>$cl_cf_1));
+  } elseif ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN) {
+    $form->addInput(array('type'=>'combobox','name'=>'cf_1',
+      'style'=>'width: 250px;',
+      'value'=>$cl_cf_1,
+      'data'=>$custom_fields->options,
+      'empty'=>array(''=>$i18n->getKey('dropdown.select'))));
+  }
+}
+
+// Submit.
+if ($request->isPost()) {
+  if ($request->getParameter('btn_submit')) {
+
+    // Validate user input.
+    if ($user->isPluginEnabled('cl') && $user->isPluginEnabled('cm') && !$cl_client)
+      $err->add($i18n->getKey('error.client'));
+    if ($custom_fields) {
+      if (!ttValidString($cl_cf_1, !$custom_fields->fields[0]['required'])) $err->add($i18n->getKey('error.field'), $custom_fields->fields[0]['label']);
+    }
+    if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) {
+      if (!$cl_project) $err->add($i18n->getKey('error.project'));
+    }
+    if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode && $user->task_required) {
+      if (!$cl_task) $err->add($i18n->getKey('error.task'));
+    }
+    if (strlen($cl_duration) == 0) {
+      if ($cl_start || $cl_finish) {
+        if (!ttTimeHelper::isValidTime($cl_start))
+          $err->add($i18n->getKey('error.field'), $i18n->getKey('label.start'));
+        if ($cl_finish) {
+          if (!ttTimeHelper::isValidTime($cl_finish))
+            $err->add($i18n->getKey('error.field'), $i18n->getKey('label.finish'));
+          if (!ttTimeHelper::isValidInterval($cl_start, $cl_finish))
+            $err->add($i18n->getKey('error.interval'), $i18n->getKey('label.finish'), $i18n->getKey('label.start'));
+        }
+      } else {
+        if ((TYPE_START_FINISH == $user->record_type) || (TYPE_ALL == $user->record_type)) {
+          $err->add($i18n->getKey('error.empty'), $i18n->getKey('label.start'));
+          $err->add($i18n->getKey('error.empty'), $i18n->getKey('label.finish'));
+        }
+        if ((TYPE_DURATION == $user->record_type) || (TYPE_ALL == $user->record_type))
+          $err->add($i18n->getKey('error.empty'), $i18n->getKey('label.duration'));
+      }
+    } else {
+      if (!ttTimeHelper::isValidDuration($cl_duration))
+        $err->add($i18n->getKey('error.field'), $i18n->getKey('label.duration'));
+    }
+    if (!ttValidString($cl_note, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.note'));
+    // Finished validating user input.
+
+    // Prohibit creating entries in future.
+    if (defined('FUTURE_ENTRIES') && !isTrue(FUTURE_ENTRIES)) {
+      $browser_today = new DateAndTime(DB_DATEFORMAT, $request->getParameter('browser_today', null));
+      if ($selected_date->after($browser_today))
+        $err->add($i18n->getKey('error.future_date'));
+    }
+
+    // Prohibit creating entries in locked range.
+    if ($user->isDateLocked($selected_date))
+      $err->add($i18n->getKey('error.range_locked'));
+
+    // Prohibit creating another uncompleted record.
+    if ($err->no()) {
+      if (($not_completed_rec = ttTimeHelper::getUncompleted($user->getActiveUser())) && (($cl_finish == '') && ($cl_duration == '')))
+        $err->add($i18n->getKey('error.uncompleted_exists')." <a href = 'time_edit.php?id=".$not_completed_rec['id']."'>".$i18n->getKey('error.goto_uncompleted')."</a>");
+    }
+
+    // Prohibit creating an overlapping record.
+    if ($err->no()) {
+      if (ttTimeHelper::overlaps($user->getActiveUser(), $cl_date, $cl_start, $cl_finish))
+        $err->add($i18n->getKey('error.overlap'));
+    }
+
+    // Insert record.
+    if ($err->no()) {
+      $id = ttTimeHelper::insert(array(
+        'date' => $cl_date,
+        'user_id' => $user->getActiveUser(),
+        'client' => $cl_client,
+        'project' => $cl_project,
+        'task' => $cl_task,
+        'start' => $cl_start,
+        'finish' => $cl_finish,
+        'duration' => $cl_duration,
+        'note' => $cl_note,
+        'billable' => $cl_billable));
+
+      // Insert a custom field if we have it.
+      $result = true;
+      if ($id && $custom_fields && $cl_cf_1) {
+        if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT)
+          $result = $custom_fields->insert($id, $custom_fields->fields[0]['id'], null, $cl_cf_1);
+        elseif ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN)
+          $result = $custom_fields->insert($id, $custom_fields->fields[0]['id'], $cl_cf_1, null);
+      }
+      if ($id && $result) {
+        header('Location: time.php');
+        exit();
+      }
+      $err->add($i18n->getKey('error.db'));
+    }
+  } elseif ($request->getParameter('btn_stop')) {
+    // Stop button pressed to finish an uncompleted record.
+    $record_id = $request->getParameter('record_id');
+    $record = ttTimeHelper::getRecord($record_id, $user->getActiveUser());
+    $browser_date = $request->getParameter('browser_date');
+    $browser_time = $request->getParameter('browser_time');
+
+    // Can we complete this record?
+    if ($record['date'] == $browser_date                                // closing today's record
+      && ttTimeHelper::isValidInterval($record['start'], $browser_time) // finish time is greater than start time
+      && !ttTimeHelper::overlaps($user->getActiveUser(), $browser_date, $record['start'], $browser_time)) { // no overlap
+      $res = ttTimeHelper::update(array(
+          'id'=>$record['id'],
+          'date'=>$record['date'],
+          'user_id'=>$user->getActiveUser(),
+          'client'=>$record['client_id'],
+          'project'=>$record['project_id'],
+          'task'=>$record['task_id'],
+          'start'=>$record['start'],
+          'finish'=>$browser_time,
+          'note'=>$record['comment'],
+          'billable'=>$record['billable']));
+      if (!$res)
+        $err->add($i18n->getKey('error.db'));
+    } else {
+      // Cannot complete, redirect for manual edit.
+      header('Location: time_edit.php?id='.$record_id);
+      exit();
+    }
+  }
+  elseif ($request->getParameter('onBehalfUser')) {
+    if($user->canManageTeam()) {
+      unset($_SESSION['behalf_id']);
+      unset($_SESSION['behalf_name']);
+
+      if($on_behalf_id != $user->id) {
+        $_SESSION['behalf_id'] = $on_behalf_id;
+        $_SESSION['behalf_name'] = ttUserHelper::getUserName($on_behalf_id);
+      }
+      header('Location: week.php');
+      exit();
+    }
+  }
+} // isPost
+
+$week_total = ttTimeHelper::getTimeForWeek($user->getActiveUser(), $selected_date);
+
+// Determine selected week start and end dates.
+$weekStartDay = $user->week_start;
+$t_arr = localtime($selected_date->getTimestamp());
+$t_arr[5] = $t_arr[5] + 1900;
+if ($t_arr[6] < $weekStartDay)
+  $startWeekBias = $weekStartDay - 7;
+else
+  $startWeekBias = $weekStartDay;
+$startDate = new DateAndTime();
+$startDate->setTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-$t_arr[6]+$startWeekBias,$t_arr[5]));
+$endDate = new DateAndTime();
+$endDate->setTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-$t_arr[6]+6+$startWeekBias,$t_arr[5]));
+// The above is needed to set date range (timestring) in page title. Consider refactoring, possibly moving into a function.
+
+$smarty->assign('selected_date', $selected_date);
+$smarty->assign('week_total', $week_total);
+$smarty->assign('day_total', ttTimeHelper::getTimeForDay($user->getActiveUser(), $cl_date));
+$smarty->assign('time_records', ttTimeHelper::getGroupedRecordsForInterval($user->getActiveUser(), $startDate->toString(DB_DATEFORMAT), $endDate->toString(DB_DATEFORMAT)));
+$smarty->assign('client_list', $client_list);
+$smarty->assign('project_list', $project_list);
+$smarty->assign('task_list', $task_list);
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('onload', 'onLoad="fillDropdowns()"');
+$smarty->assign('timestring', $startDate->toString($user->date_format).' - '.$endDate->toString($user->date_format));
+
+// Prepare and assign date headers. Note how startDate moves to the end of the week, so it no longer holds correct start week value.
+$smarty->assign('day_header_0', $startDate->toString($user->date_format));
+$startDate->incDay();
+$smarty->assign('day_header_1', $startDate->toString($user->date_format));
+$startDate->incDay();
+$smarty->assign('day_header_2', $startDate->toString($user->date_format));
+$startDate->incDay();
+$smarty->assign('day_header_3', $startDate->toString($user->date_format));
+$startDate->incDay();
+$smarty->assign('day_header_4', $startDate->toString($user->date_format));
+$startDate->incDay();
+$smarty->assign('day_header_5', $startDate->toString($user->date_format));
+$startDate->incDay();
+$smarty->assign('day_header_6', $startDate->toString($user->date_format));
+
+$smarty->assign('title', $i18n->getKey('title.time'));
+$smarty->assign('content_page_name', 'week.tpl');
+$smarty->display('index.tpl');