Added files in root dir, subdirectories remain...
--- /dev/null
+<?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
+// +----------------------------------------------------------------------+
+
+// Note: This script uses Lichart PHP library and requires GD 2.0.1 or later.
+
+require_once('initialize.php');
+import('form.Form');
+import('DateAndTime');
+import('ttChartHelper');
+import('ttSysConfig');
+import('PieChartEx');
+import('ttUserHelper');
+import('ttTeamHelper');
+
+// Access check.
+if (!ttAccessCheck(right_view_charts)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+// Initialize and store date in session.
+$cl_date = $request->getParameter('date', @$_SESSION['date']);
+if(!$cl_date) {
+ $now = new DateAndTime(DB_DATEFORMAT);
+ $cl_date = $now->toString(DB_DATEFORMAT);
+}
+$_SESSION['date'] = $cl_date;
+
+// Initialize chart interval.
+$cl_interval = $_SESSION['chart_interval'];
+if (!$cl_interval) {
+ $sc = new ttSysConfig($user->id);
+ $cl_interval = $sc->getValue(SYSC_CHART_INTERVAL);
+}
+if (!$cl_interval) $cl_interval = INTERVAL_THIS_MONTH;
+$_SESSION['chart_interval'] = $cl_interval;
+
+// Initialize chart type.
+$cl_type = $_SESSION['chart_type'];
+if (!$cl_type) {
+ $sc = new ttSysConfig($user->id);
+ $cl_type = $sc->getValue(SYSC_CHART_TYPE);
+}
+if (MODE_TIME == $user->tracking_mode) {
+ if (in_array('cl', explode(',', $user->plugins)))
+ $cl_type = CHART_CLIENTS;
+} else {
+ if ($cl_type == CHART_CLIENTS) {
+ if (!in_array('cl', explode(',', $user->plugins)))
+ $cl_type = CHART_PROJECTS;
+ } else if ($cl_type == CHART_TASKS) {
+ if (MODE_PROJECTS_AND_TASKS != $user->tracking_mode)
+ $cl_type = CHART_PROJECTS;
+ }
+}
+if (!$cl_type) $cl_type = CHART_PROJECTS;
+$_SESSION['chart_type'] = $cl_type;
+
+// Who do we draw charts for?
+$on_behalf_id = $request->getParameter('onBehalfUser', (isset($_SESSION['behalf_id'])? $_SESSION['behalf_id'] : $user->id));
+
+if ($request->getMethod( )== 'POST') {
+ // If chart interval changed - save it.
+ $cl_interval = $request->getParameter('interval');
+ if ($cl_interval) {
+ // Save in the session
+ $_SESSION['chart_interval'] = $cl_interval;
+ // and permanently.
+ $sc = new ttSysConfig($user->id);
+ $sc->setValue(SYSC_CHART_INTERVAL, $cl_interval);
+ }
+ // If chart type changed - save it.
+ $cl_type = $request->getParameter('type');
+ if ($cl_type) {
+ // Save in the session
+ $_SESSION['chart_type'] = $cl_type;
+ // and permanently.
+ $sc = new ttSysConfig($user->id);
+ $sc->setValue(SYSC_CHART_TYPE, $cl_type);
+ }
+ // If user has changed - set behalf_id accordingly in the session.
+ if ($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: charts.php');
+ exit();
+ }
+ }
+}
+
+// Elements of chartForm.
+$chart_form = new Form('chartForm');
+
+// User dropdown. Changes the user "on behalf" of whom we are working.
+if ($user->canManageTeam()) {
+ $user_list = ttTeamHelper::getActiveUsers(array('putSelfFirst'=>true));
+ if (count($user_list) > 1) {
+ $chart_form->addInput(array('type'=>'combobox',
+ 'onchange'=>'this.form.submit();',
+ 'name'=>'onBehalfUser',
+ 'value'=>$on_behalf_id,
+ 'data'=>$user_list,
+ 'datakeys'=>array('id','name'),
+ ));
+ $smarty->assign('on_behalf_control', 1);
+ }
+}
+
+// Chart interval options.
+$intervals = array();
+$intervals[INTERVAL_THIS_DAY] = $i18n->getKey('dropdown.this_day');
+$intervals[INTERVAL_THIS_WEEK] = $i18n->getKey('dropdown.this_week');
+$intervals[INTERVAL_THIS_MONTH] = $i18n->getKey('dropdown.this_month');
+$intervals[INTERVAL_THIS_YEAR] = $i18n->getKey('dropdown.this_year');
+$intervals[INTERVAL_ALL_TIME] = $i18n->getKey('dropdown.all_time');
+
+// Chart interval dropdown.
+$chart_form->addInput(array('type' => 'combobox',
+ 'onchange' => 'if(this.form) this.form.submit();',
+ 'name' => 'interval',
+ 'value' => $cl_interval,
+ 'data' => $intervals
+));
+
+// Chart type options.
+$chart_selector = (MODE_PROJECTS_AND_TASKS == $user->tracking_mode
+ || in_array('cl', explode(',', $user->plugins)));
+if ($chart_selector) {
+ $types = array();
+ if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode)
+ $types[CHART_PROJECTS] = $i18n->getKey('dropdown.projects');
+ if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode)
+ $types[CHART_TASKS] = $i18n->getKey('dropdown.tasks');
+ if (in_array('cl', explode(',', $user->plugins)))
+ $types[CHART_CLIENTS] = $i18n->getKey('dropdown.clients');
+
+ // Add chart type dropdown.
+ $chart_form->addInput(array('type' => 'combobox',
+ 'onchange' => 'if(this.form) this.form.submit();',
+ 'name' => 'type',
+ 'value' => $cl_type,
+ 'data' => $types
+ ));
+}
+
+// Calendar.
+$chart_form->addInput(array('type'=>'calendar','name'=>'date','value'=>$cl_date)); // calendar
+
+// Get data for our chart.
+$totals = ttChartHelper::getTotals($on_behalf_id, $cl_type, $cl_date, $cl_interval);
+$smarty->assign('totals', $totals);
+
+// Prepare chart for drawing.
+/*
+ * We use libchart.php library to draw chart images. It can draw chart labels, too (embed in the image).
+ * But quality of such auto-scaled text is not good. Therefore, we only use libchart to draw a pie-chart picture with
+ * auto-calculated percentage markers around it. We print labels (to the side of the picture) ourselves,
+ * using the same colors libchart is using. For labels printout, the $totals array (which is used for picture points)
+ * is also passed to charts.tpl Smarty template.
+ *
+ * To make all of the above possible with only one database call to obtain $totals we have to print the chart image
+ * to a file here (see code below). Once the image is available as a .png file, the charts.tpl can render it.
+ *
+ * PieChartEx class is a little extension to libchart-provided PieChart class. It allows us to print the chart
+ * without title, logo, and labels.
+ */
+$chart = new PieChartEx(300, 300);
+$data_set = new XYDataSet();
+foreach($totals as $total) {
+ $data_set->addPoint(new Point( $total['name'], $total['time']));
+}
+$chart->setDataSet($data_set);
+
+// Prepare a file name.
+$img_dir = TEMPLATE_DIR.'_c/'; // Directory.
+$file_name = uniqid('chart_').'.png'; // Short file name. Unique ID here is to avoid problems with browser caching.
+$img_ref = 'WEB-INF/templates_c/'.$file_name; // Image reference for html.
+$file_name = $img_dir.$file_name; // Full file name.
+
+// Clean up the file system from older images.
+$img_files = glob($img_dir.'chart_*.png');
+foreach($img_files as $file) {
+ // If the create time of file is older than 1 minute, delete it.
+ if (filemtime($file) < (time() - 60)) {
+ unlink($file);
+ }
+}
+
+// Write chart image to file system.
+$chart->renderEx(array('fileName'=>$file_name,'hideLogo'=>true,'hideTitle'=>true,'hideLabel'=>true));
+// At this point libchart usage is complete and we have chart image on disk.
+
+$smarty->assign('img_file_name', $img_ref);
+$smarty->assign('chart_selector', $chart_selector);
+$smarty->assign('forms', array($chart_form->getName() => $chart_form->toArray()));
+$smarty->assign('title', $i18n->getKey('title.charts'));
+$smarty->assign('content_page_name', 'charts.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttClientHelper');
+import('ttTeamHelper');
+
+// Access check.
+if (!ttAccessCheck(right_manage_team)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+$projects = ttTeamHelper::getActiveProjects($user->team_id);
+
+if ($request->getMethod() == 'POST') {
+ $cl_name = trim($request->getParameter('name'));
+ $cl_address = trim($request->getParameter('address'));
+ $cl_tax = $request->getParameter('tax');
+ $cl_projects = $request->getParameter('projects');
+} else {
+ // Do not assign all projects to a new client by default. This should help to reduce clutter.
+ // foreach ($projects as $project_item)
+ // $cl_projects[] = $project_item['id'];
+}
+
+$form = new Form('clientForm');
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','style'=>'width: 350px;','value'=>$cl_name));
+$form->addInput(array('type'=>'textarea','name'=>'address','maxlength'=>'255','style'=>'width: 350px;','cols'=>'55','rows'=>'5','value'=>$cl_address));
+$form->addInput(array('type'=>'floatfield','name'=>'tax','size'=>'10','format'=>'.2','value'=>$cl_tax));
+$form->addInput(array('type'=>'checkboxgroup','name'=>'projects','data'=>$projects,'layout'=>'H','datakeys'=>array('id','name'),'value'=>$cl_projects));
+$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.add')));
+
+if ($request->getMethod() == 'POST') {
+ // Validate user input.
+ if (!ttValidString($cl_name)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.client_name'));
+ if (!ttValidString($cl_address, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.client_address'));
+ if (!ttValidFloat($cl_tax, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.tax'));
+
+ if ($errors->isEmpty()) {
+ if (!ttClientHelper::getClientByName($cl_name)) {
+ if (ttClientHelper::insert(array(
+ 'team_id' => $user->team_id,
+ 'name' => $cl_name,
+ 'address' => $cl_address,
+ 'tax' => $cl_tax,
+ 'projects' => $cl_projects,
+ 'status' => ACTIVE))) {
+ header('Location: clients.php');
+ exit();
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ } else
+ $errors->add($i18n->getKey('error.client_exists'));
+ }
+} // post
+
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('onload', 'onLoad="document.clientForm.name.focus()"');
+$smarty->assign('title', $i18n->getKey('title.add_client'));
+$smarty->assign('content_page_name', 'client_add.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttClientHelper');
+
+// Access check.
+if (!ttAccessCheck(right_manage_team)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+$id = (int)$request->getParameter('id');
+$client = ttClientHelper::getClient($id);
+
+$client_to_delete = $client['name'];
+
+$form = new Form('clientDeleteForm');
+$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$id));
+$form->addInput(array('type'=>'combobox',
+ 'name'=>'delete_client_entries',
+ 'data'=>array('0'=>$i18n->getKey('dropdown.do_not_delete'),'1'=>$i18n->getKey('dropdown.delete')),
+));
+$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete')));
+$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->getKey('button.cancel')));
+
+if ($request->getMethod() == 'POST') {
+ if(ttClientHelper::getClient($id)) {
+ if ($request->getParameter('btn_delete')) {
+ if (ttClientHelper::delete($id, $request->getParameter('delete_client_entries'))) {
+ header('Location: clients.php');
+ exit();
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ }
+ } else
+ $errors->add($i18n->getKey('error.db'));
+
+ if ($request->getParameter('btn_cancel')) {
+ header('Location: clients.php');
+ exit();
+ }
+} // post
+
+$smarty->assign('client_to_delete', $client_to_delete);
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('title', $i18n->getKey('title.delete_client'));
+$smarty->assign('content_page_name', 'client_delete.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttClientHelper');
+import('ttTeamHelper');
+
+// Access check.
+if (!ttAccessCheck(right_manage_team)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+$cl_id = (int) $request->getParameter('id');
+
+$projects = ttTeamHelper::getActiveProjects($user->team_id);
+
+if ($request->getMethod() == 'POST') {
+ $cl_name = trim($request->getParameter('name'));
+ $cl_address = trim($request->getParameter('address'));
+ $cl_tax = trim($request->getParameter('tax'));
+ $cl_status = $request->getParameter('status');
+ $cl_projects = $request->getParameter('projects');
+} else {
+ $client = ttClientHelper::getClient($cl_id, true);
+ $cl_name = $client['name'];
+ $cl_address = $client['address'];
+ $cl_tax = $client['tax'];
+ $cl_status = $client['status'];
+ $assigned_projects = ttClientHelper::getAssignedProjects($cl_id);
+ foreach($assigned_projects as $project_item) {
+ $cl_projects[] = $project_item['id'];
+ }
+}
+
+$form = new Form('clientForm');
+$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_id));
+$form->addInput(array('type'=>'text','name'=>'name','maxlength'=>'100','style'=>'width: 350px;','value'=>$cl_name));
+$form->addInput(array('type'=>'textarea','name'=>'address','maxlength'=>'255','style'=>'width: 350px;','cols'=>'55','rows'=>'5','value'=>$cl_address));
+$form->addInput(array('type'=>'floatfield','name'=>'tax','size'=>'10','format'=>'.2','value'=>$cl_tax));
+$form->addInput(array('type'=>'combobox','name'=>'status','value'=>$cl_status,
+ 'data'=>array(ACTIVE=>$i18n->getKey('dropdown.status_active'),INACTIVE=>$i18n->getKey('dropdown.status_inactive'))));
+$form->addInput(array('type'=>'checkboxgroup','name'=>'projects','data'=>$projects,'datakeys'=>array('id','name'),'layout'=>'H','value'=>$cl_projects));
+$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->getKey('button.save')));
+$form->addInput(array('type'=>'submit','name'=>'btn_copy','value'=>$i18n->getKey('button.copy')));
+
+if ($request->getMethod() == 'POST') {
+ // Validate user input.
+ if (!ttValidString($cl_name)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.client_name'));
+ if (!ttValidString($cl_address, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.client_address'));
+ if (!ttValidFloat($cl_tax, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.tax'));
+
+ if ($errors->isEmpty()) {
+ if ($request->getParameter('btn_save')) {
+ $client = ttClientHelper::getClientByName($cl_name);
+ if (($client && ($cl_id == $client['id'])) || !$client) {
+ if (ttClientHelper::update(array(
+ 'id' => $cl_id,
+ 'name' => $cl_name,
+ 'address' => $cl_address,
+ 'tax' => $cl_tax,
+ 'status' => $cl_status,
+ 'projects' => $cl_projects))) {
+ header('Location: clients.php');
+ exit();
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ } else
+ $errors->add($i18n->getKey('error.client_exists'));
+ }
+
+ if ($request->getParameter('btn_copy')) {
+ if (!ttClientHelper::getClientByName($cl_name)) {
+ if (ttClientHelper::insert(array(
+ 'team_id' => $user->team_id,
+ 'name' => $cl_name,
+ 'address' => $cl_address,
+ 'tax' => $cl_tax,
+ 'status' => $cl_status,
+ 'projects' => $cl_projects))) {
+ header('Location: clients.php');
+ exit();
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ } else
+ $errors->add($i18n->getKey('error.client_exists'));
+ }
+ }
+} // post
+
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('title', $i18n->getKey('title.edit_client'));
+$smarty->assign('content_page_name', 'client_edit.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttTeamHelper');
+
+// Access check.
+if (!ttAccessCheck(right_manage_team)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+$smarty->assign('active_clients', ttTeamHelper::getActiveClients($user->team_id, true));
+$smarty->assign('inactive_clients', ttTeamHelper::getInactiveClients($user->team_id, true));
+$smarty->assign('title', $i18n->getKey('title.clients'));
+$smarty->assign('content_page_name', 'clients.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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
+// +----------------------------------------------------------------------+
+
+/*
+ * cron.php - this file is an entry point to execute scheduled jobs in Time Tracker.
+ * It must be called externally (for example, from the system cron or task scheduler).
+ *
+ * Internally, we store scheduled jobs in tt_cron table in db. The cron_spec field is in cron format.
+ * Along with it, we store last and next timestamps for jobs, we use them as an execute condition.
+ *
+ * Although cron_spec follows 5-field cron specification precisely, actual job timing depends on
+ * how often cron.php is called. For example, an hourly ping will execute jobs no more than once
+ * each hour, even if they are due more often. Configure whatever calls this file accordingly.
+ */
+
+require_once('initialize.php');
+require_once(LIBRARY_DIR.'/tdcron/class.tdcron.php');
+require_once(LIBRARY_DIR.'/tdcron/class.tdcron.entry.php');
+import('ttFavReportHelper');
+import('ttReportHelper');
+
+$mdb2 = getConnection();
+$now = mktime();
+
+$sql = "select * from tt_cron where $now >= next
+ and status = 1 and report_id is not null and email is not null";
+$res = $mdb2->query($sql);
+if (is_a($res, 'PEAR_Error'))
+ exit;
+
+while ($val = $res->fetchRow()) {
+ // We have jobs to execute in user language.
+
+ // Get favorite report details.
+ $report = ttFavReportHelper::getReport($val['report_id']);
+ if (!$report) continue;
+
+ // Recycle global $user and $i18n objects, as user settings and language are specific for each report.
+ $user = new ttUser(null, $report['user_id']);
+ $i18n->load($user->lang);
+
+ // Email report.
+ if (ttReportHelper::sendFavReport($report, $val['email']))
+ echo "Report ".$val['report_id']. " sent to ".$val['email']."<br>";
+ else
+ echo "Error while emailing report...<br>";
+
+ // Calculate next execution time.
+ $next = tdCron::getNextOccurrence($val['cron_spec'], $now);
+
+ // Update last and next values in tt_cron.
+ $sql = "update tt_cron set last = $now, next = $next where id = ".$val['id'];
+ $affected = $mdb2->exec($sql);
+ if (is_a($affected, 'PEAR_Error')) continue;
+}
+
+echo "Done!";
+
+?>
\ No newline at end of file
--- /dev/null
+<?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('WEB-INF/config.php');
+require_once('WEB-INF/lib/common.lib.php');
+if (!require_once('MDB2.php')) {
+ die ("Unable to require MDB2 module. Please check it<br>\n");
+}
+require_once('initialize.php');
+import('ttUserHelper');
+import('ttTaskHelper');
+
+function setChange($sql) {
+ print "<pre>".$sql."</pre>";
+ $mdb2 = getConnection();
+ $affected = $mdb2->exec($sql);
+ if (is_a($affected, 'PEAR_Error')) {
+ print "error: ".$affected->getMessage()."<br>";
+ } else {
+ print "successful update<br>\n";
+ }
+}
+
+
+ if ($_POST) {
+ print "Processing...<br>\n";
+
+ if ($_POST["crstructure"]) {
+ $sqlQuery = join("\n", file("mysql.sql"));
+ $sqlQuery = str_replace("TYPE=MyISAM","",$sqlQuery);
+ $queries = explode(";",$sqlQuery);
+ if (is_array($queries)) {
+ foreach ($queries as $query) {
+ $query = trim($query);
+ if (strlen($query)>0) {
+ setChange($query);
+ }
+ }
+ }
+ }
+
+ if ($_POST["convert5to7"]) {
+ setChange("alter table `activity_log` CHANGE al_comment al_comment BLOB");
+ setChange("CREATE TABLE `sysconfig` (`sysc_id` int(11) unsigned NOT NULL auto_increment,`sysc_name` varchar(32) NOT NULL default '',`sysc_value` varchar(70) default NULL, PRIMARY KEY (`sysc_id`), UNIQUE KEY `sysc_id` (`sysc_id`), UNIQUE KEY `sysc_name` (`sysc_name`))");
+ setChange("alter table `companies` add c_locktime int(4) default -1");
+ setChange("alter table `activity_log` add al_billable tinyint(4) default 0");
+ setChange("alter table `sysconfig` drop INDEX `sysc_name`");
+ setChange("alter table `sysconfig` add sysc_id_u int(4)");
+ setChange("alter table `report_filter_set` add rfs_billable VARCHAR(10)");
+ setChange("ALTER TABLE clients MODIFY clnt_id int(11) NOT NULL AUTO_INCREMENT");
+ setChange("ALTER TABLE `users` ADD `u_show_pie` smallint(2) DEFAULT '1'");
+ setChange("alter table `users` ADD `u_pie_mode` smallint(2) DEFAULT '1'");
+ setChange("alter table users drop `u_aprojects`");
+ }
+
+ if ($_POST["convert7to133"]) {
+ setChange("ALTER TABLE users ADD COLUMN u_lang VARCHAR(20) DEFAULT NULL");
+ setChange("ALTER TABLE users ADD COLUMN u_email VARCHAR(100) DEFAULT NULL");
+ setChange("ALTER TABLE `activity_log` drop `al_proof`");
+ setChange("ALTER TABLE `activity_log` drop `al_charge`");
+ setChange("ALTER TABLE `activities` drop `a_project_id`");
+ setChange("DROP TABLE `activity_status_list`");
+ setChange("DROP TABLE `project_status_list`");
+ setChange("DROP TABLE `user_status_list`");
+ setChange("DROP TABLE `companies_c_id_seq`");
+ setChange("ALTER TABLE projects ADD COLUMN p_activities TEXT");
+ }
+
+ // The update_projects function updates p_activities field in the projects table so that we could
+ // improve performance of the application by using this field instead of activity_bind table.
+ if ($_POST["update_projects"]) {
+ $mdb2 = getConnection();
+ // $sql = "select p_id from projects where p_status = 1 and p_activities is NULL";
+ $sql = "select p_id from projects where p_status = 1";
+ $res = $mdb2->query($sql);
+ if (is_a($res, 'PEAR_Error')) {
+ die($res->getMessage());
+ }
+ // Iterate through projects.
+ while ($val = $res->fetchRow()) {
+ $project_id = $val['p_id'];
+
+ // Get activity binds for project (old way).
+ // $sql = "select ab_id_a from activity_bind where ab_id_p = $project_id";
+ $sql = "select ab_id_a, a_id, a_name from activity_bind
+ inner join activities on (ab_id_a = a_id)
+ where ab_id_p = $project_id
+ order by a_name";
+
+ $result = $mdb2->query($sql);
+ if (is_a($result, 'PEAR_Error')) {
+ die($result->getMessage());
+ }
+ $activity_arr = array();
+ while ($value = $result->fetchRow()) {
+ $activity_arr[] = $value['ab_id_a'];
+ }
+ $a_comma_separated = implode(",", $activity_arr); // This is a comma-separated list of associated activity ids.
+
+ // Re-bind the project to activities (new way).
+ $sql = "update projects set p_activities = ".$mdb2->quote($a_comma_separated)." where p_id = $project_id";
+ $affected = $mdb2->exec($sql);
+ if (is_a($affected, 'PEAR_Error')) {
+ die($affected->getMessage());
+ }
+ }
+ }
+
+ if ($_POST["convert133to1340"]) {
+ setChange("ALTER TABLE companies ADD COLUMN c_show_pie smallint(2) DEFAULT 1");
+ setChange("ALTER TABLE companies ADD COLUMN c_pie_mode smallint(2) DEFAULT 1");
+ setChange("ALTER TABLE companies ADD COLUMN c_lang varchar(20) default NULL");
+ }
+
+ // The update_companies function sets up c_show_pie, c_pie_mode, and c_lang
+ // fields in the companies table from the corresponding manager fields.
+ if ($_POST["update_companies"]) {
+ $mdb2 = getConnection();
+ // Get all active managers.
+ $sql = "select u_company_id, u_show_pie, u_pie_mode, u_lang from users
+ where u_manager_id is NULL and u_login <> 'admin' and u_company_id is not NULL and u_active = 1";
+ $res = $mdb2->query($sql);
+ if (is_a($res, 'PEAR_Error')) {
+ die($res->getMessage());
+ }
+ // Iterate through managers and set fields in the companies table.
+ while ($val = $res->fetchRow()) {
+ $company_id = $val['u_company_id'];
+ $show_pie = $val['u_show_pie'];
+ $pie_mode = $val['u_pie_mode'];
+ $lang = $val['u_lang'];
+
+ $sql = "update companies set
+ c_show_pie = $show_pie, c_pie_mode = $pie_mode, c_lang = ".$mdb2->quote($lang).
+ " where c_id = $company_id";
+
+ $result = $mdb2->query($sql);
+ if (is_a($result, 'PEAR_Error')) {
+ die($result->getMessage());
+ }
+ }
+ }
+
+ if ($_POST["convert1340to1485"]) {
+ setChange("ALTER TABLE users DROP u_show_pie");
+ setChange("ALTER TABLE users DROP u_pie_mode");
+ setChange("ALTER TABLE users DROP u_lang");
+ setChange("ALTER TABLE `users` modify u_login varchar(100) NOT NULL");
+ setChange("ALTER TABLE `users` modify u_active smallint(6) default '1'");
+ setChange("drop index u_login_idx on users");
+ setChange("create unique index u_login_idx on users(u_login, u_active)");
+ setChange("ALTER TABLE companies MODIFY `c_lang` varchar(20) NOT NULL default 'en'");
+ setChange("ALTER TABLE companies ADD COLUMN `c_date_format` varchar(20) NOT NULL default '%Y-%m-%d'");
+ setChange("ALTER TABLE companies ADD COLUMN `c_time_format` varchar(20) NOT NULL default '%H:%M'");
+ setChange("ALTER TABLE companies ADD COLUMN `c_week_start` smallint(2) NOT NULL DEFAULT '0'");
+ setChange("ALTER TABLE clients MODIFY `clnt_status` smallint(6) default '1'");
+ setChange("create unique index clnt_name_idx on clients(clnt_id_um, clnt_name, clnt_status)");
+ setChange("ALTER TABLE projects modify p_status smallint(6) default '1'");
+ setChange("update projects set p_status = NULL where p_status = 1000");
+ setChange("drop index p_manager_idx on projects");
+ setChange("create unique index p_name_idx on projects(p_manager_id, p_name, p_status)");
+ setChange("ALTER TABLE activities modify a_status smallint(6) default '1'");
+ setChange("update activities set a_status = NULL where a_status = 1000");
+ setChange("drop index a_manager_idx on activities");
+ setChange("create unique index a_name_idx on activities(a_manager_id, a_name, a_status)");
+ setChange("RENAME TABLE companies TO teams");
+ setChange("RENAME TABLE teams TO att_teams");
+ setChange("ALTER TABLE att_teams CHANGE c_id id int(11) NOT NULL auto_increment");
+ setChange("RENAME TABLE users TO att_users");
+ setChange("update att_users set u_company_id = 0 where u_company_id is NULL");
+ setChange("ALTER TABLE att_users CHANGE u_company_id team_id int(11) NOT NULL");
+ setChange("RENAME TABLE att_teams TO tt_teams");
+ setChange("RENAME TABLE att_users TO tt_users");
+ setChange("ALTER TABLE tt_teams CHANGE c_name name varchar(80) NOT NULL");
+ setChange("ALTER TABLE `tt_teams` drop `c_www`");
+ setChange("ALTER TABLE `tt_teams` MODIFY `name` varchar(80) default NULL");
+ setChange("ALTER TABLE clients ADD COLUMN `your_name` varchar(255) default NULL");
+ setChange("ALTER TABLE tt_teams ADD COLUMN `address` varchar(255) default NULL");
+ setChange("ALTER TABLE invoice_header ADD COLUMN `client_name` varchar(255) default NULL");
+ setChange("ALTER TABLE invoice_header ADD COLUMN `client_addr` varchar(255) default NULL");
+ setChange("ALTER TABLE report_filter_set ADD COLUMN `rfs_cb_cost` tinyint(4) default '0'");
+ setChange("ALTER TABLE activity_log DROP primary key");
+ setChange("ALTER TABLE activity_log ADD COLUMN `id` bigint NOT NULL auto_increment primary key");
+ setChange("CREATE TABLE `tt_custom_fields` (`id` int(11) NOT NULL auto_increment, `team_id` int(11) NOT NULL, `type` tinyint(4) NOT NULL default '0', `label` varchar(32) NOT NULL default '', PRIMARY KEY (`id`))");
+ setChange("CREATE TABLE `tt_custom_field_options` (`id` int(11) NOT NULL auto_increment, `field_id` int(11) NOT NULL, `value` varchar(32) NOT NULL default '', PRIMARY KEY (`id`))");
+ setChange("CREATE TABLE `tt_custom_field_log` (`id` bigint NOT NULL auto_increment, `al_id` bigint NOT NULL, `field_id` int(11) NOT NULL, `value` varchar(255) default NULL, PRIMARY KEY (`id`))");
+ setChange("ALTER TABLE tt_users DROP u_level");
+ setChange("ALTER TABLE tt_custom_fields ADD COLUMN `status` tinyint(4) default '1'");
+ setChange("ALTER TABLE report_filter_set ADD COLUMN `rfs_cb_cf_1` tinyint(4) default '0'");
+ setChange("ALTER TABLE tt_teams ADD COLUMN `plugins` varchar(255) default NULL");
+ setChange("ALTER TABLE tt_teams MODIFY c_locktime int(4) default '0'");
+ setChange("ALTER TABLE clients DROP your_name");
+ setChange("ALTER TABLE clients DROP clnt_addr_your");
+ setChange("ALTER TABLE `tt_custom_fields` ADD COLUMN `required` tinyint(4) default '0'");
+ setChange("ALTER TABLE tt_teams DROP c_pie_mode");
+ setChange("RENAME TABLE report_filter_set TO tt_fav_reports");
+ setChange("ALTER TABLE tt_fav_reports CHANGE rfs_id id int(11) unsigned NOT NULL auto_increment");
+ setChange("ALTER TABLE tt_fav_reports CHANGE rfs_name name varchar(200) NOT NULL");
+ setChange("ALTER TABLE tt_fav_reports CHANGE rfs_id_u user_id int(11) NOT NULL");
+ setChange("ALTER TABLE tt_fav_reports CHANGE rfs_id_p project_id int(11) default NULL");
+ setChange("ALTER TABLE tt_fav_reports CHANGE rfs_id_a task_id int(11) default NULL");
+ setChange("ALTER TABLE tt_fav_reports CHANGE rfs_users users text default NULL");
+ setChange("ALTER TABLE tt_fav_reports CHANGE rfs_period period tinyint(4) default NULL");
+ setChange("ALTER TABLE tt_fav_reports CHANGE rfs_period_start period_start date default NULL");
+ setChange("ALTER TABLE tt_fav_reports CHANGE rfs_period_finish period_end date default NULL");
+ setChange("ALTER TABLE tt_fav_reports CHANGE rfs_cb_project show_project tinyint(4) NOT NULL default '0'");
+ setChange("ALTER TABLE tt_fav_reports CHANGE rfs_cb_activity show_task tinyint(4) NOT NULL default '0'");
+ setChange("ALTER TABLE tt_fav_reports CHANGE rfs_cb_note show_note tinyint(4) NOT NULL default '0'");
+ setChange("ALTER TABLE tt_fav_reports CHANGE rfs_cb_start show_start tinyint(4) NOT NULL default '0'");
+ setChange("ALTER TABLE tt_fav_reports CHANGE rfs_cb_finish show_end tinyint(4) NOT NULL default '0'");
+ setChange("ALTER TABLE tt_fav_reports CHANGE rfs_cb_duration show_duration tinyint(4) NOT NULL default '0'");
+ setChange("ALTER TABLE tt_fav_reports CHANGE rfs_cb_cost show_cost tinyint(4) NOT NULL default '0'");
+ setChange("ALTER TABLE tt_fav_reports CHANGE rfs_cb_cf_1 show_custom_field_1 tinyint(4) NOT NULL default '0'");
+ setChange("ALTER TABLE tt_fav_reports CHANGE rfs_cb_idle show_empty_days tinyint(4) NOT NULL default '0'");
+ setChange("ALTER TABLE tt_fav_reports CHANGE rfs_cb_totals_only show_totals_only tinyint(4) NOT NULL default '0'");
+ setChange("ALTER TABLE tt_fav_reports CHANGE rfs_groupby group_by varchar(20) default NULL");
+ setChange("ALTER TABLE tt_fav_reports CHANGE rfs_billable billable tinyint(4) default NULL");
+ setChange("ALTER TABLE projects CHANGE p_activities tasks text default NULL");
+ setChange("ALTER TABLE tt_teams CHANGE c_currency currency varchar(7) default NULL");
+ setChange("ALTER TABLE tt_teams CHANGE c_locktime locktime int(4) default '0'");
+ setChange("ALTER TABLE tt_teams CHANGE c_show_pie show_pie smallint(2) DEFAULT '1'");
+ setChange("ALTER TABLE tt_teams CHANGE c_lang lang varchar(10) NOT NULL default 'en'");
+ setChange("ALTER TABLE tt_teams CHANGE c_date_format date_format varchar(20) NOT NULL default '%Y-%m-%d'");
+ setChange("ALTER TABLE tt_teams CHANGE c_time_format time_format varchar(20) NOT NULL default '%H:%M'");
+ setChange("ALTER TABLE tt_teams CHANGE c_week_start week_start smallint(2) NOT NULL DEFAULT '0'");
+ setChange("ALTER TABLE tt_users CHANGE u_id id int(11) NOT NULL auto_increment");
+ setChange("ALTER TABLE tt_users CHANGE u_timestamp timestamp timestamp NOT NULL");
+ setChange("ALTER TABLE tt_users CHANGE u_login login varchar(50) NOT NULL");
+ setChange("drop index u_login_idx on tt_users");
+ setChange("create unique index login_idx on tt_users(login, u_active)");
+ setChange("ALTER TABLE tt_users CHANGE u_password password varchar(50) default NULL");
+ setChange("ALTER TABLE tt_users CHANGE u_name name varchar(100) default NULL");
+ setChange("ALTER TABLE tt_users CHANGE u_email email varchar(100) default NULL");
+ setChange("ALTER TABLE tt_users CHANGE u_rate rate float(6,2) NOT NULL default '0.00'");
+ setChange("update tt_users set u_active = NULL where u_active = 1000");
+ setChange("ALTER TABLE tt_users CHANGE u_active status tinyint(4) default '1'");
+ setChange("ALTER TABLE tt_teams ADD COLUMN status tinyint(4) default '1'");
+ setChange("ALTER TABLE tt_users ADD COLUMN role int(11) default '4'");
+ setChange("update tt_users set role = 1024 where login = 'admin'");
+ setChange("update tt_users set role = 68 where u_comanager = 1");
+ setChange("update tt_users set role = 324 where u_manager_id is null and login != 'admin'");
+ setChange("ALTER TABLE user_bind CHANGE ub_checked status tinyint(4) default '1'");
+ setChange("ALTER TABLE activities ADD COLUMN team_id int(11) NOT NULL");
+ setChange("ALTER TABLE clients ADD COLUMN team_id int(11) NOT NULL");
+ setChange("ALTER TABLE projects ADD COLUMN team_id int(11) NOT NULL");
+ }
+
+ // The update_to_team_id function sets team_id field projects, activities, and clients tables.
+ if ($_POST["update_to_team_id"]) {
+ $mdb2 = getConnection();
+
+ // Update projects.
+ $sql = "select p_id, p_manager_id from projects where team_id = 0 limit 1000";
+ $res = $mdb2->query($sql);
+ if (is_a($res, 'PEAR_Error')) {
+ die($res->getMessage());
+ }
+ // Iterate through projects.
+ $projects_updated = 0;
+ while ($val = $res->fetchRow()) {
+ $project_id = $val['p_id'];
+ $manager_id = $val['p_manager_id'];
+
+ $sql = "select team_id from tt_users where id = $manager_id";
+ $res2 = $mdb2->query($sql);
+ if (is_a($res2, 'PEAR_Error')) {
+ die($res2->getMessage());
+ }
+ $val2 = $res2->fetchRow();
+ $team_id = $val2['team_id'];
+
+ if ($team_id) {
+ $sql = "update projects set team_id = $team_id where p_id = $project_id";
+ $affected = $mdb2->exec($sql);
+ if (is_a($affected, 'PEAR_Error')) {
+ die($affected->getMessage());
+ }
+ $projects_updated += $affected;
+ }
+ }
+ print "Updated $projects_updated projects...<br>\n";
+
+ // Update tasks.
+ $sql = "select a_id, a_manager_id from activities where team_id = 0 limit 1000";
+ $res = $mdb2->query($sql);
+ if (is_a($res, 'PEAR_Error')) {
+ die($res->getMessage());
+ }
+ // Iterate through tasks.
+ $tasks_updated = 0;
+ while ($val = $res->fetchRow()) {
+ $task_id = $val['a_id'];
+ $manager_id = $val['a_manager_id'];
+
+ $sql = "select team_id from tt_users where id = $manager_id";
+ $res2 = $mdb2->query($sql);
+ if (is_a($res2, 'PEAR_Error')) {
+ die($res2->getMessage());
+ }
+ $val2 = $res2->fetchRow();
+ $team_id = $val2['team_id'];
+
+ if ($team_id) {
+ $sql = "update activities set team_id = $team_id where a_id = $task_id";
+ $affected = $mdb2->exec($sql);
+ if (is_a($affected, 'PEAR_Error')) {
+ die($affected->getMessage());
+ }
+ $tasks_updated += $affected;
+ }
+ }
+ print "Updated $tasks_updated tasks...<br>\n";
+
+ // Update clients.
+ $sql = "select clnt_id, clnt_id_um from clients where team_id = 0 limit 1000";
+ $res = $mdb2->query($sql);
+ if (is_a($res, 'PEAR_Error')) {
+ die($res->getMessage());
+ }
+ // Iterate through clients.
+ $clients_updated = 0;
+ while ($val = $res->fetchRow()) {
+ $client_id = $val['clnt_id'];
+ $manager_id = $val['clnt_id_um'];
+
+ $sql = "select team_id from tt_users where id = $manager_id";
+ $res2 = $mdb2->query($sql);
+ if (is_a($res2, 'PEAR_Error')) {
+ die($res2->getMessage());
+ }
+ $val2 = $res2->fetchRow();
+ $team_id = $val2['team_id'];
+
+ if ($team_id) {
+ $sql = "update clients set team_id = $team_id where clnt_id = $client_id";
+ $affected = $mdb2->exec($sql);
+ if (is_a($affected, 'PEAR_Error')) {
+ die($affected->getMessage());
+ }
+ $clients_updated += $affected;
+ }
+ }
+ print "Updated $clients_updated clients...<br>\n";
+ }
+
+ if ($_POST["convert1485to1579"]) {
+ setChange("ALTER TABLE tt_fav_reports MODIFY id int(11) NOT NULL auto_increment");
+ setChange("RENAME TABLE clients TO tt_clients");
+ setChange("ALTER TABLE tt_clients CHANGE clnt_id id int(11) NOT NULL AUTO_INCREMENT");
+ setChange("ALTER TABLE tt_clients CHANGE clnt_status status tinyint(4) default '1'");
+ setChange("ALTER TABLE tt_clients DROP clnt_id_um");
+ setChange("ALTER TABLE tt_clients CHANGE clnt_name name varchar(80) NOT NULL");
+ setChange("drop index clnt_name_idx on tt_clients");
+ setChange("drop index client_name_idx on tt_clients");
+ setChange("create unique index client_name_idx on tt_clients(team_id, name, status)");
+ setChange("ALTER TABLE tt_teams ADD COLUMN `timestamp` timestamp NOT NULL");
+ setChange("ALTER TABLE tt_clients CHANGE clnt_addr_cust address varchar(255) default NULL");
+ setChange("ALTER TABLE tt_clients DROP clnt_discount");
+ setChange("ALTER TABLE tt_clients DROP clnt_comment");
+ setChange("ALTER TABLE tt_clients DROP clnt_fsubtotals");
+ setChange("ALTER TABLE tt_clients CHANGE clnt_tax tax float(6,2) NOT NULL default '0.00'");
+ setChange("ALTER TABLE activity_log ADD COLUMN client_id int(11) default NULL");
+ setChange("ALTER TABLE tt_teams DROP show_pie");
+ setChange("ALTER TABLE tt_fav_reports CHANGE group_by sort_by varchar(20) default 'date'");
+ setChange("RENAME TABLE tmp_refs TO tt_tmp_refs");
+ setChange("ALTER TABLE tt_tmp_refs CHANGE tr_created timestamp timestamp NOT NULL");
+ setChange("ALTER TABLE tt_tmp_refs CHANGE tr_code ref char(32) NOT NULL default ''");
+ setChange("ALTER TABLE tt_tmp_refs CHANGE tr_userid user_id int(11) NOT NULL");
+ setChange("RENAME TABLE projects TO tt_projects");
+ setChange("ALTER TABLE tt_projects CHANGE p_id id int(11) NOT NULL auto_increment");
+ setChange("ALTER TABLE tt_projects DROP p_timestamp");
+ setChange("ALTER TABLE tt_projects CHANGE p_name name varchar(80) NOT NULL");
+ setChange("ALTER TABLE tt_projects CHANGE p_status status tinyint(4) default '1'");
+ setChange("drop index p_name_idx on tt_projects");
+ setChange("create unique index project_idx on tt_projects(team_id, name, status)");
+ setChange("RENAME TABLE activities TO tt_tasks");
+ setChange("ALTER TABLE tt_tasks CHANGE a_id id int(11) NOT NULL auto_increment");
+ setChange("ALTER TABLE tt_tasks DROP a_timestamp");
+ setChange("ALTER TABLE tt_tasks CHANGE a_name name varchar(80) NOT NULL");
+ setChange("ALTER TABLE tt_tasks CHANGE a_status status tinyint(4) default '1'");
+ setChange("drop index a_name_idx on tt_tasks");
+ setChange("create unique index task_idx on tt_tasks(team_id, name, status)");
+ setChange("RENAME TABLE invoice_header TO tt_invoice_headers");
+ setChange("ALTER TABLE tt_invoice_headers CHANGE ih_user_id user_id int(11) NOT NULL");
+ setChange("ALTER TABLE tt_invoice_headers CHANGE ih_number number varchar(20) default NULL");
+ setChange("ALTER TABLE tt_invoice_headers DROP ih_addr_your");
+ setChange("ALTER TABLE tt_invoice_headers DROP ih_addr_cust");
+ setChange("ALTER TABLE tt_invoice_headers CHANGE ih_comment comment varchar(255) default NULL");
+ setChange("ALTER TABLE tt_invoice_headers CHANGE ih_tax tax float(6,2) default '0.00'");
+ setChange("ALTER TABLE tt_invoice_headers CHANGE ih_discount discount float(6,2) default '0.00'");
+ setChange("ALTER TABLE tt_invoice_headers CHANGE ih_fsubtotals subtotals tinyint(4) NOT NULL default '0'");
+ setChange("ALTER TABLE tt_users DROP u_comanager");
+ setChange("ALTER TABLE tt_tasks DROP a_manager_id");
+ setChange("ALTER TABLE tt_projects DROP p_manager_id");
+ setChange("ALTER TABLE tt_users DROP u_manager_id");
+ setChange("ALTER TABLE activity_bind DROP ab_id");
+ setChange("RENAME TABLE activity_bind TO tt_project_task_binds");
+ setChange("ALTER TABLE tt_project_task_binds CHANGE ab_id_p project_id int(11) NOT NULL");
+ setChange("ALTER TABLE tt_project_task_binds CHANGE ab_id_a task_id int(11) NOT NULL");
+ setChange("RENAME TABLE user_bind TO tt_user_project_binds");
+ setChange("ALTER TABLE tt_user_project_binds CHANGE ub_rate rate float(6,2) NOT NULL default '0.00'");
+ setChange("ALTER TABLE tt_user_project_binds CHANGE ub_id_p project_id int(11) NOT NULL");
+ setChange("ALTER TABLE tt_user_project_binds CHANGE ub_id_u user_id int(11) NOT NULL");
+ setChange("ALTER TABLE tt_user_project_binds CHANGE ub_id id int(11) NOT NULL auto_increment");
+ setChange("CREATE TABLE `tt_client_project_binds` (`client_id` int(11) NOT NULL, `project_id` int(11) NOT NULL)");
+ setChange("ALTER TABLE tt_user_project_binds MODIFY rate float(6,2) default '0.00'");
+ setChange("ALTER TABLE tt_clients MODIFY tax float(6,2) default '0.00'");
+ setChange("RENAME TABLE activity_log TO tt_log");
+ setChange("ALTER TABLE tt_log CHANGE al_timestamp timestamp timestamp NOT NULL");
+ setChange("ALTER TABLE tt_log CHANGE al_user_id user_id int(11) NOT NULL");
+ setChange("ALTER TABLE tt_log CHANGE al_date date date NOT NULL");
+ setChange("drop index al_date_idx on tt_log");
+ setChange("create index date_idx on tt_log(date)");
+ setChange("ALTER TABLE tt_log CHANGE al_from start time default NULL");
+ setChange("ALTER TABLE tt_log CHANGE al_duration duration time default NULL");
+ setChange("ALTER TABLE tt_log CHANGE al_project_id project_id int(11) NOT NULL");
+ setChange("ALTER TABLE tt_log MODIFY project_id int(11) default NULL");
+ setChange("ALTER TABLE tt_log CHANGE al_activity_id task_id int(11) default NULL");
+ setChange("ALTER TABLE tt_log CHANGE al_comment comment blob");
+ setChange("ALTER TABLE tt_log CHANGE al_billable billable tinyint(4) default '0'");
+ setChange("drop index al_user_id_idx on tt_log");
+ setChange("drop index al_project_id_idx on tt_log");
+ setChange("drop index al_activity_id_idx on tt_log");
+ setChange("create index user_idx on tt_log(user_id)");
+ setChange("create index project_idx on tt_log(project_id)");
+ setChange("create index task_idx on tt_log(task_id)");
+ setChange("ALTER TABLE tt_custom_field_log CHANGE al_id log_id bigint NOT NULL");
+ setChange("RENAME TABLE sysconfig TO tt_config");
+ setChange("ALTER TABLE tt_config DROP sysc_id");
+ setChange("ALTER TABLE tt_config CHANGE sysc_id_u user_id int(11) NOT NULL");
+ setChange("ALTER TABLE tt_config CHANGE sysc_name param_name varchar(32) NOT NULL");
+ setChange("ALTER TABLE tt_config CHANGE sysc_value param_value varchar(80) default NULL");
+ setChange("create unique index param_idx on tt_config(user_id, param_name)");
+ setChange("ALTER TABLE tt_log ADD COLUMN invoice_id int(11) default NULL");
+ setChange("ALTER TABLE tt_projects ADD COLUMN description varchar(255) default NULL");
+ setChange("CREATE TABLE `tt_invoices` (`id` int(11) NOT NULL auto_increment, `team_id` int(11) NOT NULL, `number` varchar(20) default NULL, `client_name` varchar(255) default NULL, `client_addr` varchar(255) default NULL, `comment` varchar(255) default NULL, `tax` float(6,2) default '0.00', `discount` float(6,2) default '0.00', PRIMARY KEY (`id`))");
+ setChange("ALTER TABLE tt_invoices drop number");
+ setChange("ALTER TABLE tt_invoices drop client_name");
+ setChange("ALTER TABLE tt_invoices drop client_addr");
+ setChange("ALTER TABLE tt_invoices drop comment");
+ setChange("ALTER TABLE tt_invoices drop tax");
+ setChange("ALTER TABLE tt_invoices ADD COLUMN name varchar(80) NOT NULL");
+ setChange("ALTER TABLE tt_invoices ADD COLUMN client_id int(11) NOT NULL");
+ setChange("ALTER TABLE tt_invoices ADD COLUMN start_date date NOT NULL");
+ setChange("ALTER TABLE tt_invoices ADD COLUMN end_date date NOT NULL");
+ setChange("create unique index name_idx on tt_invoices(team_id, name)");
+ setChange("drop index ub_id_u on tt_user_project_binds");
+ setChange("create unique index bind_idx on tt_user_project_binds(user_id, project_id)");
+ setChange("create index client_idx on tt_log(client_id)");
+ setChange("create index invoice_idx on tt_log(invoice_id)");
+ }
+
+ if ($_POST["convert1579to1600"]) {
+ setChange("ALTER TABLE tt_invoices ADD COLUMN date date NOT NULL");
+ setChange("ALTER TABLE tt_teams ADD COLUMN custom_logo tinyint(4) default '0'");
+ setChange("ALTER TABLE tt_tasks ADD COLUMN description varchar(255) default NULL");
+ setChange("ALTER TABLE tt_projects MODIFY name varchar(80) COLLATE utf8_bin NOT NULL");
+ setChange("ALTER TABLE tt_users MODIFY login varchar(50) COLLATE utf8_bin NOT NULL");
+ setChange("ALTER TABLE tt_tasks MODIFY name varchar(80) COLLATE utf8_bin NOT NULL");
+ setChange("ALTER TABLE tt_invoices MODIFY name varchar(80) COLLATE utf8_bin NOT NULL");
+ setChange("ALTER TABLE tt_clients MODIFY name varchar(80) COLLATE utf8_bin NOT NULL");
+ setChange("ALTER TABLE tt_clients ADD COLUMN projects text default NULL");
+ setChange("ALTER TABLE tt_custom_field_log ADD COLUMN option_id int(11) default NULL");
+ setChange("ALTER TABLE tt_teams ADD COLUMN tracking_mode smallint(2) NOT NULL DEFAULT '2'");
+ setChange("ALTER TABLE tt_teams ADD COLUMN record_type smallint(2) NOT NULL DEFAULT '0'");
+ setChange("ALTER TABLE tt_invoices DROP start_date");
+ setChange("ALTER TABLE tt_invoices DROP end_date");
+ }
+
+ if ($_POST["convert1600to1900"]) {
+ setChange("DROP TABLE IF EXISTS tt_invoice_headers");
+ setChange("ALTER TABLE tt_fav_reports ADD COLUMN `client_id` int(11) default NULL");
+ setChange("ALTER TABLE tt_fav_reports ADD COLUMN `cf_1_option_id` int(11) default NULL");
+ setChange("ALTER TABLE tt_fav_reports ADD COLUMN `show_client` tinyint(4) NOT NULL default '0'");
+ setChange("ALTER TABLE tt_fav_reports ADD COLUMN `show_invoice` tinyint(4) NOT NULL default '0'");
+ setChange("ALTER TABLE tt_fav_reports ADD COLUMN `group_by` varchar(20) default NULL");
+ setChange("CREATE TABLE `tt_expense_items` (`id` bigint NOT NULL auto_increment, `date` date NOT NULL, `user_id` int(11) NOT NULL, `client_id` int(11) default NULL, `project_id` int(11) default NULL, `name` varchar(255) NOT NULL, `cost` decimal(10,2) default '0.00', `invoice_id` int(11) default NULL, PRIMARY KEY (`id`))");
+ setChange("create index date_idx on tt_expense_items(date)");
+ setChange("create index user_idx on tt_expense_items(user_id)");
+ setChange("create index client_idx on tt_expense_items(client_id)");
+ setChange("create index project_idx on tt_expense_items(project_id)");
+ setChange("create index invoice_idx on tt_expense_items(invoice_id)");
+ setChange("ALTER TABLE tt_fav_reports DROP sort_by");
+ setChange("ALTER TABLE tt_fav_reports DROP show_empty_days");
+ setChange("ALTER TABLE tt_invoices DROP discount");
+ setChange("ALTER TABLE tt_users ADD COLUMN `client_id` int(11) default NULL");
+ setChange("ALTER TABLE tt_teams ADD COLUMN `decimal_mark` char(1) NOT NULL default '.'");
+ setChange("ALTER TABLE tt_fav_reports ADD COLUMN `invoice` tinyint(4) default NULL");
+ setChange("CREATE TABLE `tt_cron` (`id` int(11) NOT NULL auto_increment, `cron_spec` varchar(255) NOT NULL, `last` int(11) default NULL, `next` int(11) default NULL, `report_id` int(11) default NULL, `email` varchar(100) default NULL, `status` tinyint(4) default '1', PRIMARY KEY (`id`))");
+ setChange("ALTER TABLE tt_cron ADD COLUMN `team_id` int(11) NOT NULL");
+ setChange("create index client_idx on tt_client_project_binds(client_id)");
+ setChange("create index project_idx on tt_client_project_binds(project_id)");
+ setChange("ALTER TABLE tt_log ADD COLUMN status tinyint(4) default '1'");
+ setChange("ALTER TABLE tt_custom_field_log ADD COLUMN status tinyint(4) default '1'");
+ setChange("ALTER TABLE tt_expense_items ADD COLUMN status tinyint(4) default '1'");
+ setChange("ALTER TABLE tt_invoices ADD COLUMN status tinyint(4) default '1'");
+ setChange("DROP INDEX name_idx on tt_invoices");
+ setChange("create unique index name_idx on tt_invoices(team_id, name, status)");
+ }
+
+ // The update_clients function updates projects field in tt_clients table.
+ if ($_POST["update_clients"]) {
+ $mdb2 = getConnection();
+ $sql = "select id from tt_clients where status = 1 or status = 0";
+ $res = $mdb2->query($sql);
+ if (is_a($res, 'PEAR_Error')) {
+ die($res->getMessage());
+ }
+
+ $clients_updated = 0;
+ // Iterate through clients.
+ while ($val = $res->fetchRow()) {
+ $client_id = $val['id'];
+
+ // Get projects binds for client.
+ $sql = "select cpb.project_id from tt_client_project_binds cpb
+ left join tt_projects p on (p.id = cpb.project_id)
+ where cpb.client_id = $client_id order by p.name";
+
+ $result = $mdb2->query($sql);
+ if (is_a($result, 'PEAR_Error'))
+ die($result->getMessage());
+
+ $project_arr = array();
+ while ($value = $result->fetchRow()) {
+ $project_arr[] = $value['project_id'];
+ }
+ $comma_separated = implode(',', $project_arr); // This is a comma-separated list of associated project ids.
+
+ // Update the projects field.
+ $sql = "update tt_clients set projects = ".$mdb2->quote($comma_separated)." where id = $client_id";
+ $affected = $mdb2->exec($sql);
+ if (is_a($affected, 'PEAR_Error'))
+ die($affected->getMessage());
+ $clients_updated += $affected;
+ }
+ print "Updated $clients_updated clients...<br>\n";
+ }
+
+ // The update_custom_fields function updates option_id field field in tt_custom_field_log table.
+ if ($_POST['update_custom_fields']) {
+ $mdb2 = getConnection();
+ $sql = "update tt_custom_field_log set option_id = value where field_id in (select id from tt_custom_fields where type = 2)";
+ $affected = $mdb2->exec($sql);
+ if (is_a($affected, 'PEAR_Error'))
+ die($affected->getMessage());
+
+ print "Updated $affected custom fields...<br>\n";
+ }
+
+ // The update_tracking_mode function sets the tracking_mode field in tt_teams table to 2 (== MODE_PROJECTS_AND_TASKS).
+ if ($_POST['update_tracking_mode']) {
+ $mdb2 = getConnection();
+ $sql = "update tt_teams set tracking_mode = 2 where tracking_mode = 0";
+ $affected = $mdb2->exec($sql);
+ if (is_a($affected, 'PEAR_Error'))
+ die($affected->getMessage());
+
+ print "Updated $affected teams...<br>\n";
+ }
+
+ if ($_POST["cleanup"]) {
+
+ $mdb2 = getConnection();
+ $inactive_teams = ttTeamHelper::getInactiveTeams();
+
+ $count = count($inactive_teams);
+ print "$count inactive teams found...<br>\n";
+ for ($i = 0; $i < $count; $i++) {
+ print " deleting team ".$inactive_teams[$i]."<br>\n";
+ $res = ttTeamHelper::delete($inactive_teams[$i]);
+ }
+
+ setChange("OPTIMIZE TABLE tt_client_project_binds");
+ setChange("OPTIMIZE TABLE tt_clients");
+ setChange("OPTIMIZE TABLE tt_config");
+ setChange("OPTIMIZE TABLE tt_custom_field_log");
+ setChange("OPTIMIZE TABLE tt_custom_field_options");
+ setChange("OPTIMIZE TABLE tt_custom_fields");
+ setChange("OPTIMIZE TABLE tt_expense_items");
+ setChange("OPTIMIZE TABLE tt_fav_reports");
+ setChange("OPTIMIZE TABLE tt_invoices");
+ setChange("OPTIMIZE TABLE tt_log");
+ setChange("OPTIMIZE TABLE tt_project_task_binds");
+ setChange("OPTIMIZE TABLE tt_projects");
+ setChange("OPTIMIZE TABLE tt_tasks");
+ setChange("OPTIMIZE TABLE tt_teams");
+ setChange("OPTIMIZE TABLE tt_tmp_refs");
+ setChange("OPTIMIZE TABLE tt_user_project_binds");
+ setChange("OPTIMIZE TABLE tt_users");
+ }
+
+ print "done.<br>\n";
+}
+?>
+<html>
+<body>
+<div align="center">
+<form method="POST">
+<h2>DB Install</h2>
+<table width="80%" border="1" cellpadding="10" cellspacing="0">
+<tr>
+ <td width="80%"><b>Create database structure (v1.9)</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>
+
+<h2>Updates</h2>
+
+<table width="80%" border="1" cellpadding="10" cellspacing="0">
+<tr valign="top">
+ <td>Update database structure (v0.5 to v0.7)</td><td><input type="submit" name="convert5to7" value="Update"></td>
+</tr>
+<tr valign="top">
+ <td>Update database structure (v0.7 to v1.3.3)</td>
+ <td><input type="submit" name="convert7to133" value="Update"><br><input type="submit" name="update_projects" value="Update projects"></td>
+</tr>
+<tr valign="top">
+ <td>Update database structure (v1.3.3 to v1.3.40)</td>
+ <td><input type="submit" name="convert133to1340" value="Update"><br><input type="submit" name="update_companies" value="Update companies"></td>
+</tr>
+<tr valign="top">
+ <td>Update database structure (v1.3.40 to v1.4.85)</td>
+ <td><input type="submit" name="convert1340to1485" value="Update"><br><input type="submit" name="update_to_team_id" value="Update team_id"></td>
+</tr>
+<tr valign="top">
+ <td>Update database structure (v1.4.85 to v1.5.79)</td>
+ <td><input type="submit" name="convert1485to1579" value="Update"></td>
+</tr>
+<tr valign="top">
+ <td>Update database structure (v1.5.79 to v1.6)</td>
+ <td><input type="submit" name="convert1579to1600" value="Update"><br><input type="submit" name="update_clients" value="Update clients"><br><input type="submit" name="update_custom_fields" value="Update custom fields"><br><input type="submit" name="update_tracking_mode" value="Update tracking mode"></td>
+</tr>
+<tr valign="top">
+ <td>Update database structure (v1.6 to v1.9)</td>
+ <td><input type="submit" name="convert1600to1900" value="Update"><br></td>
+</tr>
+</table>
+
+<h2>DB Maintenance</h2>
+<table width="80%" border="1" cellpadding="10" cellspacing="0">
+<tr>
+ <td width="80%">Clean up DB from inactive teams</td><td><input type="submit" name="cleanup" value="Clean up"></td>
+</tr>
+</table>
+
+</form>
+</div>
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+/*
+// +----------------------------------------------------------------------+
+// | 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
+// +----------------------------------------------------------------------+
+*/
+
+a { color: blue; text-decoration: none; }
+
+a:visited { text-decoration: none; }
+
+a:hover { text-decoration: underline; }
+
+body {
+ font-size: 10pt;
+ font-family: verdana;
+ background-color: white;
+}
+
+table { font-size: 10pt; font-family: verdana; }
+
+input, button { font-size: 10pt; font-family: verdana; }
+
+textarea { font-size: 10pt; font-family: verdana; }
+
+select{ font-size: 10pt; font-family: verdana; }
+
+.pageTitle {
+ font-size: 12pt;
+ font-weight: bold;
+ color: silver;
+}
+
+.systemMenu {
+ font-size: 12pt;
+ font-weight: bold;
+ color: #ffffff;
+ background-color: #000000;
+}
+
+.mainMenu {
+ font-size: 12pt;
+ color: #444444;
+}
+
+.tableHeader {
+ font-weight: bold;
+ text-align: left;
+ color: #000000;
+ background-color: #a6ccf7;
+}
+
+.tableHeaderCentered {
+ font-weight: bold;
+ text-align: center;
+ color: #000000;
+ background-color: #a6ccf7;
+}
+
+.rowReportItem {
+ background-color: #ccccce;
+}
+
+.rowReportItemAlt {
+ background-color: #f5f5f5;
+}
+
+.rowReportSubtotal {
+ background-color: #e0e0e0;
+}
+
+.cellLeftAligned {
+ text-align: left;
+ vertical-align: top;
+}
+
+.cellRightAligned {
+ text-align: right;
+ vertical-align: top;
+}
+
+.cellLeftAlignedSubtotal {
+ font-weight: bold;
+ vertical-align: top;
+}
+
+.cellRightAlignedSubtotal {
+ font-weight: bold;
+ text-align: right;
+ vertical-align: top;
+}
+
+.sectionHeader {
+ font-weight: bold;
+ border-bottom: 1px solid silver;
+}
+
+.sectionHeaderNoBorder {
+ font-weight: bold;
+}
+
+.error {
+ font-weight: bold;
+ color: red;
+}
+
+.info_message {
+ font-weight: bold;
+ color: #0000c0;
+}
+
+div#LoginAboutText { width:400px; }
+
--- /dev/null
+<?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('DateAndTime');
+import('ttExpenseHelper');
+
+// Access check.
+if (!ttAccessCheck(right_data_entry)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+$cl_id = $request->getParameter('id');
+$expense_item = ttExpenseHelper::getItem($cl_id, $user->getActiveUser());
+
+// Prohibit deleting invoiced records.
+if ($expense_item['invoice_id']) die($i18n->getKey('error.sys'));
+
+if ($request->getMethod() == 'POST') {
+ if ($request->getParameter('delete_button')) { // Delete button pressed.
+
+ // Determine if it's okay to delete the record.
+
+ // Determine lock date.
+ $lock_interval = $user->lock_interval;
+ $lockdate = 0;
+ if ($lock_interval > 0) {
+ $lockdate = new DateAndTime();
+ $lockdate->decDay($lock_interval);
+ }
+ if ($lockdate) {
+ $item_date = new DateAndTime(DB_DATEFORMAT);
+ $item_date->parseVal($expense_item['date'], DB_DATEFORMAT);
+ if ($item_date->before($lockdate))
+ $errors->add($i18n->getKey('error.period_locked'));
+ }
+
+ if ($errors->isEmpty()) {
+ // Mark the record as deleted.
+ if (ttExpenseHelper::markDeleted($cl_id, $user->getActiveUser())) {
+ header('Location: expenses.php');
+ exit();
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ }
+ }
+ if ($request->getParameter('cancel_button')) { // Cancel button pressed.
+ header('Location: expenses.php');
+ exit();
+ }
+}
+
+$form = new Form('expenseItemForm');
+$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_id));
+$form->addInput(array('type'=>'submit','name'=>'delete_button','value'=>$i18n->getKey('label.delete')));
+$form->addInput(array('type'=>'submit','name'=>'cancel_button','value'=>$i18n->getKey('button.cancel')));
+
+$smarty->assign('expense_item', $expense_item);
+$smarty->assign('forms', array($form->getName() => $form->toArray()));
+$smarty->assign('title', $i18n->getKey('title.delete_expense'));
+$smarty->assign('content_page_name', 'expense_delete.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttTeamHelper');
+import('DateAndTime');
+import('ttExpenseHelper');
+
+// Access check.
+if (!ttAccessCheck(right_data_entry)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+$cl_id = $request->getParameter('id');
+
+// Get the expense item we are editing.
+$expense_item = ttExpenseHelper::getItem($cl_id, $user->getActiveUser());
+
+// Prohibit editing invoiced items.
+if ($expense_item['invoice_id']) die($i18n->getKey('error.sys'));
+
+$item_date = new DateAndTime(DB_DATEFORMAT, $expense_item['date']);
+
+// Initialize variables.
+$cl_date = $cl_client = $cl_project = $cl_item_name = $cl_cost = null;
+if ($request->getMethod() == 'POST') {
+ $cl_date = trim($request->getParameter('date'));
+ $cl_client = $request->getParameter('client');
+ $cl_project = $request->getParameter('project');
+ $cl_item_name = trim($request->getParameter('item_name'));
+ $cl_cost = trim($request->getParameter('cost'));
+} else {
+ $cl_date = $item_date->toString($user->date_format);
+ $cl_client = $expense_item['client_id'];
+ $cl_project = $expense_item['project_id'];
+ $cl_item_name = $expense_item['name'];
+ $cl_cost = $expense_item['cost'];
+}
+
+// Initialize elements of 'expenseItemForm'.
+$form = new Form('expenseItemForm');
+
+// Dropdown for clients in MODE_TIME. Use all active clients.
+if (MODE_TIME == $user->tracking_mode && in_array('cl', explode(',', $user->plugins))) {
+ $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',
+ '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 (in_array('cl', explode(',', $user->plugins))) {
+ $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']);
+ $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'))
+ ));
+ }
+}
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'item_name','style'=>'width: 250px;','value'=>$cl_item_name));
+$form->addInput(array('type'=>'text','maxlength'=>'40','name'=>'cost','style'=>'width: 100px;','value'=>$cl_cost));
+$form->addInput(array('type'=>'datefield','name'=>'date','maxlength'=>'20','value'=>$cl_date));
+// Hidden control for record id.
+$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_id));
+$form->addInput(array('type'=>'hidden','name'=>'browser_today','value'=>'')); // User current date, which gets filled in on btn_save or btn_copy click.
+$form->addInput(array('type'=>'submit','name'=>'btn_save','onclick'=>'browser_today.value=get_date()','value'=>$i18n->getKey('button.save')));
+$form->addInput(array('type'=>'submit','name'=>'btn_copy','onclick'=>'browser_today.value=get_date()','value'=>$i18n->getKey('button.copy')));
+$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete')));
+
+if ($request->getMethod() == 'POST') {
+ // Validate user input.
+ if (in_array('cl', explode(',', $user->plugins)) && in_array('cm', explode(',', $user->plugins)) && !$cl_client)
+ $errors->add($i18n->getKey('error.client'));
+ if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) {
+ if (!$cl_project) $errors->add($i18n->getKey('error.project'));
+ }
+ if (!ttValidString($cl_item_name)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.item'));
+ if (!ttValidFloat($cl_cost)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.cost'));
+ if (!ttValidDate($cl_date)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.date'));
+
+ // Determine lock date.
+ $lock_interval = $user->lock_interval;
+ $lockdate = 0;
+ if ($lock_interval > 0) {
+ $lockdate = new DateAndTime();
+ $lockdate->decDay($lock_interval);
+ }
+
+ // This is a new date for the expense item.
+ $new_date = new DateAndTime($user->date_format, $cl_date);
+
+ // Prohibit creating entries in future.
+ if (defined('FUTURE_ENTRIES') && !isTrue(FUTURE_ENTRIES)) {
+ $browser_today = new DateAndTime(DB_DATEFORMAT, $request->getParameter('browser_today', null));
+ if ($new_date->after($browser_today))
+ $errors->add($i18n->getKey('error.future_date'));
+ }
+
+ // Save record.
+ if ($request->getParameter('btn_save')) {
+ // We need to:
+ // 1) Prohibit updating locked entries (that are in locked interval).
+ // 2) Prohibit saving unlocked entries into locked interval.
+
+ // Now, step by step.
+ // 1) Prohibit updating locked entries.
+ if($lockdate && $item_date->before($lockdate))
+ $errors->add($i18n->getKey('error.period_locked'));
+ // 2) Prohibit saving completed unlocked entries into locked interval.
+ if($errors->isEmpty() && $lockdate && $new_date->before($lockdate))
+ $errors->add($i18n->getKey('error.period_locked'));
+
+ // Now, an update.
+ if ($errors->isEmpty()) {
+ if (ttExpenseHelper::update(array('id'=>$cl_id,'date'=>$new_date->toString(DB_DATEFORMAT),'user_id'=>$user->getActiveUser(),
+ 'client_id'=>$cl_client,'project_id'=>$cl_project,'name'=>$cl_item_name,'cost'=>$cl_cost))) {
+ header('Location: expenses.php?date='.$new_date->toString(DB_DATEFORMAT));
+ exit();
+ }
+ }
+ }
+
+ // Save as new record.
+ if ($request->getParameter('btn_copy')) {
+ // We need to prohibit saving into locked interval.
+ if($lockdate && $new_date->before($lockdate))
+ $errors->add($i18n->getKey('error.period_locked'));
+
+ // Now, a new insert.
+ if ($errors->isEmpty()) {
+ if (ttExpenseHelper::insert(array('date'=>$new_date->toString(DB_DATEFORMAT),'user_id'=>$user->getActiveUser(),
+ 'client_id'=>$cl_client,'project_id'=>$cl_project,'name'=>$cl_item_name,'cost'=>$cl_cost,'status'=>1))) {
+ header('Location: expenses.php?date='.$new_date->toString(DB_DATEFORMAT));
+ exit();
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ }
+ }
+
+ if ($request->getParameter('btn_delete')) {
+ header("Location: expense_delete.php?id=$cl_id");
+ exit();
+ }
+} // End of if ($request->getMethod() == "POST")
+
+$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('title', $i18n->getKey('title.edit_expense'));
+$smarty->assign('content_page_name', 'expense_edit.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('DateAndTime');
+import('ttExpenseHelper');
+
+// Access check.
+if (!ttAccessCheck(right_data_entry)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+// 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;
+
+// Initialize variables.
+$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_item_name = $request->getParameter('item_name');
+$cl_cost = $request->getParameter('cost');
+
+// Elements of expensesForm.
+$form = new Form('expensesForm');
+
+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 && in_array('cl', explode(',', $user->plugins))) {
+ $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 (in_array('cl', explode(',', $user->plugins))) {
+ $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']);
+ $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'))
+ ));
+ }
+}
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'item_name','style'=>'width: 250px;','value'=>$cl_item_name));
+$form->addInput(array('type'=>'text','maxlength'=>'40','name'=>'cost','style'=>'width: 100px;','value'=>$cl_cost));
+$form->addInput(array('type'=>'calendar','name'=>'date','highlight'=>'expenses','value'=>$cl_date)); // calendar
+$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')));
+
+// Determine lock date. Time entries earlier than lock date cannot be created or modified.
+$lock_interval = $user->lock_interval;
+$lockdate = 0;
+if ($lock_interval > 0) {
+ $lockdate = new DateAndTime();
+ $lockdate->decDay($lock_interval);
+}
+
+// Submit.
+if ($request->getMethod() == 'POST') {
+ if ($request->getParameter('btn_submit')) {
+ // Validate user input.
+ if (in_array('cl', explode(',', $user->plugins)) && in_array('cm', explode(',', $user->plugins)) && !$cl_client)
+ $errors->add($i18n->getKey('error.client'));
+ if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) {
+ if (!$cl_project) $errors->add($i18n->getKey('error.project'));
+ }
+ if (!ttValidString($cl_item_name)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.item'));
+ if (!ttValidFloat($cl_cost)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.cost'));
+
+ // 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))
+ $errors->add($i18n->getKey('error.future_date'));
+ }
+ // Finished validating input data.
+
+ // Prohibit creating time entries in locked interval.
+ if($lockdate && $selected_date->before($lockdate))
+ $errors->add($i18n->getKey('error.period_locked'));
+
+ // Insert record.
+ if ($errors->isEmpty()) {
+ if (ttExpenseHelper::insert(array('date'=>$cl_date,'user_id'=>$user->getActiveUser(),
+ 'client_id'=>$cl_client,'project_id'=>$cl_project,'name'=>$cl_item_name,'cost'=>$cl_cost,'status'=>1))) {
+ header('Location: expenses.php');
+ exit();
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ }
+ }
+ else if ($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: expenses.php');
+ exit();
+ }
+ }
+}
+
+$smarty->assign('day_total', ttExpenseHelper::getTotalForDay($user->getActiveUser(), $cl_date));
+$smarty->assign('expense_items', ttExpenseHelper::getItems($user->getActiveUser(), $cl_date));
+$smarty->assign('client_list', $client_list);
+$smarty->assign('project_list', $project_list);
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('timestring', $selected_date->toString($user->date_format));
+$smarty->assign('title', $i18n->getKey('title.expenses'));
+$smarty->assign('content_page_name', 'expenses.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttExportHelper');
+import('form.Form');
+
+// Access check.
+if (!ttAccessCheck(right_export_team)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+$cl_compression = $request->getParameter('compression');
+$compressors = array('' => $i18n->getKey('form.export.compression_none'));
+if (function_exists('bzcompress'))
+ $compressors['bzip'] = $i18n->getKey('form.export.compression_bzip');
+
+$form = new Form('exportForm');
+$form->addInput(array('type'=>'combobox','name'=>'compression','value'=>$cl_compression,'data'=>$compressors));
+$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.export')));
+
+if ($request->getMethod() == 'POST') {
+
+ $filename = 'team_data.xml';
+ $mime_type = 'text/xml';
+ $compress = false;
+ if ('bzip' == $cl_compression) {
+ $compress = true;
+ $filename .= '.bz2';
+ $mime_type = 'application/x-bzip2';
+ }
+
+ $exportHelper = new ttExportHelper();
+ if ($exportHelper->createDataFile($compress)) {
+ header('Pragma: public'); // This is needed for IE8 to download files over https.
+ header('Content-Type: '.$mime_type);
+ header('Expires: '.gmdate('D, d M Y H:i:s').' GMT');
+ header('Content-Disposition: attachment; filename="'.$filename.'"');
+ header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
+ header('Cache-Control: private', false);
+
+ if ($file_pointer = fopen($exportHelper->getFileName(), 'r')) {
+ while ($data = fread($file_pointer, 4096)) {
+ echo $data;
+ }
+ fclose($file_pointer);
+ unlink($exportHelper->getFileName());
+ }
+ exit;
+ } else
+ $errors->add($i18n->getKey('error.sys'));
+}
+
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('title', $i18n->getKey('title.export'));
+$smarty->assign('content_page_name', 'export.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttImportHelper');
+import('form.Form');
+
+// Access check.
+if (!ttAccessCheck(right_administer_site)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+$form = new Form('importForm');
+$form->addInput(array('type'=>'upload','name'=>'xmlfile','value'=>'browse','maxsize'=>16777216)); // 16 MB file upload limit.
+// Note: for the above limit to work make sure to set upload_max_filesize and post_max_size in php.ini to at least 16M.
+$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.import')));
+
+if ($request->getMethod() == 'POST') {
+
+ $import = new ttImportHelper($errors);
+ $import->importXml();
+ if ($errors->isEmpty())
+ $messages->add($i18n->getKey('form.import.success'));
+}
+
+$smarty->assign('forms', array($form->getName()=>$form->toArray()) );
+$smarty->assign('title', $i18n->getKey('title.import'));
+$smarty->assign('content_page_name', 'import.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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');
+
+// Redirects for admin and client roles.
+if ($auth->isAuthenticated()) {
+ if ($user->isAdmin()) {
+ header('Location: admin_teams.php');
+ exit();
+ }
+ else if ($user->isClient()) {
+ header('Location: reports.php');
+ exit();
+ }
+}
+
+// Redirect to time.php or mobile/time.php for other roles.
+
+// Regexp to determine mobile browser was taken from detectmobilebrowsers.com on Aug 24, 2012.
+$useragent = $_SERVER['HTTP_USER_AGENT'];
+$mobileBrowser = preg_match('/android.+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|meego.+mobile|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i',$useragent)||preg_match('/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i',substr($useragent,0,4));
+
+if ($mobileBrowser) {
+?>
+
+<html>
+ <script src="js/strftime.js"></script>
+ <script>
+ location.href = "mobile/time.php?date="+(new Date()).strftime('<?php print DB_DATEFORMAT;?>');
+ </script>
+ <noscript>
+ <p>Anuko Time Tracker is a simple, easy to use, open source, web-based time tracking system.</p>
+ <p>Your browser does not support JavaScript. Time Tracker will not work without it.</p>
+ </noscript>
+</html>
+
+<?php
+} else {
+?>
+
+<html>
+ <script src="js/strftime.js"></script>
+ <script>
+ location.href = "time.php?date="+(new Date()).strftime('<?php print DB_DATEFORMAT;?>');
+ </script>
+ <noscript>
+ <p>Anuko Time Tracker is a simple, easy to use, open source, web-based time tracking system.</p>
+ <p>Your browser does not support JavaScript. Time Tracker will not work without it.</p>
+ </noscript>
+</html>
+
+<?php
+}
+?>
+
--- /dev/null
+<?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
+// +----------------------------------------------------------------------+
+
+// Report all errors except E_NOTICE and E_STRICT.
+// Ignoring E_STRICT is here because PEAR 1.9.4 that we use is not E_STRICT compliant.
+if (!defined('E_STRICT')) define('E_STRICT', 2048);
+// if (!defined('E_DEPRECATED')) define('E_DEPRECATED', 8192);
+error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT); // & ~E_DEPRECATED);
+// E_ALL tends to change as PHP evloves, therefore we use & here instead of exclusive OR (^).
+
+// Disable displaying errors on screen.
+ini_set('display_errors', 'Off');
+
+// require_once('init_auth.php');
+define("APP_DIR", dirname(__FILE__));
+define("LIBRARY_DIR", APP_DIR."/WEB-INF/lib");
+define("TEMPLATE_DIR", APP_DIR."/WEB-INF/templates");
+// Date format for database and URI parameters.
+define('DB_DATEFORMAT', '%Y-%m-%d');
+
+require_once(LIBRARY_DIR.'/common.lib.php');
+
+// Require the configuration file with application settings.
+if (!file_exists(APP_DIR."/WEB-INF/config.php")) die ("WEB-INF/config.php file does not exist.");
+require_once("WEB-INF/config.php");
+// Check whether DSN is defined.
+if (!defined("DSN")) {
+ die ("DSN value is not defined. Check your config.php file.");
+}
+
+// Depending on DSN, require either mysqli or mysql extensions.
+if (strrpos(DSN, 'mysqli://', -strlen(DSN)) !== FALSE) {
+ check_extension('mysqli'); // DSN starts with mysqli:// - require mysqli extension.
+}
+if (strrpos(DSN, 'mysql://', -strlen(DSN)) !== FALSE) {
+ check_extension('mysql'); // DSN starts with mysql:// - require mysql extension.
+}
+
+// Require other extensions.
+check_extension('mbstring');
+
+// If auth params are not defined (in config.php) - initialize with an empty array.
+if (!isset($GLOBALS['AUTH_MODULE_PARAMS']) || !is_array($GLOBALS['AUTH_MODULE_PARAMS']))
+ $GLOBALS['AUTH_MODULE_PARAMS'] = array();
+
+// Smarty initialization.
+import('smarty.Smarty');
+$smarty = new Smarty;
+$smarty->use_sub_dirs = false;
+$smarty->template_dir = TEMPLATE_DIR;
+$smarty->compile_dir = TEMPLATE_DIR.'_c';
+$GLOBALS['SMARTY'] = &$smarty;
+
+// Note: these 3 settings below used to be in .htaccess file. Moved them here to eliminate "error 500" problems
+// with some shared hostings that do not have AllowOverride Options or AllowOverride All in their apache configurations.
+// Change http cache expiration time to 1 minute.
+session_cache_expire(1);
+
+$phpsessid_ttl = defined('PHPSESSID_TTL') ? PHPSESSID_TTL : 60*60*24;
+// Set lifetime for garbage collection.
+ini_set('session.gc_maxlifetime', $phpsessid_ttl);
+// Set session cookie lifetime.
+session_set_cookie_params($phpsessid_ttl);
+if (isset($_COOKIE['tt_PHPSESSID'])) {
+ // Extend PHP session cookie lifetime by PHPSESSID_TTL (if defined, otherwise 24 hours)
+ // so that users don't have to re-login during this period from now.
+ setcookie('tt_PHPSESSID', $_COOKIE['tt_PHPSESSID'], time() + $phpsessid_ttl, '/');
+}
+
+// Start or resume PHP session.
+session_name('tt_PHPSESSID'); // "tt_" prefix is to avoid sharing session with other PHP apps that do not name session.
+@session_start();
+
+// Authorization.
+import('Auth');
+$auth = Auth::factory(AUTH_MODULE, $GLOBALS['AUTH_MODULE_PARAMS']);
+
+// Some defines we'll need.
+//
+define('RESOURCE_DIR', APP_DIR.'/WEB-INF/resources');
+define('COOKIE_EXPIRE', 60*60*24*30); // Cookies expire in 30 days.
+
+// Status values for projects, users, etc.
+define('ACTIVE', 1);
+define('INACTIVE', 0);
+// define('DELETED', -1); // DELETED items should have a NULL status. This allows us to have duplicate NULL status entries with existing indexes.
+
+// Definitions for tracking mode types.
+define('MODE_TIME', 0); // Tracking time only. There are no projects or tasks.
+define('MODE_PROJECTS', 1); // Tracking time per projects. There are no tasks.
+define('MODE_PROJECTS_AND_TASKS', 2); // Tracking time for projects and tasks.
+
+// Definitions of types for time records.
+define('TYPE_ALL', 0); // Time record can be specified with either duration or start and finish times.
+define('TYPE_START_FINISH', 1); // Time record has start and finish times.
+define('TYPE_DURATION', 2); // Time record has only duration, no start and finish times.
+
+// User access rights - bits that collectively define an access mask to the system (a role).
+// We'll have some bits here (1,2, etc...) reserved for future use.
+define('right_data_entry', 4); // Right to enter work hours and expenses.
+define('right_view_charts', 8); // Right to view charts.
+define('right_view_reports', 16); // Right to view reports.
+define('right_view_invoices', 32); // Right to view invoices.
+define('right_manage_team', 64); // Right to manage team. Note that this is not full access to team.
+define('right_assign_roles', 128); // Right to assign user roles.
+define('right_export_team', 256); // Right to export team data to a file.
+define('right_administer_site', 1024); // Admin account right to manage the application as a whole.
+
+// User roles.
+define('ROLE_USER', 4); // Regular user.
+define('ROLE_CLIENT', 16); // Client (to view reports and invoices).
+define('ROLE_COMANAGER', 68); // Team co-manager. Can do many things but not as much as team manager.
+define('ROLE_MANAGER', 324); // Team manager. Can do everything for a team.
+define('ROLE_SITE_ADMIN', 1024); // Site administrator.
+
+
+define('CHARSET', 'utf-8');
+
+date_default_timezone_set(@date_default_timezone_get());
+
+// Strip auto-inserted extra slashes when magic_quotes ON for PHP versions prior to 5.4.0.
+if (get_magic_quotes_gpc())
+ magic_quotes_off();
+
+// Initialize global objects that are needed for the application.
+import('html.HttpRequest');
+$request = new ttHttpRequest();
+
+import('form.ActionErrors');
+$errors = new ActionErrors();
+$messages = new ActionErrors();
+
+// Create an instance of ttUser class. This gets us most of user details.
+import('ttUser');
+$user = new ttUser(null, $auth->getUserId());
+if ($user->custom_logo) {
+ $smarty->assign('custom_logo', 'images/'.$user->team_id.'.png');
+ $smarty->assign('mobile_custom_logo', '../images/'.$user->team_id.'.png');
+}
+$smarty->assign('user', $user);
+
+// Localization.
+import('I18n');
+$i18n = new I18n();
+
+// Determine the language to use.
+$lang = $user->lang;
+if (!$lang) {
+ if (defined('LANG_DEFAULT'))
+ $lang = LANG_DEFAULT;
+
+ // If we still do not have the language get it from the browser.
+ if (!$lang) {
+ $lang = $i18n->getBrowserLanguage();
+
+ // Finally - English is the default.
+ if (!$lang) {
+ $lang = 'en';
+ }
+ }
+}
+
+// Load i18n file.
+$i18n->load($lang);
+$GLOBALS['I18N'] = &$i18n;
+
+$GLOBALS['USER'] = &$user;
+
+// Assign things for smarty to use in template files.
+$smarty->assign('i18n', $i18n->keys);
+$smarty->assign('errors', $errors);
+$smarty->assign('messages', $messages);
+
+// TODO: move this code out of here to the files that use it.
+
+// We use js/strftime.js to print dates in JavaScript (in DateField controls).
+// One of our date formats (%d.%m.%Y %a) prints a localized short weekday name (%a).
+// The init_js_date_locale function iniitializes Date.ext.locales array in js/strftime.js for our language
+// so that we could print localized short weekday names.
+//
+// JavaScript usage (see http://hacks.bluesmoon.info/strftime/localisation.html).
+//
+// var d = new Date();
+// d.locale = "fr"; // Remember to initialize locale.
+// d.strftime("%d.%m.%Y %a"); // This will output a localized %a as in "31.05.2013 Ven"
+
+// Initialize date locale for JavaScript.
+init_js_date_locale();
+
+function init_js_date_locale()
+{
+ global $i18n, $smarty;
+ $lang = $i18n->lang;
+
+ $days = $i18n->weekdayNames;
+ $short_day_names = array();
+ foreach($days as $k => $v) {
+ $short_day_names[$k] = mb_substr($v, 0, 3, 'utf-8');
+ }
+
+ /*
+ $months = $i18n->monthNames;
+ $short_month_names = array();
+ foreach ($months as $k => $v) {
+ $short_month_names[$k] = mb_substr($v, 0, 3, 'utf-8');
+ }
+ $js = "Date.ext.locales['$lang'] = {
+ a: ['" . join("', '", $short_day_names) . "'],
+ A: ['" . join("', '", $days) . "'],
+ b: ['" . join("', '", $short_month_names) . "'],
+ B: ['" . join("', '", $months) . "'],
+ c: '%a %d %b %Y %T %Z',
+ p: ['', ''],
+ P: ['', ''],
+ x: '%Y-%m-%d',
+ X: '%T'
+ };"; */
+ // We use %a in one of date formats. Therefore, simplified code here (instead of the above block).
+ // %p is also used on the Profile page in 12-hour time format example. Note that %p is not localized.
+ $js = "Date.ext.locales['$lang'] = {
+ a: ['" . join("', '", $short_day_names) . "'],
+ p: ['AM', 'PM']
+ };";
+ $smarty->assign('js_date_locale', $js);
+}
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttTeamHelper');
+import('ttInvoiceHelper');
+
+// Access check.
+if (!ttAccessCheck(right_manage_team)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+if ($request->getMethod() == 'POST') {
+ $cl_date = $request->getParameter('date');
+ $cl_client = $request->getParameter('client');
+ $cl_project = $request->getParameter('project');
+ $cl_number = trim($request->getParameter('number'));
+ $cl_start = $request->getParameter('start');
+ $cl_finish = $request->getParameter('finish');
+}
+
+$form = new Form('invoiceForm');
+$form->addInput(array('type'=>'datefield','name'=>'date','size'=>'20','value'=>$cl_date));
+
+// Dropdown for clients if the clients plugin is enabled.
+if (in_array('cl', explode(',', $user->plugins))) {
+ $clients = ttTeamHelper::getActiveClients($user->team_id);
+ $form->addInput(array('type'=>'combobox','name'=>'client','style'=>'width: 250px;','data'=>$clients,'datakeys'=>array('id','name'),'value'=>$cl_client,'empty'=>array(''=>$i18n->getKey('dropdown.select'))));
+}
+// Dropdown for projects.
+if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) {
+ $projects = ttTeamHelper::getActiveProjects($user->team_id);
+ $form->addInput(array('type'=>'combobox','name'=>'project','style'=>'width: 250px;','data'=>$projects,'datakeys'=>array('id','name'),'value'=>$cl_project,'empty'=>array(''=>$i18n->getKey('dropdown.all'))));
+}
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'number','style'=>'width: 250px;','value'=>$cl_number));
+$form->addInput(array('type'=>'datefield','maxlength'=>'20','name'=>'start','value'=>$cl_start));
+$form->addInput(array('type'=>'datefield','maxlength'=>'20','name'=>'finish','value'=>$cl_finish));
+$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.add')));
+
+if ($request->getMethod() == 'POST') {
+ // Validate user input.
+ if (!ttValidString($cl_number)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('form.invoice.number'));
+ if (!ttValidDate($cl_date)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.date'));
+ if (!$cl_client) $errors->add($i18n->getKey('error.client'));
+ if (!ttValidDate($cl_start)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.start_date'));
+ if (!ttValidDate($cl_finish)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.end_date'));
+
+ $fields = array('date'=>$cl_date,'name'=>$cl_number,'client_id'=>$cl_client,'project_id'=>$cl_project,'start_date'=>$cl_start,'end_date'=>$cl_finish);
+ if ($errors->isEmpty()) {
+ if (ttInvoiceHelper::getInvoiceByName($cl_number))
+ $errors->add($i18n->getKey('error.invoice_exists'));
+
+ if (!ttInvoiceHelper::invoiceableItemsExist($fields))
+ $errors->add($i18n->getKey('error.no_invoiceable_items'));
+ }
+
+ if ($errors->isEmpty()) {
+ // Now we can go ahead and create our invoice.
+ if (ttInvoiceHelper::createInvoice($fields)) {
+ header('Location: invoices.php');
+ exit();
+ }
+ $errors->add($i18n->getKey('error.db'));
+ }
+} // post
+
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('onload', 'onLoad="document.invoiceForm.number.focus()"');
+$smarty->assign('title', $i18n->getKey('title.add_invoice'));
+$smarty->assign('content_page_name', 'invoice_add.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttInvoiceHelper');
+
+// Access check.
+if (!ttAccessCheck(right_manage_team)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+$cl_invoice_id = (int)$request->getParameter('id');
+$invoice = ttInvoiceHelper::getInvoice($cl_invoice_id);
+$invoice_to_delete = $invoice['name'];
+
+$form = new Form('invoiceDeleteForm');
+$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_invoice_id));
+$form->addInput(array('type'=>'combobox',
+ 'name'=>'delete_invoice_entries',
+ 'data'=>array('0'=>$i18n->getKey('dropdown.do_not_delete'),'1'=>$i18n->getKey('dropdown.delete')),
+));
+$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete')));
+$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->getKey('button.cancel')));
+
+if ($request->getMethod() == 'POST') {
+ if ($request->getParameter('btn_delete')) {
+ if (ttInvoiceHelper::getInvoice($cl_invoice_id)) {
+ if (ttInvoiceHelper::delete($cl_invoice_id, $request->getParameter('delete_invoice_entries'))) {
+ header('Location: invoices.php');
+ exit();
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ } else if ($request->getParameter('btn_cancel')) {
+ header('Location: invoices.php');
+ exit();
+ }
+} // post
+
+$smarty->assign('invoice_to_delete', $invoice_to_delete);
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('onload', 'onLoad="document.invoiceDeleteForm.btn_cancel.focus()"');
+$smarty->assign('title', $i18n->getKey('title.delete_invoice'));
+$smarty->assign('content_page_name', 'invoice_delete.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttInvoiceHelper');
+import('ttSysConfig');
+
+// Access check.
+if (!ttAccessCheck(right_view_invoices)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+$cl_invoice_id = (int)$request->getParameter('id');
+$invoice = ttInvoiceHelper::getInvoice($cl_invoice_id);
+$sc = new ttSysConfig($user->id);
+
+// Security check.
+if (!$cl_invoice_id || !$invoice)
+ die ($i18n->getKey('error.sys'));
+
+if ($request->getMethod() == 'POST') {
+ $cl_receiver = trim($request->getParameter('receiver'));
+ $cl_cc = trim($request->getParameter('cc'));
+ $cl_subject = trim($request->getParameter('subject'));
+ $cl_comment = trim($request->getParameter('comment'));
+} else {
+ $cl_receiver = $sc->getValue(SYSC_LAST_INVOICE_EMAIL);
+ $cl_cc = $sc->getValue(SYSC_LAST_INVOICE_CC);
+ $cl_subject = $i18n->getKey('title.invoice').' '.$invoice['name'].', '.$user->team;
+}
+
+$form = new Form('mailForm');
+$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_invoice_id));
+$form->addInput(array('type'=>'text','name'=>'receiver','style'=>'width: 300px;','value'=>$cl_receiver));
+$form->addInput(array('type'=>'text','name'=>'cc','style'=>'width: 300px;','value'=>$cl_cc));
+$form->addInput(array('type'=>'text','name'=>'subject','style'=>'width: 300px;','value'=>$cl_subject));
+$form->addInput(array('type'=>'textarea','name'=>'comment','maxlength'=>'250','style'=>'width: 300px; height: 60px;'));
+$form->addInput(array('type'=>'submit','name'=>'btn_send','value'=>$i18n->getKey('button.send')));
+
+if ($request->getMethod() == 'POST') {
+ // Validate user input.
+ if (!ttValidEmailList($cl_receiver)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('form.mail.to'));
+ if (!ttValidEmailList($cl_cc, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('form.mail.cc'));
+ if (!ttValidString($cl_subject)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('form.mail.subject'));
+ if (!ttValidString($cl_comment, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.comment'));
+
+ if ($errors->isEmpty()) {
+ // Save last invoice emails for future use.
+ $sc->setValue(SYSC_LAST_INVOICE_EMAIL, $cl_receiver);
+ $sc->setValue(SYSC_LAST_INVOICE_CC, $cl_cc);
+
+ $body = ttInvoiceHelper::prepareInvoiceBody($cl_invoice_id, $cl_comment);
+
+ import('mail.Mailer');
+ $mailer = new Mailer();
+ $mailer->setCharSet(CHARSET);
+ $mailer->setContentType('text/html');
+ $mailer->setSender(SENDER);
+ $mailer->setReceiver($cl_receiver);
+ if (isset($cl_cc))
+ $mailer->setReceiverCC($cl_cc);
+ $mailer->setSendType(MAIL_MODE);
+ if ($mailer->send($cl_subject, $body))
+ $messages->add($i18n->getKey('form.mail.invoice_sent'));
+ else
+ $errors->add($i18n->getKey('error.mail_send'));
+ }
+}
+
+$smarty->assign('title', $i18n->getKey('title.send_invoice'));
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('onload', 'onLoad="document.mailForm.'.($cl_receiver?'comment':'receiver').'.focus()"');
+$smarty->assign('content_page_name', 'mail.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('DateAndTime');
+import('ttInvoiceHelper');
+import('ttClientHelper');
+
+// Access check.
+if (!ttAccessCheck(right_view_invoices)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+$invoice_id = (int)$request->getParameter('id');
+$invoice = ttInvoiceHelper::getInvoice($invoice_id);
+$invoice_date = new DateAndTime(DB_DATEFORMAT, $invoice['date']);
+$client = ttClientHelper::getClient($invoice['client_id'], true);
+if (!$client) // In case client was deleted.
+ $client = ttClientHelper::getDeletedClient($invoice['client_id']);
+
+$invoice_items = ttInvoiceHelper::getInvoiceItems($invoice_id);
+$tax_percent = $client['tax'];
+
+$subtotal = 0;
+$tax = 0;
+foreach($invoice_items as $item)
+ $subtotal += $item['cost'];
+if ($tax_percent) {
+ $tax_expenses = in_array('et', explode(',', $user->plugins));
+ foreach($invoice_items as $item) {
+ if ($item['type'] == 2 && !$tax_expenses)
+ continue;
+ $tax += round($item['cost'] * $tax_percent / 100, 2);
+ }
+}
+$total = $subtotal + $tax;
+
+$smarty->assign('subtotal', $user->currency.' '.str_replace('.', $user->decimal_mark, sprintf('%8.2f', round($subtotal, 2))));
+if ($tax) $smarty->assign('tax', $user->currency.' '.str_replace('.', $user->decimal_mark, sprintf('%8.2f', round($tax, 2))));
+$smarty->assign('total', $user->currency.' '.str_replace('.', $user->decimal_mark, sprintf('%8.2f', round($total, 2))));
+
+if ('.' != $user->decimal_mark) {
+ foreach ($invoice_items as &$item)
+ $item['cost'] = str_replace('.', $user->decimal_mark, $item['cost']);
+}
+
+// Calculate colspan for invoice summary.
+$colspan = 4;
+if (MODE_PROJECTS == $user->tracking_mode)
+ $colspan++;
+else if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode)
+ $colspan += 2;
+
+$smarty->assign('invoice_id', $invoice_id);
+$smarty->assign('invoice_name', $invoice['name']);
+$smarty->assign('invoice_date', $invoice_date->toString($user->date_format));
+$smarty->assign('client_name', $client['name']);
+$smarty->assign('client_address', $client['address']);
+$smarty->assign('invoice_items', $invoice_items);
+$smarty->assign('colspan', $colspan);
+$smarty->assign('title', $i18n->getKey('title.view_invoice'));
+$smarty->assign('content_page_name', 'invoice_view.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttTeamHelper');
+
+// Access check.
+if (!ttAccessCheck(right_view_invoices)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+$invoices = ttTeamHelper::getActiveInvoices();
+
+$smarty->assign('invoices', $invoices);
+$smarty->assign('title', $i18n->getKey('title.invoices'));
+$smarty->assign('content_page_name', 'invoices.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+Anuko Time Tracker
+
+Copyright 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.
--- /dev/null
+<?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('ttTeamHelper');
+import('ttUser');
+
+$cl_login = $request->getParameter('login');
+$cl_password = $request->getParameter('password');
+if ($cl_login == null && $request->getMethod() == 'GET')
+ $cl_login = @$_COOKIE['tt_login'];
+
+$form = new Form('loginForm');
+$form->addInput(array('type'=>'text','size'=>'25','maxlength'=>'100','name'=>'login','style'=>'width: 220px;','value'=>$cl_login));
+$form->addInput(array('type'=>'text','size'=>'25','maxlength'=>'50','name'=>'password','style'=>'width: 220px;','aspassword'=>true,'value'=>$cl_password));
+$form->addInput(array('type'=>'hidden','name'=>'browser_today','value'=>'')); // User current date, which gets filled in on btn_login click.
+$form->addInput(array('type'=>'submit','name'=>'btn_login','onclick'=>'browser_today.value=get_date()','value'=>$i18n->getKey('button.login')));
+
+if ($request->getMethod() == 'POST') {
+ // Validate user input.
+ if (!ttValidString($cl_login)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.login'));
+ if (!ttValidString($cl_password)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.password'));
+
+ if ($errors->isEmpty()) {
+ // Use the "limit" plugin if we have one. Ignore include errors.
+ // The "limit" plugin is not required for normal operation of Time Tracker.
+ @include('plugins/limit/access_check.php');
+
+ if ($auth->doLogin($cl_login, $cl_password)) {
+ // Set current user date (as determined by user browser) into session.
+ $current_user_date = $request->getParameter('browser_today', null);
+ if ($current_user_date)
+ $_SESSION['date'] = $current_user_date;
+
+ // Remember user login in a cookie.
+ setcookie('tt_login', $cl_login, time() + COOKIE_EXPIRE, '/');
+
+ $user = new ttUser(null, $auth->getUserId());
+ // Redirect, depending on user role.
+ if ($user->isAdmin()) {
+ header('Location: admin_teams.php');
+ exit();
+ }
+ else if ($user->isClient()) {
+ header('Location: reports.php');
+ exit();
+ }
+ else {
+ header('Location: time.php');
+ exit();
+ }
+ } else
+ $errors->add($i18n->getKey('error.auth'));
+ }
+}
+
+if(!isTrue(MULTITEAM_MODE) && !ttTeamHelper::getTeams())
+ $errors->add($i18n->getKey('error.no_teams'));
+
+// Determine whether to show login hint. It is currently used only for Windows LDAP authentication.
+$show_hint = ('ad' == $GLOBALS['AUTH_MODULE_PARAMS']['type']);
+
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('show_hint', $show_hint);
+$smarty->assign('onload', 'onLoad="document.loginForm.'.(!$cl_login?'login':'password').'.focus()"');
+$smarty->assign('title', $i18n->getKey('title.login'));
+$smarty->assign('content_page_name', 'login.tpl');
+$smarty->assign('about_text', $i18n->getKey('form.login.about'));
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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');
+$auth->doLogout();
+session_unset();
+
+header('Location: login.php');
+?>
\ No newline at end of file
--- /dev/null
+# Usage:
+# 1) Create a database using the "CREATE DATABASE" mysql command.
+# 2) Then, execute this script from command prompt with a command like this:
+# mysql -h host -u user -p -D db_name < mysql.sql
+
+# create database timetracker character set = 'utf8';
+
+# use timetracker;
+
+
+#
+# Structure for table tt_teams. A team is a group of users for whom we are tracking work time.
+# This table stores settings common to all team members such as language, week start day, etc.
+#
+CREATE TABLE `tt_teams` (
+ `id` int(11) NOT NULL auto_increment, # team id
+ `timestamp` timestamp NOT NULL, # modification timestamp
+ `name` varchar(80) default NULL, # team name
+ `address` varchar(255) default NULL, # team address, used in invoices
+ `currency` varchar(7) default NULL, # team currency symbol
+ `decimal_mark` char(1) NOT NULL default '.', # separator in decimals
+ `locktime` int(4) default '0', # lock interval in days
+ `lang` varchar(10) NOT NULL default 'en', # language
+ `date_format` varchar(20) NOT NULL default '%Y-%m-%d', # date format
+ `time_format` varchar(20) NOT NULL default '%H:%M', # time format
+ `week_start` smallint(2) NOT NULL DEFAULT '0', # Week start day, 0 == Sunday.
+ `tracking_mode` smallint(2) NOT NULL DEFAULT '1', # tracking mode ("projects" or "projects and tasks")
+ `record_type` smallint(2) NOT NULL DEFAULT '0', # time record type ("start and finish", "duration", or both)
+ `plugins` varchar(255) default NULL, # a list of enabled plugins for team
+ `custom_logo` tinyint(4) default '0', # whether to use a custom logo or not
+ `status` tinyint(4) default '1', # team status
+ PRIMARY KEY (`id`)
+);
+
+
+#
+# Structure for table tt_users. This table is used to store user properties.
+#
+CREATE TABLE `tt_users` (
+ `id` int(11) NOT NULL auto_increment, # user id
+ `timestamp` timestamp NOT NULL, # modification timestamp
+ `login` varchar(50) COLLATE utf8_bin NOT NULL, # user login
+ `password` varchar(50) default NULL, # password hash
+ `name` varchar(100) default NULL, # user name
+ `team_id` int(11) NOT NULL, # team id
+ `role` int(11) default '4', # user role ("manager", "co-manager", "client", or "user")
+ `client_id` int(11) default NULL, # client id for "client" user role
+ `rate` float(6,2) NOT NULL default '0.00', # default hourly rate
+ `email` varchar(100) default NULL, # user email
+ `status` tinyint(4) default '1', # user status
+ PRIMARY KEY (`id`)
+);
+
+# Create an index that guarantees unique active and inactive logins.
+create unique index login_idx on tt_users(login, status);
+
+# Create admin account with password 'secret'. Admin is a superuser, who can create teams.
+DELETE from `tt_users` WHERE login = 'admin';
+INSERT INTO `tt_users` (`login`, `password`, `name`, `team_id`, `role`) VALUES ('admin', md5('secret'), 'Admin', '0', '1024');
+
+
+#
+# Structure for table tt_projects.
+#
+CREATE TABLE `tt_projects` (
+ `id` int(11) NOT NULL auto_increment, # project id
+ `team_id` int(11) NOT NULL, # team id
+ `name` varchar(80) COLLATE utf8_bin NOT NULL, # project name
+ `description` varchar(255) default NULL, # project description
+ `tasks` text default NULL, # comma-separated list of task ids associated with this project
+ `status` tinyint(4) default '1', # project status
+ PRIMARY KEY (`id`)
+);
+
+# Create an index that guarantees unique active and inactive projects per team.
+create unique index project_idx on tt_projects(team_id, name, status);
+
+
+#
+# Structure for table tt_tasks.
+#
+CREATE TABLE `tt_tasks` (
+ `id` int(11) NOT NULL auto_increment, # task id
+ `team_id` int(11) NOT NULL, # team id
+ `name` varchar(80) COLLATE utf8_bin NOT NULL, # task name
+ `description` varchar(255) default NULL, # task description
+ `status` tinyint(4) default '1', # task status
+ PRIMARY KEY (`id`)
+);
+
+# Create an index that guarantees unique active and inactive tasks per team.
+create unique index task_idx on tt_tasks(team_id, name, status);
+
+
+#
+# Structure for table tt_user_project_binds. This table maps users to assigned projects.
+#
+CREATE TABLE `tt_user_project_binds` (
+ `id` int(11) NOT NULL auto_increment, # bind id
+ `user_id` int(11) NOT NULL, # user id
+ `project_id` int(11) NOT NULL, # project id
+ `rate` float(6,2) default '0.00', # rate for this user when working on this project
+ `status` tinyint(4) default '1', # bind status
+ PRIMARY KEY (`id`)
+);
+
+# Create an index that guarantees unique user to project binds.
+create unique index bind_idx on tt_user_project_binds(user_id, project_id);
+
+
+#
+# Structure for table tt_project_task_binds. This table maps projects to assigned tasks.
+#
+CREATE TABLE `tt_project_task_binds` (
+ `project_id` int(11) NOT NULL, # project id
+ `task_id` int(11) NOT NULL # task id
+);
+
+# Indexes for tt_project_task_binds.
+create index project_idx on tt_project_task_binds(project_id);
+create index task_idx on tt_project_task_binds(task_id);
+
+
+#
+# Structure for table tt_log. This is the table where time entries for users are stored.
+# If you use custom fields, additional info for each record may exist in tt_custom_field_log.
+#
+CREATE TABLE `tt_log` (
+ `id` bigint NOT NULL auto_increment, # time record id
+ `timestamp` timestamp NOT NULL, # modification timestamp
+ `user_id` int(11) NOT NULL, # user id
+ `date` date NOT NULL, # date the record is for
+ `start` time default NULL, # record start time (for example, 09:00)
+ `duration` time default NULL, # record duration (for example, 1 hour)
+ `client_id` int(11) default NULL, # client id
+ `project_id` int(11) default NULL, # project id
+ `task_id` int(11) default NULL, # task id
+ `invoice_id` int(11) default NULL, # invoice id
+ `comment` blob, # user provided comment for time record
+ `billable` tinyint(4) default '0', # whether the record is billable or not
+ `status` tinyint(4) default '1', # time record status
+ PRIMARY KEY (`id`)
+);
+
+# Create indexes on tt_log for performance.
+create index date_idx on tt_log(date);
+create index user_idx on tt_log(user_id);
+create index client_idx on tt_log(client_id);
+create index invoice_idx on tt_log(invoice_id);
+create index project_idx on tt_log(project_id);
+create index task_idx on tt_log(task_id);
+
+
+#
+# Structure for table tt_invoices. Invoices are issued to clients for billable work.
+#
+CREATE TABLE `tt_invoices` (
+ `id` int(11) NOT NULL auto_increment, # invoice id
+ `team_id` int(11) NOT NULL, # team id
+ `name` varchar(80) COLLATE utf8_bin NOT NULL, # invoice name
+ `date` date NOT NULL, # invoice date
+ `client_id` int(11) NOT NULL, # client id
+ `status` tinyint(4) default '1', # invoice status
+ PRIMARY KEY (`id`)
+);
+
+# Create an index that guarantees unique invoice names per team.
+create unique index name_idx on tt_invoices(team_id, name, status);
+
+
+#
+# Structure for table tt_tmp_refs. Used for reset password mechanism.
+#
+CREATE TABLE `tt_tmp_refs` (
+ `timestamp` timestamp NOT NULL, # creation timestamp
+ `ref` char(32) NOT NULL default '', # unique reference for user, used in urls
+ `user_id` int(11) NOT NULL # user id
+);
+
+
+#
+# Structure for table tt_fav_reports. Favorite reports are pre-configured report configurations.
+#
+CREATE TABLE `tt_fav_reports` (
+ `id` int(11) NOT NULL auto_increment, # favorite report id
+ `name` varchar(200) NOT NULL, # favorite report name
+ `user_id` int(11) NOT NULL, # user id favorite report belongs to
+ `client_id` int(11) default NULL, # client id (if selected)
+ `cf_1_option_id` int(11) default NULL, # custom field 1 option id (if selected)
+ `project_id` int(11) default NULL, # project id (if selected)
+ `task_id` int(11) default NULL, # task id (if selected)
+ `billable` tinyint(4) default NULL, # whether to include billable, not billable, or all records
+ `invoice` tinyint(4) default NULL, # whether to include invoiced, not invoiced, or all records
+ `users` text default NULL, # Comma-separated list of user ids. Nothing here means "all" users.
+ `period` tinyint(4) default NULL, # selected period type for report
+ `period_start` date default NULL, # period start
+ `period_end` date default NULL, # period end
+ `show_client` tinyint(4) NOT NULL default '0', # whether to show client column
+ `show_invoice` tinyint(4) NOT NULL default '0', # whether to show invoice column
+ `show_project` tinyint(4) NOT NULL default '0', # whether to show project column
+ `show_start` tinyint(4) NOT NULL default '0', # whether to show start field
+ `show_duration` tinyint(4) NOT NULL default '0', # whether to show duration field
+ `show_cost` tinyint(4) NOT NULL default '0', # whether to show cost field
+ `show_task` tinyint(4) NOT NULL default '0', # whether to show task column
+ `show_end` tinyint(4) NOT NULL default '0', # whether to show end field
+ `show_note` tinyint(4) NOT NULL default '0', # whether to show note column
+ `show_custom_field_1` tinyint(4) NOT NULL default '0', # whether to show custom field 1
+ `show_totals_only` tinyint(4) NOT NULL default '0', # whether to show totals only
+ `group_by` varchar(20) default NULL, # group by field
+ PRIMARY KEY (`id`)
+);
+
+
+#
+# Structure for table tt_cron. It is used to email favorite reports on schedule.
+#
+CREATE TABLE `tt_cron` (
+ `id` int(11) NOT NULL auto_increment, # entry id
+ `team_id` int(11) NOT NULL, # team id
+ `cron_spec` varchar(255) NOT NULL, # cron specification, "0 1 * * *" for "daily at 01:00"
+ `last` int(11) default NULL, # UNIX timestamp of when job was last run
+ `next` int(11) default NULL, # UNIX timestamp of when to run next job
+ `report_id` int(11) default NULL, # report id from tt_fav_reports, a report to mail on schedule
+ `email` varchar(100) default NULL, # email to send results to
+ `status` tinyint(4) default '1', # entry status
+ PRIMARY KEY (`id`)
+);
+
+
+#
+# Structure for table tt_clients. A client is an entity for whom work is performed and who may be invoiced.
+#
+CREATE TABLE `tt_clients` (
+ `id` int(11) NOT NULL AUTO_INCREMENT, # client id
+ `team_id` int(11) NOT NULL, # team id
+ `name` varchar(80) COLLATE utf8_bin NOT NULL, # client name
+ `address` varchar(255) default NULL, # client address
+ `tax` float(6,2) default '0.00', # applicable tax for this client
+ `projects` text default NULL, # comma-separated list of project ids assigned to this client
+ `status` tinyint(4) default '1', # client status
+ PRIMARY KEY (`id`)
+);
+
+# Create an index that guarantees unique active and inactive clients per team.
+create unique index client_name_idx on tt_clients(team_id, name, status);
+
+
+#
+# Structure for table tt_client_project_binds. This table maps clients to assigned projects.
+#
+CREATE TABLE `tt_client_project_binds` (
+ `client_id` int(11) NOT NULL, # client id
+ `project_id` int(11) NOT NULL # project id
+);
+
+# Indexes for tt_client_project_binds.
+create index client_idx on tt_client_project_binds(client_id);
+create index project_idx on tt_client_project_binds(project_id);
+
+
+#
+# Structure for table tt_config. This table is used to store configuration info for users.
+# For example, last_report_email parameter stores an email for user last report was emailed to.
+#
+CREATE TABLE `tt_config` (
+ `user_id` int(11) NOT NULL, # user id
+ `param_name` varchar(32) NOT NULL, # parameter name
+ `param_value` varchar(80) default NULL # parameter value
+);
+
+# Create an index that guarantees unique parameter names per user.
+create unique index param_idx on tt_config(user_id, param_name);
+
+
+# Below are the tables used by CustomFields plugin.
+
+#
+# Structure for table tt_custom_fields. This table contains definitions of custom fields.
+#
+CREATE TABLE `tt_custom_fields` (
+ `id` int(11) NOT NULL auto_increment, # custom field id
+ `team_id` int(11) NOT NULL, # team id
+ `type` tinyint(4) NOT NULL default '0', # custom field type (text or dropdown)
+ `label` varchar(32) NOT NULL default '', # custom field label
+ `required` tinyint(4) default '0', # whether this custom field is mandatory for time records
+ `status` tinyint(4) default '1', # custom field status
+ PRIMARY KEY (`id`)
+);
+
+
+#
+# Structure for table tt_custom_field_options. This table defines options for dropdown custom fields.
+#
+CREATE TABLE `tt_custom_field_options` (
+ `id` int(11) NOT NULL auto_increment, # option id
+ `field_id` int(11) NOT NULL, # custom field id
+ `value` varchar(32) NOT NULL default '', # option value
+ PRIMARY KEY (`id`)
+);
+
+
+#
+# Structure for table tt_custom_field_log.
+# This table supplements tt_log and contains custom field values for records.
+#
+CREATE TABLE `tt_custom_field_log` (
+ `id` bigint NOT NULL auto_increment, # cutom field log id
+ `log_id` bigint NOT NULL, # id of a record in tt_log this record corresponds to
+ `field_id` int(11) NOT NULL, # custom field id
+ `option_id` int(11) default NULL, # Option id. Used for dropdown custom fields.
+ `value` varchar(255) default NULL, # Text value. Used for text custom fields.
+ `status` tinyint(4) default '1', # custom field log entry status
+ PRIMARY KEY (`id`)
+);
+
+
+#
+# Structure for table tt_expense_items.
+# This table lists expense items.
+#
+CREATE TABLE `tt_expense_items` (
+ `id` bigint NOT NULL auto_increment, # expense item id
+ `date` date NOT NULL, # date the record is for
+ `user_id` int(11) NOT NULL, # user id the expense item is reported by
+ `client_id` int(11) default NULL, # client id
+ `project_id` int(11) default NULL, # project id
+ `name` varchar(255) NOT NULL, # expense item name (what is an expense for)
+ `cost` decimal(10,2) default '0.00', # item cost (including taxes, etc.)
+ `invoice_id` int(11) default NULL, # invoice id
+ `status` tinyint(4) default '1', # item status
+ PRIMARY KEY (`id`)
+);
+
+# Create indexes on tt_expense_items for performance.
+create index date_idx on tt_expense_items(date);
+create index user_idx on tt_expense_items(user_id);
+create index client_idx on tt_expense_items(client_id);
+create index project_idx on tt_expense_items(project_id);
+create index invoice_idx on tt_expense_items(invoice_id);
--- /dev/null
+<?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');
+require_once(LIBRARY_DIR.'/tdcron/class.tdcron.php');
+require_once(LIBRARY_DIR.'/tdcron/class.tdcron.entry.php');
+import('form.Form');
+import('ttFavReportHelper');
+import('ttNotificationHelper');
+
+// Access check.
+if (!ttAccessCheck(right_manage_team)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+$fav_reports = ttFavReportHelper::getReports($user->id);
+
+if ($request->getMethod() == 'POST') {
+ $cl_fav_report = trim($request->getParameter('fav_report'));
+ $cl_cron_spec = trim($request->getParameter('cron_spec'));
+ $cl_email = trim($request->getParameter('email'));
+} else {
+ $cl_cron_spec = '0 4 * * 1'; // Default schedule - weekly on Mondays at 04:00 (server time).
+}
+
+$form = new Form('notificationForm');
+$form->addInput(array('type'=>'combobox',
+ 'name'=>'fav_report',
+ 'style'=>'width: 250px;',
+ 'value'=>$cl_fav_report,
+ 'data'=>$fav_reports,
+ 'datakeys'=>array('id','name'),
+ 'empty'=>array(''=>$i18n->getKey('dropdown.select'))
+));
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'cron_spec','style'=>'width: 250px;','value'=>$cl_cron_spec));
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'email','style'=>'width: 250px;','value'=>$cl_email));
+$form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->getKey('button.add')));
+
+if ($request->getMethod() == 'POST') {
+ // Validate user input.
+ if (!$cl_fav_report) $errors->add($i18n->getKey('error.report'));
+ if (!ttValidCronSpec($cl_cron_spec)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.cron_schedule'));
+ if (!ttValidEmail($cl_email)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.email'));
+
+ if ($errors->isEmpty()) {
+ // Calculate next execution time.
+ $next = tdCron::getNextOccurrence($cl_cron_spec, mktime());
+
+ if (ttNotificationHelper::insert(array(
+ 'team_id' => $user->team_id,
+ 'cron_spec' => $cl_cron_spec,
+ 'next' => $next,
+ 'report_id' => $cl_fav_report,
+ 'email' => $cl_email,
+ 'status' => ACTIVE))) {
+ header('Location: notifications.php');
+ exit();
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ }
+} // post
+
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+// $smarty->assign('onload', 'onLoad="document.clientForm.name.focus()"');
+$smarty->assign('title', $i18n->getKey('title.add_notification'));
+$smarty->assign('content_page_name', 'notification_add.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttNotificationHelper');
+
+// Access check.
+if (!ttAccessCheck(right_manage_team)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+$cl_notification_id = (int)$request->getParameter('id');
+$notification = ttNotificationHelper::get($cl_notification_id);
+$notification_to_delete = $notification['name'];
+
+$form = new Form('notificationDeleteForm');
+$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_notification_id));
+$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete')));
+$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->getKey('button.cancel')));
+
+if ($request->getMethod() == 'POST') {
+ if ($request->getParameter('btn_delete')) {
+ if(ttNotificationHelper::get($cl_notification_id)) {
+ if (ttNotificationHelper::delete($cl_notification_id)) {
+ header('Location: notifications.php');
+ exit();
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ } else if ($request->getParameter('btn_cancel')) {
+ header('Location: notifications.php');
+ exit();
+ }
+} // post
+
+$smarty->assign('notification_to_delete', $notification_to_delete);
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('onload', 'onLoad="document.notificationDeleteForm.btn_cancel.focus()"');
+$smarty->assign('title', $i18n->getKey('title.delete_notification'));
+$smarty->assign('content_page_name', 'notification_delete.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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');
+require_once(LIBRARY_DIR.'/tdcron/class.tdcron.php');
+require_once(LIBRARY_DIR.'/tdcron/class.tdcron.entry.php');
+import('form.Form');
+import('ttFavReportHelper');
+import('ttNotificationHelper');
+
+// Access check.
+if (!ttAccessCheck(right_manage_team)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+$notification_id = (int) $request->getParameter('id');
+$fav_reports = ttFavReportHelper::getReports($user->id);
+
+if ($request->getMethod() == 'POST') {
+ $cl_fav_report = trim($request->getParameter('fav_report'));
+ $cl_cron_spec = trim($request->getParameter('cron_spec'));
+ $cl_email = trim($request->getParameter('email'));
+} else {
+ $notification = ttNotificationHelper::get($notification_id);
+ $cl_fav_report = $notification['report_id'];
+ $cl_cron_spec = $notification['cron_spec'];
+ $cl_email = $notification['email'];
+}
+
+$form = new Form('notificationForm');
+$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$notification_id));
+$form->addInput(array('type'=>'combobox',
+ 'name'=>'fav_report',
+ 'style'=>'width: 250px;',
+ 'value'=>$cl_fav_report,
+ 'data'=>$fav_reports,
+ 'datakeys'=>array('id','name'),
+ 'empty'=>array(''=>$i18n->getKey('dropdown.select'))
+));
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'cron_spec','style'=>'width: 250px;','value'=>$cl_cron_spec));
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'email','style'=>'width: 250px;','value'=>$cl_email));
+$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.save')));
+
+if ($request->getMethod() == 'POST') {
+ // Validate user input.
+ if (!$cl_fav_report) $errors->add($i18n->getKey('error.report'));
+ if (!ttValidCronSpec($cl_cron_spec)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.cron_schedule'));
+ if (!ttValidEmail($cl_email)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.email'));
+
+ if ($errors->isEmpty()) {
+ // Calculate next execution time.
+ $next = tdCron::getNextOccurrence($cl_cron_spec, mktime());
+
+ if (ttNotificationHelper::update(array(
+ 'id' => $notification_id,
+ 'team_id' => $user->team_id,
+ 'cron_spec' => $cl_cron_spec,
+ 'next' => $next,
+ 'report_id' => $cl_fav_report,
+ 'email' => $cl_email,
+ 'status' => ACTIVE))) {
+ header('Location: notifications.php');
+ exit();
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ }
+} // post
+
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+// $smarty->assign('onload', 'onLoad="document.clientForm.name.focus()"');
+$smarty->assign('title', $i18n->getKey('title.add_notification'));
+$smarty->assign('content_page_name', 'notification_edit.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttTeamHelper');
+
+// Access check.
+if (!ttAccessCheck(right_manage_team)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+$form = new Form('notificationsForm');
+
+if ($request->getMethod() == 'POST') {
+ if ($request->getParameter('btn_add')) {
+ // The Add button clicked. Redirect to notification_add.php page.
+ header('Location: notification_add.php');
+ exit();
+ }
+} else {
+ $form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->getKey('button.add')));
+ $notifications = ttTeamHelper::getNotifications($user->team_id);
+}
+
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('notifications', $notifications);
+$smarty->assign('title', $i18n->getKey('title.notifications'));
+$smarty->assign('content_page_name', 'notifications.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttUser');
+
+$auth->doLogout();
+
+$cl_ref = $request->getParameter('ref');
+if (!$cl_ref || $auth->isPasswordExternal()) {
+ header('Location: login.php');
+ exit();
+}
+
+// Get user ID.
+$user_id = ttUserHelper::getUserIdByTmpRef($cl_ref);
+if ($user_id) {
+ $user = new ttUser(null, $user_id); // Note: reusing $user from initialize.php.
+ // In case user language is different - reload $i18n.
+ if ($i18n->lang != $user->lang) {
+ $i18n->load($user->lang);
+ $smarty->assign('i18n', $i18n->keys);
+ }
+ if ($user->custom_logo) {
+ $smarty->assign('custom_logo', 'images/'.$user->team_id.'.png');
+ $smarty->assign('mobile_custom_logo', '../images/'.$user->team_id.'.png');
+ }
+ $smarty->assign('user', $user);
+}
+
+$cl_password1 = $request->getParameter('password1');
+$cl_password2 = $request->getParameter('password2');
+
+$form = new Form('newPasswordForm');
+$form->addInput(array('type'=>'text','maxlength'=>'120','name'=>'password1','aspassword'=>true,'value'=>$cl_password1));
+$form->addInput(array('type'=>'text','maxlength'=>'120','name'=>'password2','aspassword'=>true,'value'=>$cl_password2));
+$form->addInput(array('type'=>'hidden','name'=>'ref','value'=>$cl_ref));
+$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->getKey('button.save')));
+
+if ($request->getMethod() == 'POST') {
+ // Validate user input.
+ if (!ttValidString($cl_password1)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.password'));
+ if (!ttValidString($cl_password2)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.confirm_password'));
+ if ($cl_password1 !== $cl_password2)
+ $errors->add($i18n->getKey('error.not_equal'), $i18n->getKey('label.password'), $i18n->getKey('label.confirm_password'));
+
+ if ($errors->isEmpty()) {
+ // Use the "limit" plugin if we have one. Ignore include errors.
+ // The "limit" plugin is not required for normal operation of Time Tracker.
+ $cl_login = $user->login; // $cl_login is used in access_check.cpp.
+ @include('plugins/limit/access_check.php');
+
+ ttUserHelper::setPassword($user_id, $cl_password1);
+
+ if ($auth->doLogin($user->login, $cl_password1)) {
+
+ setcookie('tt_login', $user->login, time() + COOKIE_EXPIRE, '/');
+ header('Location: time.php');
+ exit();
+ } else {
+ $errors->add($i18n->getKey('error.auth'));
+ }
+ }
+}
+
+$smarty->assign('forms', array($form->getName() => $form->toArray()));
+$smarty->assign('title', $i18n->getKey('title.change_password'));
+$smarty->assign('content_page_name', 'password_change.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttUser');
+import('ttUserHelper');
+
+if ($auth->isPasswordExternal()) {
+ header('Location: login.php');
+ exit();
+}
+
+$form = new Form('resetPasswordForm');
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'login','style'=>'width: 300px;'));
+$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.reset_password')));
+
+if ($request->getMethod() == 'POST') {
+ $cl_login = $request->getParameter('login');
+
+ // Validate user input.
+ if (!ttValidString($cl_login)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.login'));
+
+ if ($errors->IsEmpty()) {
+ if (!ttUserHelper::getUserByLogin($cl_login)) {
+ // User with a specified login was not found.
+ // In this case, if login looks like email, try finding user by email.
+ if (ttValidEmail($cl_login)) {
+ $login = ttUserHelper::getUserByEmail($cl_login);
+ if ($login)
+ $cl_login = $login;
+ else
+ $errors->add($i18n->getKey('error.no_login'));
+ } else
+ $errors->add($i18n->getKey('error.no_login'));
+ }
+ }
+
+ if ($errors->IsEmpty()) {
+ $user = new ttUser($cl_login); // Note: reusing $user from initialize.php here.
+
+ // Prepare and save a temporary reference for user.
+ $temp_ref = md5(uniqid());
+ ttUserHelper::saveTmpRef($temp_ref, $user->id);
+
+ $user_i18n = null;
+ if ($user->lang != $i18n->lang) {
+ $user_i18n = new I18n();
+ $user_i18n->load($user->lang);
+ } else
+ $user_i18n = &$i18n;
+
+ // Where do we email to?
+ $receiver = null;
+ if ($user->email)
+ $receiver = $user->email;
+ else {
+ if (ttValidEmail($cl_login))
+ $receiver = $cl_login;
+ else
+ $errors->add($i18n->getKey('error.no_email'));
+ }
+
+ if ($receiver) {
+ import('mail.Mailer');
+ $sender = new Mailer();
+ $sender->setCharSet(CHARSET);
+ $sender->setSender(SENDER);
+ $sender->setReceiver("$receiver");
+ if ((!empty($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] !== 'off')) || ($_SERVER['SERVER_PORT'] == 443))
+ $secure_connection = true;
+ if($secure_connection)
+ $http = 'https';
+ else
+ $http = 'http';
+
+ $cl_subject = $user_i18n->getKey('form.reset_password.email_subject');
+ if (APP_NAME)
+ $pass_edit_url = $http.'://'.$_SERVER['HTTP_HOST'].'/'.APP_NAME.'/password_change.php?ref='.$temp_ref;
+ else
+ $pass_edit_url = $http.'://'.$_SERVER['HTTP_HOST'].'/password_change.php?ref='.$temp_ref;
+
+ $sender->setSendType(MAIL_MODE);
+ $res = $sender->send($cl_subject, sprintf($user_i18n->getKey('form.reset_password.email_body'), $pass_edit_url));
+ $smarty->assign('result_message', $res ? $i18n->getKey('form.reset_password.message') : $i18n->getKey('error.mail_send'));
+ }
+ }
+}
+
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('onload', 'onLoad="document.resetPasswordForm.login.focus()"');
+$smarty->assign('title', $i18n->getKey('title.reset_password'));
+$smarty->assign('content_page_name', 'password_reset.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?php
+phpinfo();
+?>
+
--- /dev/null
+<?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');
+
+// Access check.
+if (!ttAccessCheck(right_data_entry|right_view_reports)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+if (!defined('CURRENCY_DEFAULT')) define('CURRENCY_DEFAULT', '$');
+$can_change_login = $user->canManageTeam();
+
+if ($request->getMethod() == 'POST') {
+ $cl_name = trim($request->getParameter('name'));
+ $cl_login = trim($request->getParameter('login'));
+ if (!$auth->isPasswordExternal()) {
+ $cl_password1 = $request->getParameter('password1');
+ $cl_password2 = $request->getParameter('password2');
+ }
+ $cl_email = trim($request->getParameter('email'));
+
+ if ($user->canManageTeam()) {
+ $cl_team = trim($request->getParameter('team_name'));
+ $cl_address = trim($request->getParameter('address'));
+ $cl_currency = trim($request->getParameter('currency'));
+ if (!$cl_currency) $cl_currency = CURRENCY_DEFAULT;
+ $cl_lock_interval = $request->getParameter('lock_interval');
+ $cl_lang = $request->getParameter('lang');
+ $cl_decimal_mark = $request->getParameter('decimal_mark');
+ $cl_custom_format_date = $request->getParameter('format_date');
+ $cl_custom_format_time = $request->getParameter('format_time');
+ $cl_start_week = $request->getParameter('start_week');
+ $cl_tracking_mode = $request->getParameter('tracking_mode');
+ $cl_record_type = $request->getParameter('record_type');
+ $cl_charts = $request->getParameter('charts');
+ $cl_clients = $request->getParameter('clients');
+ $cl_client_required = $request->getParameter('client_required');
+ $cl_invoices = $request->getParameter('invoices');
+ $cl_custom_fields = $request->getParameter('custom_fields');
+ $cl_expenses = $request->getParameter('expenses');
+ $cl_tax_expenses = $request->getParameter('tax_expenses');
+ $cl_notifications = $request->getParameter('notifications');
+ }
+} else {
+ $cl_name = $user->name;
+ $cl_login = $user->login;
+ $cl_email = $user->email;
+ if ($user->canManageTeam()) {
+ $cl_team = $user->team;
+ $cl_address = $user->address;
+ $cl_currency = ($user->currency == ''? CURRENCY_DEFAULT : $user->currency);
+ $cl_lock_interval = $user->lock_interval;
+ $cl_lang = $user->lang;
+ $cl_decimal_mark = $user->decimal_mark;
+ $cl_custom_format_date = $user->date_format;
+ $cl_custom_format_time = $user->time_format;
+ $cl_start_week = $user->week_start;
+ $cl_tracking_mode = $user->tracking_mode;
+ $cl_record_type = $user->record_type;
+
+ // Which plugins do we have enabled?
+ $plugins = explode(',', $user->plugins);
+ $cl_charts = in_array('ch', $plugins);
+ $cl_clients = in_array('cl', $plugins);
+ $cl_client_required = in_array('cm', $plugins);
+ $cl_invoices = in_array('iv', $plugins);
+ $cl_custom_fields = in_array('cf', $plugins);
+ $cl_expenses = in_array('ex', $plugins);
+ $cl_tax_expenses = in_array('et', $plugins);
+ $cl_notifications = in_array('no', $plugins);
+ }
+}
+
+$form = new Form('profileForm');
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>$cl_name));
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'login','value'=>$cl_login,'enable'=>$can_change_login));
+if (!$auth->isPasswordExternal()) {
+ $form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'password1','aspassword'=>true,'value'=>$cl_password1));
+ $form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'password2','aspassword'=>true,'value'=>$cl_password2));
+}
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'email','value'=>$cl_email));
+if ($user->canManageTeam()) {
+ $form->addInput(array('type'=>'text','maxlength'=>'200','name'=>'team_name','value'=>$cl_team));
+ $form->addInput(array('type'=>'textarea','name'=>'address','maxlength'=>'255','style'=>'width: 350px;','cols'=>'55','rows'=>'4','value'=>$cl_address));
+ $form->addInput(array('type'=>'text','maxlength'=>'7','name'=>'currency','value'=>$cl_currency));
+ $DECIMAL_MARK_OPTIONS = array(
+ array('id'=>'.','name'=>'.'),
+ array('id'=>',','name'=>','));
+ $form->addInput(array('type'=>'combobox','name'=>'decimal_mark','style'=>'width: 150px','data'=>$DECIMAL_MARK_OPTIONS,'datakeys'=>array('id','name'),'value'=>$cl_decimal_mark,
+ 'onchange'=>'adjustDecimalPreview()'));
+ $form->addInput(array('type'=>'text','maxlength'=>'10','name'=>'lock_interval','value'=>$cl_lock_interval));
+ // Prepare an array of available languages.
+ $lang_files = I18n::getLangFileList();
+ foreach ($lang_files as $lfile) {
+ $content = file(RESOURCE_DIR."/".$lfile);
+ $lname = '';
+ foreach ($content as $line) {
+ if (strstr($line, 'i18n_language')) {
+ $a = explode('=', $line);
+ $lname = trim(str_replace(';','',str_replace("'","",$a[1])));
+ break;
+ }
+ }
+ unset($content);
+ $longname_lang[] = array('id'=>I18n::getLangFromFilename($lfile),'name'=>$lname);
+ }
+ $longname_lang = mu_sort($longname_lang, 'name');
+ $form->addInput(array('type'=>'combobox','name'=>'lang','style'=>'width: 150px','data'=>$longname_lang,'datakeys'=>array('id','name'),'value'=>$cl_lang));
+ $DATE_FORMAT_OPTIONS = array(
+ array('id'=>'%Y-%m-%d','name'=>'Y-m-d'),
+ array('id'=>'%m/%d/%Y','name'=>'m/d/Y'),
+ array('id'=>'%d.%m.%Y','name'=>'d.m.Y'),
+ array('id'=>'%d.%m.%Y %a','name'=>'d.m.Y a'));
+ $form->addInput(array('type'=>'combobox','name'=>'format_date','style'=>'width: 150px;','data'=>$DATE_FORMAT_OPTIONS,'datakeys'=>array('id','name'),'value'=>$cl_custom_format_date,
+ 'onchange'=>'MakeFormatPreview("date_format_preview", this);'));
+ $TIME_FORMAT_OPTIONS = array(
+ array('id'=>'%H:%M','name'=>$i18n->getKey('form.profile.24_hours')),
+ array('id'=>'%I:%M %p','name'=>$i18n->getKey('form.profile.12_hours')));
+ $form->addInput(array('type'=>'combobox','name'=>'format_time','style'=>'width: 150px;','data'=>$TIME_FORMAT_OPTIONS,'datakeys'=>array('id','name'),'value'=>$cl_custom_format_time,
+ 'onchange'=>'MakeFormatPreview("time_format_preview", this);'));
+
+ // Prepare week start choices.
+ $week_start_options = array();
+ foreach ($i18n->weekdayNames as $id => $week_dn) {
+ $week_start_options[] = array('id' => $id, 'name' => $week_dn);
+ }
+ $form->addInput(array('type'=>'combobox','name'=>'start_week','style'=>'width: 150px;','data'=>$week_start_options,'datakeys'=>array('id','name'),'value'=>$cl_start_week));
+
+ // Prepare tracking mode choices.
+ $tracking_mode_options = array();
+ $tracking_mode_options[MODE_TIME] = $i18n->getKey('form.profile.mode_time');
+ $tracking_mode_options[MODE_PROJECTS] = $i18n->getKey('form.profile.mode_projects');
+ $tracking_mode_options[MODE_PROJECTS_AND_TASKS] = $i18n->getKey('form.profile.mode_projects_and_tasks');
+ $form->addInput(array('type'=>'combobox','name'=>'tracking_mode','style'=>'width: 150px;','data'=>$tracking_mode_options,'value'=>$cl_tracking_mode));
+
+ // Prepare record type choices.
+ $record_type_options = array();
+ $record_type_options[TYPE_ALL] = $i18n->getKey('form.profile.type_all');
+ $record_type_options[TYPE_START_FINISH] = $i18n->getKey('form.profile.type_start_finish');
+ $record_type_options[TYPE_DURATION] = $i18n->getKey('form.profile.type_duration');
+ $form->addInput(array('type'=>'combobox','name'=>'record_type','style'=>'width: 150px;','data'=>$record_type_options,'value'=>$cl_record_type));
+
+ $form->addInput(array('type'=>'checkbox','name'=>'charts','data'=>1,'value'=>$cl_charts));
+ $form->addInput(array('type'=>'checkbox','name'=>'clients','data'=>1,'value'=>$cl_clients,'onchange'=>'handlePluginCheckboxes()'));
+ $form->addInput(array('type'=>'checkbox','name'=>'client_required','data'=>1,'value'=>$cl_client_required));
+
+ $form->addInput(array('type'=>'checkbox','name'=>'invoices','data'=>1,'value'=>$cl_invoices));
+ $form->addInput(array('type'=>'checkbox','name'=>'custom_fields','data'=>1,'value'=>$cl_custom_fields,'onchange'=>'handlePluginCheckboxes()'));
+ $form->addInput(array('type'=>'checkbox','name'=>'expenses','data'=>1,'value'=>$cl_expenses,'onchange'=>'handlePluginCheckboxes()'));
+ $form->addInput(array('type'=>'checkbox','name'=>'tax_expenses','data'=>1,'value'=>$cl_tax_expenses));
+ $form->addInput(array('type'=>'checkbox','name'=>'notifications','data'=>1,'value'=>$cl_notifications,'onchange'=>'handlePluginCheckboxes()'));
+}
+$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->getKey('button.save')));
+
+if ($request->getMethod() == 'POST') {
+ // Validate user input.
+ if (!ttValidString($cl_name)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.person_name'));
+ if ($can_change_login) {
+ if (!ttValidString($cl_login)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.login'));
+
+ // New login must be unique.
+ if ($cl_login != $user->login && ttUserHelper::getUserByLogin($cl_login))
+ $errors->add($i18n->getKey('error.user_exists'));
+ }
+ if (!$auth->isPasswordExternal() && ($cl_password1 || $cl_password2)) {
+ if (!ttValidString($cl_password1)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.password'));
+ if (!ttValidString($cl_password2)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.confirm_password'));
+ if ($cl_password1 !== $cl_password2)
+ $errors->add($i18n->getKey('error.not_equal'), $i18n->getKey('label.password'), $i18n->getKey('label.confirm_password'));
+ }
+ if (!ttValidEmail($cl_email, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.email'));
+ if ($user->canManageTeam()) {
+ if (!ttValidString($cl_team, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.team_name'));
+ if (!ttValidString($cl_address, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.address'));
+ if (!ttValidString($cl_currency, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.currency'));
+ if (!ttValidInteger($cl_lock_interval, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.lock_interval'));
+ }
+ // Finished validating user input.
+
+ if ($errors->isEmpty()) {
+ if ($cl_lock_interval == null || trim($cl_lock_interval) == '')
+ $cl_lock_interval = 0;
+
+ $update_result = true;
+ if ($user->canManageTeam()) {
+
+ // Prepare plugins string.
+ if ($cl_charts)
+ $plugins .= ',ch';
+ if ($cl_clients)
+ $plugins .= ',cl';
+ if ($cl_client_required)
+ $plugins .= ',cm';
+ if ($cl_invoices)
+ $plugins .= ',iv';
+ if ($cl_custom_fields)
+ $plugins .= ',cf';
+ if ($cl_expenses)
+ $plugins .= ',ex';
+ if ($cl_tax_expenses)
+ $plugins .= ',et';
+ if ($cl_notifications)
+ $plugins .= ',no';
+ $plugins = trim($plugins, ',');
+
+ $update_result = ttTeamHelper::update($user->team_id, array(
+ 'name' => $cl_team,
+ 'address' => $cl_address,
+ 'currency' => $cl_currency,
+ 'locktime' => $cl_lock_interval,
+ 'lang' => $cl_lang,
+ 'decimal_mark' => $cl_decimal_mark,
+ 'date_format' => $cl_custom_format_date,
+ 'time_format' => $cl_custom_format_time,
+ 'week_start' => $cl_start_week,
+ 'tracking_mode' => $cl_tracking_mode,
+ 'record_type' => $cl_record_type,
+ 'plugins' => $plugins));
+ }
+ if ($update_result) {
+ $update_result = ttUserHelper::update($user->id, array(
+ 'name' => $cl_name,
+ 'login' => $cl_login,
+ 'password' => $cl_password1,
+ 'email' => $cl_email,
+ 'status' => ACTIVE));
+ }
+ if ($update_result) {
+ header('Location: time.php');
+ exit();
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ }
+} // POST
+
+$smarty->assign('auth_external', $auth->isPasswordExternal());
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('onload', 'onLoad="handlePluginCheckboxes()"');
+$smarty->assign('title', $i18n->getKey('title.profile'));
+$smarty->assign('content_page_name', 'profile_edit.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttProjectHelper');
+import('ttTeamHelper');
+
+// Access check.
+if (!ttAccessCheck(right_manage_team)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+$users = ttTeamHelper::getActiveUsers();
+foreach ($users as $user_item)
+ $all_users[$user_item['id']] = $user_item['name'];
+
+$tasks = ttTeamHelper::getActiveTasks($user->team_id);
+foreach ($tasks as $task_item)
+ $all_tasks[$task_item['id']] = $task_item['name'];
+
+if ($request->getMethod() == 'POST') {
+ $cl_name = trim($request->getParameter('project_name'));
+ $cl_description = trim($request->getParameter('description'));
+ $cl_users = $request->getParameter('users', array());
+ $cl_tasks = $request->getParameter('tasks', array());
+} else {
+ foreach ($users as $user_item)
+ $cl_users[] = $user_item['id'];
+ foreach ($tasks as $task_item)
+ $cl_tasks[] = $task_item['id'];
+}
+
+$form = new Form('projectForm');
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'project_name','style'=>'width: 250px;','value'=>$cl_name));
+$form->addInput(array('type'=>'textarea','name'=>'description','style'=>'width: 250px; height: 40px;','value'=>$cl_description));
+$form->addInput(array('type'=>'checkboxgroup','name'=>'users','data'=>$all_users,'layout'=>'H','value'=>$cl_users));
+if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode)
+ $form->addInput(array('type'=>'checkboxgroup','name'=>'tasks','data'=>$all_tasks,'layout'=>'H','value'=>$cl_tasks));
+$form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->getKey('button.add')));
+
+if ($request->getMethod() == 'POST') {
+ // Validate user input.
+ if (!ttValidString($cl_name)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name'));
+ if (!ttValidString($cl_description, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.description'));
+
+ if ($errors->isEmpty()) {
+ if (!ttProjectHelper::getProjectByName($cl_name)) {
+ if (ttProjectHelper::insert(array(
+ 'team_id' => $user->team_id,
+ 'name' => $cl_name,
+ 'description' => $cl_description,
+ 'users' => $cl_users,
+ 'tasks' => $cl_tasks,
+ 'status' => ACTIVE))) {
+ header('Location: projects.php');
+ exit();
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ } else
+ $errors->add($i18n->getKey('error.project_exists'));
+ }
+} // post
+
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('onload', 'onLoad="document.projectForm.project_name.focus()"');
+$smarty->assign('title', $i18n->getKey('title.add_project'));
+$smarty->assign('content_page_name', 'project_add.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttProjectHelper');
+
+// Access check.
+if (!ttAccessCheck(right_manage_team)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+$cl_project_id = (int)$request->getParameter('id');
+$project = ttProjectHelper::get($cl_project_id);
+$project_to_delete = $project['name'];
+
+$form = new Form('projectDeleteForm');
+$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_project_id));
+$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete')));
+$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->getKey('button.cancel')));
+
+if ($request->getMethod() == 'POST') {
+ if ($request->getParameter('btn_delete')) {
+ if(ttProjectHelper::get($cl_project_id)) {
+ if (ttProjectHelper::delete($cl_project_id)) {
+ header('Location: projects.php');
+ exit();
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ } else if ($request->getParameter('btn_cancel')) {
+ header('Location: projects.php');
+ exit();
+ }
+} // post
+
+$smarty->assign('project_to_delete', $project_to_delete);
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('onload', 'onLoad="document.projectDeleteForm.btn_cancel.focus()"');
+$smarty->assign('title', $i18n->getKey('title.delete_project'));
+$smarty->assign('content_page_name', 'project_delete.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttProjectHelper');
+import('ttTeamHelper');
+
+// Access check.
+if (!ttAccessCheck(right_manage_team)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+$cl_project_id = (int)$request->getParameter('id');
+
+$users = ttTeamHelper::getActiveUsers();
+foreach ($users as $user_item)
+ $all_users[$user_item['id']] = $user_item['name'];
+
+$tasks = ttTeamHelper::getActiveTasks($user->team_id);
+foreach ($tasks as $task_item)
+ $all_tasks[$task_item['id']] = $task_item['name'];
+
+if ($request->getMethod() == 'POST') {
+ $cl_name = trim($request->getParameter('project_name'));
+ $cl_description = trim($request->getParameter('description'));
+ $cl_status = $request->getParameter('status');
+ $cl_users = $request->getParameter('users', array());
+ $cl_tasks = $request->getParameter('tasks', array());
+} else {
+ $project = ttProjectHelper::get($cl_project_id);
+ $cl_name = $project['name'];
+ $cl_description = $project['description'];
+ $cl_status = $project['status'];
+
+ $mdb2 = getConnection();
+ $sql = "select user_id from tt_user_project_binds where status = 1 and project_id = $cl_project_id";
+ $res = $mdb2->query($sql);
+ if (is_a($res, 'PEAR_Error'))
+ die($res->getMessage());
+ while ($row = $res->fetchRow())
+ $cl_users[] = $row['user_id'];
+
+ $cl_tasks = explode(',', $project['tasks']);
+}
+
+$form = new Form('projectForm');
+$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_project_id));
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'project_name','style'=>'width: 250px;','value'=>$cl_name));
+$form->addInput(array('type'=>'textarea','name'=>'description','style'=>'width: 250px; height: 40px;','value'=>$cl_description));
+$form->addInput(array('type'=>'combobox','name'=>'status','value'=>$cl_status,
+ 'data'=>array(ACTIVE=>$i18n->getKey('dropdown.status_active'),INACTIVE=>$i18n->getKey('dropdown.status_inactive'))));
+$form->addInput(array('type'=>'checkboxgroup','name'=>'users','data'=>$all_users,'layout'=>'H','value'=>$cl_users));
+if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode)
+ $form->addInput(array('type'=>'checkboxgroup','name'=>'tasks','data'=>$all_tasks,'layout'=>'H','value'=>$cl_tasks));
+$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->getKey('button.save')));
+$form->addInput(array('type'=>'submit','name'=>'btn_copy','value'=>$i18n->getKey('button.copy')));
+
+if ($request->getMethod() == 'POST') {
+ // Validate user input.
+ if (!ttValidString($cl_name)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name'));
+ if (!ttValidString($cl_description, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.description'));
+
+ if ($errors->isEmpty()) {
+ if ($request->getParameter('btn_save')) {
+ $existing_project = ttProjectHelper::getProjectByName($cl_name);
+ if (!$existing_project || ($cl_project_id == $existing_project['id'])) {
+ // Update project information.
+ if (ttProjectHelper::update(array(
+ 'id' => $cl_project_id,
+ 'name' => $cl_name,
+ 'description' => $cl_description,
+ 'status' => $cl_status,
+ 'users' => $cl_users,
+ 'tasks' => $cl_tasks))) {
+ header('Location: projects.php');
+ exit();
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ } else
+ $errors->add($i18n->getKey('error.project_exists'));
+ }
+
+ if ($request->getParameter('btn_copy')) {
+ if (!ttProjectHelper::getProjectByName($cl_name)) {
+ if (ttProjectHelper::insert(array(
+ 'team_id' => $user->team_id,
+ 'name' => $cl_name,
+ 'description' => $cl_description,
+ 'users' => $cl_users,
+ 'tasks' => $cl_tasks,
+ 'status' => ACTIVE))) {
+ header('Location: projects.php');
+ exit();
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ } else
+ $errors->add($i18n->getKey('error.project_exists'));
+ }
+ }
+} // post
+
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('onload', 'onLoad="document.projectForm.name.focus()"');
+$smarty->assign('title', $i18n->getKey('title.edit_project'));
+$smarty->assign('content_page_name', 'project_edit.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttTeamHelper');
+
+// Access check.
+if (!ttAccessCheck(right_data_entry)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+if($user->canManageTeam()) {
+ $active_projects = ttTeamHelper::getActiveProjects($user->team_id);
+ $inactive_projects = ttTeamHelper::getInactiveProjects($user->team_id);
+} else
+ $active_projects = $user->getAssignedProjects();
+
+$smarty->assign('active_projects', $active_projects);
+$smarty->assign('inactive_projects', $inactive_projects);
+$smarty->assign('title', $i18n->getKey('title.projects'));
+$smarty->assign('content_page_name', 'projects.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+Anuko Time Tracker.
+Copyright (c) Anuko (https://www.anuko.com).
+
+Project home page: https://www.anuko.com/time_tracker/index.htm
+Free hosting of Time Tracker for individuals and small teams is available at https://timetracker.anuko.com
+
+Each file in this archive is protected by the LIBERAL FREEWARE LICENSE.
+Read the file license.txt for details.
+
+
+INSTALLATION INSTRUCTIONS
+
+Detailed documentation is available at https://www.anuko.com/time_tracker/install_guide/index.htm
+
+The general installation procedure looks like this:
+
+- Install a web server and make sure it can serve HTML documents.
+- Install PHP, configure your server to work with PHP scripts, and make sure it can work with PHP files.
+- Install the following PHP extensions: MySQL and GD. The GD extension is needed for pie-charts only.
+- Install a database server such as MySQL and make sure it is working properly.
+- Install, configure, and test Anuko Time Tracker like so:
+
+1) Unpack distribution files into a selected directory for Apache web server.
+2) Create a database using the mysql.sql file in the distribution.
+3) If you are upgrading from earlier versions run dbinstall.php from your browser and do the required "Update database structure" steps.
+4) Create user name and password to access the time tracker database.
+5) Change $dsn value in /WEB-INF/config.php file to reflect your database connection parameters (user name and password).
+6) For UNIX systems set full access rights for catalog WEB-INF/templates_c/ (chmod 777 templates_c).
+7) If you install time tracker into a sub-directory of your site reflect this in the APP_NAME parameter in /WEB-INF/config.php file. For example, for http://localhost/timetracker/ set APP_NAME = "timetracker".
+8) Login to your time tracker site as admin with password "secret" without quotes and create at least one team.
+9) Change admin password (on the admin "options" page). You can also use the following SQL console command:
+ update tt_users set password = md5('new_password_here') where login='admin'
+ or by using the "Change password of administrator account" option in http://your_time_tracker_site/dbinstall.php
+10) Test if everything is working.
+11) Remove dbinstall.php file from your installation directory.
+
+
+UPGRADE FROM EARLIER VERSIONS
+
+See https://www.anuko.com/time_tracker/upgrade.htm
+
+
+BLANK PAGES IN TIME TRACKER
+
+If you see a blank page in when trying to access Anuko Time Tracker it may mean many things, among others, such as:
+
+ * MySQL extension for PHP not installed or not working.
+ * Time tracker database not created.
+ * Access (login / password) to the database is not configured properly in config.php.
+ * MySQL service is down.
+ * On UNIX systems - no full access rights for catalog WEB-INF/templates_c/ (chmod 777 templates_c).
+
+You need to thoroughly test each and every component to make sure they work together nicely.
+
+
+INSTALLATION / UPGRADES / DATA MIGRATION HELP
+
+Support is available on per-incident basis - see https://www.anuko.com/support.htm
+
+
+CHANGE LOG
+
+Change log is available at https://www.anuko.com/time_tracker/change_log/index.htm
\ No newline at end of file
--- /dev/null
+<?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');
+
+if (!isTrue(MULTITEAM_MODE) || $auth->isPasswordExternal()) {
+ header('Location: login.php');
+ exit();
+}
+
+$auth->doLogout();
+
+if (!defined('CURRENCY_DEFAULT')) define('CURRENCY_DEFAULT', '$');
+
+if ($request->getMethod() == 'POST') {
+ $cl_team_name = trim($request->getParameter('team_name'));
+ $cl_currency = trim($request->getParameter('currency'));
+ if (!$cl_currency) $cl_currency = CURRENCY_DEFAULT;
+ $cl_manager_name = trim($request->getParameter('manager_name'));
+ $cl_manager_login = trim($request->getParameter('manager_login'));
+ $cl_password1 = $request->getParameter('password1');
+ $cl_password2 = $request->getParameter('password2');
+ $cl_manager_email = trim($request->getParameter('manager_email'));
+} else
+ $cl_currency = CURRENCY_DEFAULT;
+
+$form = new Form('profileForm');
+$form->addInput(array('type'=>'text','maxlength'=>'200','name'=>'team_name','value'=>$cl_team_name));
+$form->addInput(array('type'=>'text','maxlength'=>'7','name'=>'currency','value'=>$cl_currency));
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'manager_name','value'=>$cl_manager_name));
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'manager_login','value'=>$cl_manager_login));
+$form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'password1','aspassword'=>true,'value'=>$cl_password1));
+$form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'password2','aspassword'=>true,'value'=>$cl_password2));
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'manager_email','value'=>$cl_manager_email));
+$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.submit')));
+
+if ($request->getMethod() == 'POST') {
+ // Validate user input.
+ if (!ttValidString($cl_team_name, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.team_name'));
+ if (!ttValidString($cl_currency, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.currency'));
+ if (!ttValidString($cl_manager_name)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.manager_name'));
+ if (!ttValidString($cl_manager_login)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.manager_login'));
+ if (!ttValidString($cl_password1)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.password'));
+ if (!ttValidString($cl_password2)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.confirm_password'));
+ if ($cl_password1 !== $cl_password2)
+ $errors->add($i18n->getKey('error.not_equal'), $i18n->getKey('label.password'), $i18n->getKey('label.confirm_password'));
+ if (!ttValidEmail($cl_manager_email, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.email'));
+
+ if ($errors->isEmpty()) {
+ if (!ttUserHelper::getUserByLogin($cl_manager_login)) {
+ // Create a new team.
+ $team_id = ttTeamHelper::insert(array('name'=>$cl_team_name,'currency'=>$cl_currency));
+ if ($team_id) {
+ // Team created, now create a team manager.
+ $user_id = ttUserHelper::insert(array(
+ 'team_id' => $team_id,
+ 'role' => ROLE_MANAGER,
+ 'name' => $cl_manager_name,
+ 'login' => $cl_manager_login,
+ 'password' => $cl_password1,
+ 'email' => $cl_manager_email));
+ }
+ if ($team_id && $user_id) {
+ if ($auth->doLogin($cl_manager_login, $cl_password1)) {
+ setcookie('tt_login', $cl_manager_login, time() + COOKIE_EXPIRE, '/');
+ header('Location: time.php');
+ } else {
+ header('Location: login.php');
+ }
+ exit();
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ } else
+ $errors->add($i18n->getKey('error.user_exists'));
+ }
+}
+
+$smarty->assign('title', $i18n->getKey('title.create_team'));
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('onload', 'onLoad="document.profileForm.team.focus()"');
+$smarty->assign('content_page_name', 'register.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('form.ActionForm');
+import('ttReportHelper');
+import('ttTeamHelper');
+
+// Access check.
+if (!ttAccessCheck(right_view_reports)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+// Use custom fields plugin if it is enabled.
+if (in_array('cf', explode(',', $user->plugins))) {
+ require_once('plugins/CustomFields.class.php');
+ $custom_fields = new CustomFields($user->team_id);
+ $smarty->assign('custom_fields', $custom_fields);
+}
+
+$form = new Form('reportForm');
+
+// Report settings are stored in session bean before we get here from reports.php.
+$bean = new ActionForm('reportBean', $form, $request);
+$client_id = $bean->getAttribute('client');
+if ($client_id && $bean->getAttribute('chinvoice') && ('no_grouping' == $bean->getAttribute('group_by')) && !$user->isClient()) {
+ // Client is selected and we are displaying the invoice column.
+ $recent_invoices = ttTeamHelper::getRecentInvoices($user->team_id, $client_id);
+ if ($recent_invoices) {
+ $form->addInput(array('type'=>'combobox',
+ 'name'=>'recent_invoice',
+ 'data'=>$recent_invoices,
+ 'datakeys'=>array('id','name'),
+ 'empty'=>array(''=>$i18n->getKey('dropdown.select_invoice'))));
+ $form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.submit')));
+ $smarty->assign('use_checkboxes', true);
+ }
+}
+
+if ($request->getMethod() == 'POST') {
+ foreach($_POST as $key => $val) {
+ if ('log_id_' == substr($key, 0, 7))
+ $time_log_ids[] = substr($key, 7);
+ if ('item_id_' == substr($key, 0, 8))
+ $expense_item_ids[] = substr($key, 8);
+ if ('recent_invoice' == $key)
+ $invoice_id = $val;
+ }
+ if ($time_log_ids || $expense_item_ids) {
+ // Some records are checked for invoice editing... Adjust their invoice accordingly.
+ ttReportHelper::assignToInvoice($invoice_id, $time_log_ids, $expense_item_ids);
+ }
+ // Re-display this form.
+ header('Location: report.php');
+ exit();
+} // post
+
+$group_by = $bean->getAttribute('group_by');
+
+$report_items = ttReportHelper::getItems($bean);
+if ('no_grouping' != $group_by)
+ $subtotals = ttReportHelper::getSubtotals($bean);
+$totals = ttReportHelper::getTotals($bean);
+
+// Assign variables that are used to print subtotals.
+if ($report_items && 'no_grouping' != $group_by) {
+ $smarty->assign('print_subtotals', true);
+ $smarty->assign('first_pass', true);
+ $smarty->assign('group_by', $group_by);
+ $smarty->assign('prev_grouped_by', '');
+ $smarty->assign('cur_grouped_by', '');
+}
+// Determine group by header.
+if ('no_grouping' != $group_by) {
+ if ('cf_1' == $group_by)
+ $smarty->assign('group_by_header', $custom_fields->fields[0]['label']);
+ else {
+ $key = 'label.'.$group_by;
+ $smarty->assign('group_by_header', $i18n->getKey($key));
+ }
+}
+// Assign variables that are used to alternate color of rows for different dates.
+$smarty->assign('prev_date', '');
+$smarty->assign('cur_date', '');
+$smarty->assign('report_row_class', 'rowReportItem');
+
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+
+$smarty->assign('report_items', $report_items);
+$smarty->assign('subtotals', $subtotals);
+$smarty->assign('totals', $totals);
+$smarty->assign('bean', $bean);
+$smarty->assign('title', $i18n->getKey('title.report').": ".$totals['start_date']." - ".$totals['end_date']);
+$smarty->assign('content_page_name', 'report.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('form.ActionForm');
+import('ttSysConfig');
+import('ttReportHelper');
+
+// Access check.
+if (!ttAccessCheck(right_view_reports)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+$sc = new ttSysConfig($user->id);
+
+if ($request->getMethod() == 'POST') {
+ $cl_receiver = trim($request->getParameter('receiver'));
+ $cl_cc = trim($request->getParameter('cc'));
+ $cl_subject = trim($request->getParameter('subject'));
+ $cl_comment = trim($request->getParameter('comment'));
+} else {
+ $cl_receiver = $sc->getValue(SYSC_LAST_REPORT_EMAIL);
+ $cl_cc = $sc->getValue(SYSC_LAST_REPORT_CC);
+ $cl_subject = $i18n->getKey('form.mail.report_subject');
+}
+
+$form = new Form('mailForm');
+$form->addInput(array('type'=>'text','name'=>'receiver','style'=>'width: 300px;','value'=>$cl_receiver));
+$form->addInput(array('type'=>'text','name'=>'cc','style'=>'width: 300px;','value'=>$cl_cc));
+$form->addInput(array('type'=>'text','name'=>'subject','style'=>'width: 300px;','value'=>$cl_subject));
+$form->addInput(array('type'=>'textarea','name'=>'comment','maxlength'=>'250','style'=>'width: 300px; height: 60px;'));
+$form->addInput(array('type'=>'submit','name'=>'btn_send','value'=>$i18n->getKey('button.send')));
+
+if ($request->getMethod() == 'POST') {
+ // Validate user input.
+ if (!ttValidEmailList($cl_receiver)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('form.mail.to'));
+ if (!ttValidEmailList($cl_cc, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('form.mail.cc'));
+ if (!ttValidString($cl_subject)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('form.mail.subject'));
+ if (!ttValidString($cl_comment, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.comment'));
+
+ if ($errors->isEmpty()) {
+ // Save last report emails for future use.
+ $sc->setValue(SYSC_LAST_REPORT_EMAIL, $cl_receiver);
+ $sc->setValue(SYSC_LAST_REPORT_CC, $cl_cc);
+
+ // Obtain session bean with report attributes.
+ $bean = new ActionForm('reportBean', new Form('reportForm'));
+ // Prepare report body.
+ $body = ttReportHelper::prepareReportBody($bean, $cl_comment);
+
+ import('mail.Mailer');
+ $mailer = new Mailer();
+ $mailer->setCharSet(CHARSET);
+ $mailer->setContentType('text/html');
+ $mailer->setSender(SENDER);
+ $mailer->setReceiver($cl_receiver);
+ if (isset($cl_cc))
+ $mailer->setReceiverCC($cl_cc);
+ $mailer->setSendType(MAIL_MODE);
+ if ($mailer->send($cl_subject, $body))
+ $messages->add($i18n->getKey('form.mail.report_sent'));
+ else
+ $errors->add($i18n->getKey('error.mail_send'));
+ }
+}
+
+$smarty->assign('title', $i18n->getKey('title.send_report'));
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('onload', 'onLoad="document.mailForm.'.($cl_receiver?'comment':'receiver').'.focus()"');
+$smarty->assign('content_page_name', 'mail.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('form.ActionForm');
+import('DateAndTime');
+import('ttTeamHelper');
+import('Period');
+import('ttProjectHelper');
+import('ttFavReportHelper');
+import('ttClientHelper');
+
+// Access check.
+if (!ttAccessCheck(right_view_reports)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+// Use custom fields plugin if it is enabled.
+if (in_array('cf', explode(',', $user->plugins))) {
+ require_once('plugins/CustomFields.class.php');
+ $custom_fields = new CustomFields($user->team_id);
+ $smarty->assign('custom_fields', $custom_fields);
+}
+
+$form = new Form('reportForm');
+
+// Get saved favorite reports for user.
+$report_list = ttFavReportHelper::getReports($user->id);
+$form->addInput(array('type'=>'combobox',
+ 'name'=>'favorite_report',
+ 'onchange'=>'document.reportForm.fav_report_changed.value=1;document.reportForm.submit();',
+ 'style'=>'width: 250px;',
+ 'data'=>$report_list,
+ 'datakeys'=>array('id','name'),
+ 'empty'=>array('-1'=>$i18n->getKey('dropdown.no'))
+));
+$form->addInput(array('type'=>'hidden','name'=>'fav_report_changed'));
+// Generate and Delete buttons.
+$form->addInput(array('type'=>'submit','name'=>'btn_generate','value'=>$i18n->getKey('button.generate')));
+$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete'),'onclick'=>"return confirm('".$i18n->getKey('form.reports.confirm_delete')."')"));
+
+// Dropdown for clients if the clients plugin is enabled.
+if (in_array('cl', explode(',', $user->plugins)) && !($user->isClient() && $user->client_id)) {
+ if ($user->canManageTeam() || ($user->isClient() && !$user->client_id))
+ $client_list = ttClientHelper::getClients($user->team_id);
+ else
+ $client_list = ttClientHelper::getClientsForUser();
+ $form->addInput(array('type'=>'combobox',
+ 'name'=>'client',
+ 'style'=>'width: 250px;',
+ 'data'=>$client_list,
+ 'datakeys'=>array('id', 'name'),
+ 'empty'=>array(''=>$i18n->getKey('dropdown.all'))
+ ));
+}
+
+// If we have a TYPE_DROPDOWN custom field - add control to select an option.
+if ($custom_fields && $custom_fields->fields[0] && $custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN) {
+ $form->addInput(array('type'=>'combobox','name'=>'option',
+ 'style'=>'width: 250px;',
+ 'value'=>$cl_cf_1,
+ 'data'=>$custom_fields->options,
+ 'empty'=>array(''=>$i18n->getKey('dropdown.all'))
+ ));
+}
+
+// Add controls for projects and tasks.
+if ($user->canManageTeam()) {
+ $project_list = ttProjectHelper::getProjects(); // Manager and co-managers can run reports on all active and inactive projects.
+} else if ($user->isClient()) {
+ $project_list = ttProjectHelper::getProjectsForClient();
+} else {
+ $project_list = ttProjectHelper::getAssignedProjects($user->id);
+}
+$form->addInput(array('type'=>'combobox',
+ 'onchange'=>'fillTaskDropdown(this.value);selectAssignedUsers(this.value);',
+ 'name'=>'project',
+ 'style'=>'width: 250px;',
+ 'data'=>$project_list,
+ 'datakeys'=>array('id','name'),
+ 'empty'=>array(''=>$i18n->getKey('dropdown.all'))
+));
+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;',
+ 'data'=>$task_list,
+ 'datakeys'=>array('id','name'),
+ 'empty'=>array(''=>$i18n->getKey('dropdown.all'))
+ ));
+}
+
+// Add include records control.
+$include_options = array('1'=>$i18n->getKey('form.reports.include_billable'),
+ '2'=>$i18n->getKey('form.reports.include_not_billable'));
+$form->addInput(array('type'=>'combobox',
+ 'name'=>'include_records',
+ 'style'=>'width: 250px;',
+ 'data'=>$include_options,
+ 'empty'=>array(''=>$i18n->getKey('dropdown.all'))
+));
+
+// Add invoiced / not invoiced selector.
+$invoice_options = array('1'=>$i18n->getKey('form.reports.include_invoiced'),
+ '2'=>$i18n->getKey('form.reports.include_not_invoiced'));
+$form->addInput(array('type'=>'combobox',
+ 'name'=>'invoice',
+ 'style'=>'width: 250px;',
+ 'data'=>$invoice_options,
+ 'empty'=>array(''=>$i18n->getKey('dropdown.all'))
+));
+
+$user_list = array();
+if ($user->canManageTeam() || $user->isClient()) {
+ // Prepare user and assigned projects arrays.
+ if ($user->canManageTeam())
+ $users = ttTeamHelper::getUsers(); // Active and inactive users for managers.
+ else if ($user->isClient())
+ $users = ttTeamHelper::getUsersForClient(); // Active and inactive users for clients.
+
+ foreach ($users as $single_user) {
+ $user_list[$single_user['id']] = $single_user['name'];
+ $projects = ttProjectHelper::getAssignedProjects($single_user['id']);
+ if ($projects) {
+ foreach ($projects as $single_project) {
+ $assigned_projects[$single_user['id']][] = $single_project['id'];
+ }
+ }
+ }
+ $row_count = ceil(count($user_list)/3);
+ $form->addInput(array('type'=>'checkboxgroup',
+ 'name'=>'users',
+ 'data'=>$user_list,
+ 'layout'=>'V',
+ 'groupin'=>$row_count,
+ 'style'=>'width: 100%;'
+ ));
+}
+
+// Add control for time period.
+$form->addInput(array('type'=>'combobox',
+ 'name'=>'period',
+ 'style'=>'width: 250px;',
+ 'data'=>array(INTERVAL_THIS_MONTH=>$i18n->getKey('dropdown.this_month'),
+ INTERVAL_LAST_MONTH=>$i18n->getKey('dropdown.last_month'),
+ INTERVAL_THIS_WEEK=>$i18n->getKey('dropdown.this_week'),
+ INTERVAL_LAST_WEEK=>$i18n->getKey('dropdown.last_week')),
+ 'empty'=>array(''=>$i18n->getKey('dropdown.select'))
+));
+// Add controls for start and end dates.
+$form->addInput(array('type'=>'datefield','maxlength'=>'20','name'=>'start_date'));
+$form->addInput(array('type'=>'datefield','maxlength'=>'20','name'=>'end_date'));
+
+// Add checkboxes for fields.
+if (in_array('cl', explode(',', $user->plugins)))
+ $form->addInput(array('type'=>'checkbox','name'=>'chclient','data'=>1));
+if (($user->canManageTeam() || $user->isClient()) && in_array('iv', explode(',', $user->plugins)))
+ $form->addInput(array('type'=>'checkbox','name'=>'chinvoice','data'=>1));
+if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode)
+ $form->addInput(array('type'=>'checkbox','name'=>'chproject','data'=>1));
+if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode)
+ $form->addInput(array('type'=>'checkbox','name'=>'chtask','data'=>1));
+if ((TYPE_START_FINISH == $user->record_type) || (TYPE_ALL == $user->record_type)) {
+ $form->addInput(array('type'=>'checkbox','name'=>'chstart','data'=>1));
+ $form->addInput(array('type'=>'checkbox','name'=>'chfinish','data'=>1));
+}
+$form->addInput(array('type'=>'checkbox','name'=>'chduration','data'=>1));
+$form->addInput(array('type'=>'checkbox','name'=>'chnote','data'=>1));
+if (defined('COST_ON_REPORTS') && isTrue(COST_ON_REPORTS))
+ $form->addInput(array('type'=>'checkbox','name'=>'chcost','data'=>1));
+// If we have a custom field - add a checkbox for it.
+if ($custom_fields && $custom_fields->fields[0])
+ $form->addInput(array('type'=>'checkbox','name'=>'chcf_1','data'=>1));
+
+// Add group by control.
+$group_by_options['no_grouping'] = $i18n->getKey('form.reports.group_by_no');
+$group_by_options['date'] = $i18n->getKey('form.reports.group_by_date');
+if ($user->canManageTeam() || $user->isClient())
+ $group_by_options['user'] = $i18n->getKey('form.reports.group_by_user');
+if (in_array('cl', explode(',', $user->plugins)) && !($user->isClient() && $user->client_id))
+ $group_by_options['client'] = $i18n->getKey('form.reports.group_by_client');
+if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode)
+ $group_by_options['project'] = $i18n->getKey('form.reports.group_by_project');
+if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode)
+ $group_by_options['task'] = $i18n->getKey('form.reports.group_by_task');
+if ($custom_fields && $custom_fields->fields[0] && $custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN) {
+ $group_by_options['cf_1'] = $custom_fields->fields[0]['label'];
+}
+$form->addInput(array('type'=>'combobox','onchange'=>'handleCheckboxes();','name'=>'group_by','data'=>$group_by_options));
+$form->addInput(array('type'=>'checkbox','name'=>'chtotalsonly','data'=>1));
+
+// Add text field for a new favorite report name.
+$form->addInput(array('type'=>'text','name'=>'new_fav_report','maxlength'=>'30','style'=>'width: 250px;'));
+// Save button.
+$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->getKey('button.save')));
+
+$form->addInput(array('type'=>'submit','name'=>'btn_generate','value'=>$i18n->getKey('button.generate')));
+
+// Create a bean (which is a mechanism to remember form values in session).
+$bean = new ActionForm('reportBean', $form, $request);
+// At this point form values are obtained from session if they are there...
+
+if (($request->getMethod() == 'GET') && !$bean->isSaved()) {
+ // No previous form data were found in session. Use the following default values.
+ $form->setValueByElement('users', array_keys($user_list));
+ $period = new Period(INTERVAL_THIS_MONTH, new DateAndTime($user->date_format));
+ $form->setValueByElement('start_date', $period->getBeginDate());
+ $form->setValueByElement('end_date', $period->getEndDate());
+ $form->setValueByElement('chclient', 1);
+ $form->setValueByElement('chinvoice', 0);
+ $form->setValueByElement('chproject', 1);
+ $form->setValueByElement('chstart', 1);
+ $form->setValueByElement('chduration', 1);
+ $form->setValueByElement('chcost', 0);
+ $form->setValueByElement('chtask', 1);
+ $form->setValueByElement('chfinish', 1);
+ $form->setValueByElement('chnote', 1);
+ $form->setValueByElement('chcf_1', 0);
+ $form->setValueByElement('chtotalsonly', 0);
+}
+
+$form->setValueByElement('fav_report_changed','');
+
+// Disable the Delete button when no favorite report is selected.
+if (!$bean->getAttribute('favorite_report') || ($bean->getAttribute('favorite_report') == -1))
+ $form->getElement('btn_delete')->setEnable(false);
+
+if ($request->getMethod() == 'POST') {
+ if((!$bean->getAttribute('btn_generate') && ($request->getParameter('fav_report_changed')))) {
+ // User changed favorite report. We need to load new values into the form.
+ if ($bean->getAttribute('favorite_report')) {
+ // This loads new favorite report options into the bean (into our form).
+ ttFavReportHelper::loadReport($user->id, $bean);
+
+ // If user selected no favorite report - mark all user checkboxes (most probable scenario).
+ if ($bean->getAttribute('favorite_report') == -1)
+ $form->setValueByElement('users', array_keys($user_list));
+
+ // Save form data in session for future use.
+ $bean->saveBean();
+ header('Location: reports.php');
+ exit();
+ }
+ } elseif ($bean->getAttribute('btn_save')) {
+ // User clicked the Save button. We need to save form options as new favorite report.
+ if (!ttValidString($bean->getAttribute('new_fav_report'))) $errors->add($i18n->getKey('error.field'), $i18n->getKey('form.reports.save_as_favorite'));
+
+ if ($errors->isEmpty()) {
+ $id = ttFavReportHelper::saveReport($user->id, $bean);
+ if (!$id)
+ $errors->add($i18n->getKey('error.db'));
+ if ($errors->isEmpty()) {
+ $bean->setAttribute('favorite_report', $id);
+ $bean->saveBean();
+ header('Location: reports.php');
+ exit();
+ }
+ }
+ } elseif($bean->getAttribute('btn_delete')) {
+ // Delete button pressed. User wants to delete a favorite report.
+ if ($bean->getAttribute('favorite_report')) {
+ ttFavReportHelper::deleteReport($bean->getAttribute('favorite_report'));
+ // Load default report.
+ $bean->setAttribute('favorite_report','');
+ $bean->setAttribute('new_fav_report', $report_list[0]['name']);
+ ttFavReportHelper::loadReport($user->id, $bean);
+ $form->setValueByElement('users', array_keys($user_list));
+ $bean->saveBean();
+ header('Location: reports.php');
+ exit();
+ }
+ } else {
+ // Generate button pressed. Check some values.
+ if (!$bean->getAttribute('period')) {
+ $start_date = new DateAndTime($user->date_format, $bean->getAttribute('start_date'));
+
+ if ($start_date->isError() || !$bean->getAttribute('start_date'))
+ $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.start_date'));
+
+ $end_date = new DateAndTime($user->date_format, $bean->getAttribute('end_date'));
+ if ($end_date->isError() || !$bean->getAttribute('end_date'))
+ $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.end_date'));
+
+ if ($start_date->compare($end_date) > 0)
+ $errors->add($i18n->getKey('error.interval'), $i18n->getKey('label.end_date'), $i18n->getKey('label.start_date'));
+ }
+
+ $bean->saveBean();
+
+ if ($errors->isEmpty()) {
+ // Now we can go ahead and create a report.
+ header('Location: report.php');
+ exit();
+ }
+ }
+}
+
+$smarty->assign('project_list', $project_list);
+$smarty->assign('task_list', $task_list);
+$smarty->assign('assigned_projects', $assigned_projects);
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('onload', 'onLoad="handleCheckboxes()"');
+$smarty->assign('title', $i18n->getKey('title.reports'));
+$smarty->assign('content_page_name', 'reports.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+/* css definitions for rtl languages */
+
+html {
+ direction: rtl;
+}
\ No newline at end of file
--- /dev/null
+<?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('form.ActionForm');
+import('ttTeamHelper');
+import('ttTaskHelper');
+
+// Access check.
+if (!ttAccessCheck(right_manage_team)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+$projects = ttTeamHelper::getActiveProjects($user->team_id);
+
+if ($request->getMethod() == 'POST') {
+ $cl_name = trim($request->getParameter('name'));
+ $cl_description = trim($request->getParameter('description'));
+ $cl_projects = $request->getParameter('projects');
+} else {
+ foreach ($projects as $project_item)
+ $cl_projects[] = $project_item['id'];
+}
+
+$form = new Form('taskForm');
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','style'=>'width: 250px;','value'=>$cl_name));
+$form->addInput(array('type'=>'textarea','name'=>'description','style'=>'width: 250px; height: 40px;','value'=>$cl_description));
+$form->addInput(array('type'=>'checkboxgroup','name'=>'projects','layout'=>'H','data'=>$projects,'datakeys'=>array('id','name'),'value'=>$cl_projects));
+$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.add')));
+
+if ($request->getMethod() == 'POST') {
+ // Validate user input.
+ if (!ttValidString($cl_name)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name'));
+ if (!ttValidString($cl_description, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.description'));
+
+ if ($errors->isEmpty()) {
+ if (!ttTaskHelper::getTaskByName($cl_name)) {
+ if (ttTaskHelper::insert(array(
+ 'team_id' => $user->team_id,
+ 'name' => $cl_name,
+ 'description' => $cl_description,
+ 'status' => ACTIVE,
+ 'projects' => $cl_projects))) {
+ header('Location: tasks.php');
+ exit();
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ } else
+ $errors->add($i18n->getKey('error.task_exists'));
+ }
+} // post
+
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('onload', 'onLoad="document.taskForm.name.focus()"');
+$smarty->assign('title', $i18n->getKey('title.add_task'));
+$smarty->assign('content_page_name', 'task_add.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttTaskHelper');
+import('form.Form');
+
+// Access check.
+if (!ttAccessCheck(right_manage_team)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+$cl_task_id = (int)$request->getParameter('id');
+$task = ttTaskHelper::getTask($cl_task_id);
+$task_to_delete = $task['name'];
+
+$form = new Form('taskDeleteForm');
+$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_task_id));
+$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete')));
+$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->getKey('button.cancel')));
+
+if ($request->getMethod() == 'POST') {
+ if ($request->getParameter('btn_delete')) {
+ if(ttTaskHelper::getTask($cl_task_id)) {
+ if (ttTaskHelper::delete($cl_task_id)) {
+ header('Location: tasks.php');
+ exit();
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ } else if ($request->getParameter('btn_cancel')) {
+ header('Location: tasks.php');
+ exit();
+ }
+} // post
+
+$smarty->assign('task_to_delete', $task_to_delete);
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('onload', 'onLoad="document.taskDeleteForm.btn_cancel.focus()"');
+$smarty->assign('title', $i18n->getKey('title.delete_task'));
+$smarty->assign('content_page_name', 'task_delete.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttTeamHelper');
+import('ttTaskHelper');
+
+// Access check.
+if (!ttAccessCheck(right_manage_team)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+$cl_task_id = (int)$request->getParameter('id');
+$projects = ttTeamHelper::getActiveProjects($user->team_id);
+
+if ($request->getMethod() == 'POST') {
+ $cl_name = trim($request->getParameter('name'));
+ $cl_description = trim($request->getParameter('description'));
+ $cl_status = $request->getParameter('status');
+ $cl_projects = $request->getParameter('projects');
+} else {
+ $task = ttTaskHelper::getTask($cl_task_id);
+ $cl_name = $task['name'];
+ $cl_description = $task['description'];
+ $cl_status = $task['status'];
+
+ $assigned_projects = ttTaskHelper::getAssignedProjects($cl_task_id);
+ foreach ($assigned_projects as $project_item)
+ $cl_projects[] = $project_item['id'];
+}
+
+$form = new Form('taskForm');
+$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_task_id));
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','style'=>'width: 250px;','value'=>$cl_name));
+$form->addInput(array('type'=>'textarea','name'=>'description','style'=>'width: 250px; height: 40px;','value'=>$cl_description));
+$form->addInput(array('type'=>'combobox','name'=>'status','value'=>$cl_status,
+ 'data'=>array(ACTIVE=>$i18n->getKey('dropdown.status_active'),INACTIVE=>$i18n->getKey('dropdown.status_inactive'))));
+$form->addInput(array('type'=>'checkboxgroup','name'=>'projects','layout'=>'H','data'=>$projects,'datakeys'=>array('id','name'),'value'=>$cl_projects));
+$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->getKey('button.save')));
+$form->addInput(array('type'=>'submit','name'=>'btn_copy','value'=>$i18n->getKey('button.copy')));
+
+if ($request->getMethod() == 'POST') {
+ // Validate user input.
+ if (!ttValidString($cl_name)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name'));
+ if (!ttValidString($cl_description, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.description'));
+
+ if ($errors->isEmpty()) {
+ if ($request->getParameter('btn_save')) {
+ $existing_task = ttTaskHelper::getTaskByName($cl_name);
+ if (!$existing_task || ($cl_task_id == $existing_task['id'])) {
+ // Update task information.
+ if (ttTaskHelper::update(array(
+ 'task_id' => $cl_task_id,
+ 'name' => $cl_name,
+ 'description' => $cl_description,
+ 'status' => $cl_status,
+ 'projects' => $cl_projects))) {
+ header('Location: tasks.php');
+ exit();
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ } else
+ $errors->add($i18n->getKey('error.task_exists'));
+ }
+
+ if ($request->getParameter('btn_copy')) {
+ if (!ttTaskHelper::getTaskByName($cl_name)) {
+ if (ttTaskHelper::insert(array(
+ 'team_id' => $user->team_id,
+ 'name' => $cl_name,
+ 'description' => $cl_description,
+ 'status' => $cl_status,
+ 'projects' => $cl_projects))) {
+ header('Location: tasks.php');
+ exit();
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ } else
+ $errors->add($i18n->getKey('error.task_exists'));
+ }
+ }
+} // post
+
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('title', $i18n->getKey('title.edit_task'));
+$smarty->assign('content_page_name', 'task_edit.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttTeamHelper');
+
+// Access check.
+if (!ttAccessCheck(right_manage_team)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+$smarty->assign('active_tasks', ttTeamHelper::getActiveTasks($user->team_id));
+$smarty->assign('inactive_tasks', ttTeamHelper::getInactiveTasks($user->team_id));
+$smarty->assign('title', $i18n->getKey('title.tasks'));
+$smarty->assign('content_page_name', 'tasks.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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');
+
+// This is a now removed check whether user browser supports cookies.
+// if (!isset($_COOKIE['tt_PHPSESSID'])) {
+ // This test gives a false-positive if user goes directly to this page
+ // as from a desktop shortcut (on first request only).
+ // die ("Your browser's cookie functionality is turned off. Please turn it on.");
+// }
+
+// Access check.
+if (!ttAccessCheck(right_data_entry)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+// 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 (in_array('cf', explode(',', $user->plugins))) {
+ require_once('plugins/CustomFields.class.php');
+ $custom_fields = new CustomFields($user->team_id);
+ $smarty->assign('custom_fields', $custom_fields);
+}
+
+// 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 (in_array('iv', explode(',', $user->plugins))) {
+ if ($request->getMethod() == 'POST') {
+ $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 && in_array('cl', explode(',', $user->plugins))) {
+ $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 (in_array('cl', explode(',', $user->plugins))) {
+ $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']);
+ $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')->setEnable(false);
+ $form->getElement('finish')->setEnable(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');"));
+$form->addInput(array('type'=>'textarea','name'=>'note','style'=>'width: 600px; height: 40px;','value'=>$cl_note));
+$form->addInput(array('type'=>'calendar','name'=>'date','value'=>$cl_date)); // calendar
+if (in_array('iv', explode(',', $user->plugins)))
+ $form->addInput(array('type'=>'checkbox','name'=>'billable','data'=>1,'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));
+ } else if ($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'))
+ ));
+ }
+}
+
+// Determine lock date. Time entries earlier than lock date cannot be created or modified.
+$lock_interval = $user->lock_interval;
+$lockdate = 0;
+if ($lock_interval > 0) {
+ $lockdate = new DateAndTime();
+ $lockdate->decDay($lock_interval);
+}
+
+// Submit.
+if ($request->getMethod() == 'POST') {
+ if ($request->getParameter('btn_submit')) {
+
+ // Validate user input.
+ if (in_array('cl', explode(',', $user->plugins)) && in_array('cm', explode(',', $user->plugins)) && !$cl_client)
+ $errors->add($i18n->getKey('error.client'));
+ if ($custom_fields) {
+ if (!ttValidString($cl_cf_1, !$custom_fields->fields[0]['required'])) $errors->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) $errors->add($i18n->getKey('error.project'));
+ }
+ if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) {
+ if (!$cl_task) $errors->add($i18n->getKey('error.task'));
+ }
+ if (!$cl_duration) {
+ if ('0' == $cl_duration)
+ $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.duration'));
+ else if ($cl_start || $cl_finish) {
+ if (!ttTimeHelper::isValidTime($cl_start))
+ $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.start'));
+ if ($cl_finish) {
+ if (!ttTimeHelper::isValidTime($cl_finish))
+ $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.finish'));
+ if (!ttTimeHelper::isValidInterval($cl_start, $cl_finish))
+ $errors->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)) {
+ $errors->add($i18n->getKey('error.empty'), $i18n->getKey('label.start'));
+ $errors->add($i18n->getKey('error.empty'), $i18n->getKey('label.finish'));
+ }
+ if ((TYPE_DURATION == $user->record_type) || (TYPE_ALL == $user->record_type))
+ $errors->add($i18n->getKey('error.empty'), $i18n->getKey('label.duration'));
+ }
+ } else {
+ if (!ttTimeHelper::isValidDuration($cl_duration))
+ $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.duration'));
+ }
+ if (!ttValidString($cl_note, true)) $errors->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))
+ $errors->add($i18n->getKey('error.future_date'));
+ }
+
+ // Prohibit creating time entries in locked interval.
+ if($lockdate && $selected_date->before($lockdate))
+ $errors->add($i18n->getKey('error.period_locked'));
+
+ // Prohibit creating another uncompleted record.
+ if ($errors->isEmpty()) {
+ if (($not_completed_rec = ttTimeHelper::getUncompleted($user->getActiveUser())) && (($cl_finish == '') && ($cl_duration == '')))
+ $errors->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 ($errors->isEmpty()) {
+ if (ttTimeHelper::overlaps($user->getActiveUser(), $cl_date, $cl_start, $cl_finish))
+ $errors->add($i18n->getKey('error.overlap'));
+ }
+
+ // Insert record.
+ if ($errors->isEmpty()) {
+ $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);
+ else if ($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();
+ }
+ $errors->add($i18n->getKey('error.db'));
+ }
+ }
+ else if ($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)
+ $errors->add($i18n->getKey('error.db'));
+ } else {
+ // Cannot complete, redirect for manual edit.
+ header('Location: time_edit.php?id='.$record_id);
+ exit();
+ }
+ }
+ else if ($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: time.php');
+ exit();
+ }
+ }
+}
+
+$week_total = ttTimeHelper::getTimeForWeek($user->getActiveUser(), $selected_date);
+
+$smarty->assign('week_total', $week_total);
+$smarty->assign('day_total', ttTimeHelper::getTimeForDay($user->getActiveUser(), $cl_date));
+$smarty->assign('time_records', ttTimeHelper::getRecords($user->getActiveUser(), $cl_date));
+$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', $selected_date->toString($user->date_format));
+$smarty->assign('title', $i18n->getKey('title.time'));
+$smarty->assign('content_page_name', 'time.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttTimeHelper');
+import('DateAndTime');
+
+// Access check.
+if (!ttAccessCheck(right_data_entry)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+// Use Custom Fields plugin if we have one.
+// if (file_exists("plugins/CustomFields.class.php")) {
+// require_once("plugins/CustomFields.class.php");
+// $custom_fields = new CustomFields($user->team_id);
+// }
+
+$cl_id = $request->getParameter('id');
+$time_rec = ttTimeHelper::getRecord($cl_id, $user->getActiveUser());
+
+// Prohibit deleting invoiced records.
+if ($time_rec['invoice_id']) die($i18n->getKey('error.sys'));
+
+// Escape comment for presentation.
+$time_rec['comment'] = htmlspecialchars($time_rec['comment']);
+
+if ($request->getMethod() == 'POST') {
+ if ($request->getParameter('delete_button')) { // Delete button pressed.
+
+ // Determine if it's okay to delete the record.
+ $item_date = new DateAndTime(DB_DATEFORMAT, $time_rec['date']);
+ // Determine lock date.
+ $lock_interval = $user->lock_interval;
+ $lockdate = 0;
+ if ($lock_interval > 0) {
+ $lockdate = new DateAndTime();
+ $lockdate->decDay($lock_interval);
+ }
+ // Determine if the record is uncompleted.
+ $uncompleted = ($time_rec['duration'] == '0:00');
+
+ if($lockdate && $item_date->before($lockdate) && !$uncompleted) {
+ $errors->add($i18n->getKey('error.period_locked'));
+ }
+
+ if ($errors->isEmpty()) {
+
+ // Delete the record.
+ $result = ttTimeHelper::delete($cl_id, $user->getActiveUser());
+
+ if ($result) {
+ header('Location: time.php');
+ exit();
+ } else {
+ $errors->add($i18n->getKey('error.db'));
+ }
+ }
+ }
+ if ($request->getParameter('cancel_button')) { // Cancel button pressed.
+ header('Location: time.php');
+ exit();
+ }
+}
+
+$form = new Form('timeRecordForm');
+$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_id));
+$form->addInput(array('type'=>'submit','name'=>'delete_button','value'=>$i18n->getKey('label.delete')));
+$form->addInput(array('type'=>'submit','name'=>'cancel_button','value'=>$i18n->getKey('button.cancel')));
+
+$smarty->assign('time_rec', $time_rec);
+$smarty->assign('forms', array($form->getName() => $form->toArray()));
+$smarty->assign('title', $i18n->getKey('title.delete_time_record'));
+$smarty->assign('content_page_name', 'time_delete.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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();
+}
+
+// Use custom fields plugin if it is enabled.
+if (in_array('cf', explode(',', $user->plugins))) {
+ require_once('plugins/CustomFields.class.php');
+ $custom_fields = new CustomFields($user->team_id);
+ $smarty->assign('custom_fields', $custom_fields);
+}
+
+$cl_id = $request->getParameter('id');
+
+// Get the time record we are editing.
+$time_rec = ttTimeHelper::getRecord($cl_id, $user->getActiveUser());
+
+// Prohibit editing invoiced records.
+if ($time_rec['invoice_id']) die($i18n->getKey('error.sys'));
+
+$item_date = new DateAndTime(DB_DATEFORMAT, $time_rec['date']);
+
+// Initialize variables.
+$cl_start = $cl_finish = $cl_duration = $cl_date = $cl_note = $cl_project = $cl_task = $cl_billable = null;
+if ($request->getMethod() == 'POST') {
+ $cl_start = trim($request->getParameter('start'));
+ $cl_finish = trim($request->getParameter('finish'));
+ $cl_duration = trim($request->getParameter('duration'));
+ $cl_date = $request->getParameter('date');
+ $cl_note = trim($request->getParameter('note'));
+ $cl_cf_1 = trim($request->getParameter('cf_1'));
+ $cl_client = $request->getParameter('client');
+ $cl_project = $request->getParameter('project');
+ $cl_task = $request->getParameter('task');
+ $cl_billable = 1;
+ if (in_array('iv', explode(',', $user->plugins)))
+ $cl_billable = $request->getParameter('billable');
+} else {
+ $cl_client = $time_rec['client_id'];
+ $cl_project = $time_rec['project_id'];
+ $cl_task = $time_rec['task_id'];
+ $cl_start = $time_rec['start'];
+ $cl_finish = $time_rec['finish'];
+ $cl_duration = $time_rec['duration'];
+ $cl_date = $item_date->toString($user->date_format);
+ $cl_note = $time_rec['comment'];
+
+ // If we have custom fields - obtain values for them.
+ if ($custom_fields) {
+ // Get custom field value for time record.
+ $fields = $custom_fields->get($time_rec['id']);
+ if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT)
+ $cl_cf_1 = $fields[0]['value'];
+ else if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN)
+ $cl_cf_1 = $fields[0]['option_id'];
+ }
+
+ $cl_billable = $time_rec['billable'];
+
+ // Add an info message to the form if we are editing an uncompleted record.
+ if (($cl_start == $cl_finish) && ($cl_duration == '0:00')) {
+ $cl_finish = '';
+ $cl_duration = '';
+ $messages->add($i18n->getKey('form.time_edit.uncompleted'));
+ }
+}
+
+// Initialize elements of 'timeRecordForm'.
+$form = new Form('timeRecordForm');
+
+// Dropdown for clients in MODE_TIME. Use all active clients.
+if (MODE_TIME == $user->tracking_mode && in_array('cl', explode(',', $user->plugins))) {
+ $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 (in_array('cl', explode(',', $user->plugins))) {
+ $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']);
+ $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')->setEnable(false);
+ $form->getElement('finish')->setEnable(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');"));
+$form->addInput(array('type'=>'datefield','name'=>'date','maxlength'=>'20','value'=>$cl_date));
+$form->addInput(array('type'=>'textarea','name'=>'note','style'=>'width: 250px; height: 200px;','value'=>$cl_note));
+// 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));
+ } else if ($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'))
+ ));
+ }
+}
+// Hidden control for record id.
+$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_id));
+if (in_array('iv', explode(',', $user->plugins)))
+ $form->addInput(array('type'=>'checkbox','name'=>'billable','data'=>1,'value'=>$cl_billable));
+$form->addInput(array('type'=>'hidden','name'=>'browser_today','value'=>'')); // User current date, which gets filled in on btn_save or btn_copy click.
+$form->addInput(array('type'=>'submit','name'=>'btn_save','onclick'=>'browser_today.value=get_date()','value'=>$i18n->getKey('button.save')));
+$form->addInput(array('type'=>'submit','name'=>'btn_copy','onclick'=>'browser_today.value=get_date()','value'=>$i18n->getKey('button.copy')));
+$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete')));
+
+if ($request->getMethod() == 'POST') {
+
+ // Validate user input.
+ if (in_array('cl', explode(',', $user->plugins)) && in_array('cm', explode(',', $user->plugins)) && !$cl_client)
+ $errors->add($i18n->getKey('error.client'));
+ if ($custom_fields) {
+ if (!ttValidString($cl_cf_1, !$custom_fields->fields[0]['required'])) $errors->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) $errors->add($i18n->getKey('error.project'));
+ }
+ if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) {
+ if (!$cl_task) $errors->add($i18n->getKey('error.task'));
+ }
+ if (!$cl_duration) {
+ if ('0' == $cl_duration)
+ $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.duration'));
+ else if ($cl_start || $cl_finish) {
+ if (!ttTimeHelper::isValidTime($cl_start))
+ $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.start'));
+ if ($cl_finish) {
+ if (!ttTimeHelper::isValidTime($cl_finish))
+ $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.finish'));
+ if (!ttTimeHelper::isValidInterval($cl_start, $cl_finish))
+ $errors->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)) {
+ $errors->add($i18n->getKey('error.empty'), $i18n->getKey('label.start'));
+ $errors->add($i18n->getKey('error.empty'), $i18n->getKey('label.finish'));
+ }
+ if ((TYPE_DURATION == $user->record_type) || (TYPE_ALL == $user->record_type))
+ $errors->add($i18n->getKey('error.empty'), $i18n->getKey('label.duration'));
+ }
+ } else {
+ if (!ttTimeHelper::isValidDuration($cl_duration))
+ $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.duration'));
+ }
+ if (!ttValidDate($cl_date)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.date'));
+ if (!ttValidString($cl_note, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.note'));
+ // Finished validating user input.
+
+ // Determine lock date.
+ $lock_interval = $user->lock_interval;
+ $lockdate = 0;
+ if ($lock_interval > 0) {
+ $lockdate = new DateAndTime();
+ $lockdate->decDay($lock_interval);
+ }
+
+ // This is a new date for the time record.
+ $new_date = new DateAndTime($user->date_format, $cl_date);
+
+ // Prohibit creating entries in future.
+ if (defined('FUTURE_ENTRIES') && !isTrue(FUTURE_ENTRIES)) {
+ $browser_today = new DateAndTime(DB_DATEFORMAT, $request->getParameter('browser_today', null));
+ if ($new_date->after($browser_today))
+ $errors->add($i18n->getKey('error.future_date'));
+ }
+
+ // Save record.
+ if ($request->getParameter('btn_save')) {
+ // We need to:
+ // 1) Prohibit saving locked time entries in any form.
+ // 2) Prohibit saving completed unlocked entries into locked interval.
+ // 3) Prohibit saving uncompleted unlocked entries when another uncompleted entry exists.
+
+ // Now, step by step.
+ if ($errors->isEmpty()) {
+ // 1) Prohibit saving locked time entries in any form.
+ if($lockdate && $item_date->before($lockdate))
+ $errors->add($i18n->getKey('error.period_locked'));
+ // 2) Prohibit saving completed unlocked entries into locked interval.
+ if($errors->isEmpty() && $lockdate && $new_date->before($lockdate))
+ $errors->add($i18n->getKey('error.period_locked'));
+ // 3) Prohibit saving uncompleted unlocked entries when another uncompleted entry exists.
+ $uncompleted = ($cl_finish == '' && $cl_duration == '');
+ if ($uncompleted) {
+ $not_completed_rec = ttTimeHelper::getUncompleted($user->getActiveUser());
+ if ($not_completed_rec && ($time_rec['id'] <> $not_completed_rec['id'])) {
+ // We have another not completed record.
+ $errors->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 ($errors->isEmpty()) {
+ if (ttTimeHelper::overlaps($user->getActiveUser(), $new_date->toString(DB_DATEFORMAT), $cl_start, $cl_finish, $cl_id))
+ $errors->add($i18n->getKey('error.overlap'));
+ }
+
+ // Now, an update.
+ if ($errors->isEmpty()) {
+ $res = ttTimeHelper::update(array(
+ 'id'=>$cl_id,
+ 'date'=>$new_date->toString(DB_DATEFORMAT),
+ '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));
+
+ // If we have custom fields - update values.
+ if ($res && $custom_fields) {
+ if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT)
+ $res = $custom_fields->update($cl_id, $custom_fields->fields[0]['id'], null, $cl_cf_1);
+ else if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN)
+ $res = $custom_fields->update($cl_id, $custom_fields->fields[0]['id'], $cl_cf_1, null);
+ }
+ if ($res)
+ {
+ header('Location: time.php?date='.$new_date->toString(DB_DATEFORMAT));
+ exit();
+ }
+ }
+ }
+
+ // Save as new record.
+ if ($request->getParameter('btn_copy')) {
+ // We need to:
+ // 1) Prohibit saving into locked interval.
+ // 2) Prohibit saving uncompleted unlocked entries when another uncompleted entry exists.
+
+ // Now, step by step.
+ if ($errors->isEmpty()) {
+ // 1) Prohibit saving into locked interval.
+ if($lockdate && $new_date->before($lockdate))
+ $errors->add($i18n->getKey('error.period_locked'));
+ // 2) Prohibit saving uncompleted unlocked entries when another uncompleted entry exists.
+ $uncompleted = ($cl_finish == '' && $cl_duration == '');
+ if ($uncompleted) {
+ $not_completed_rec = ttTimeHelper::getUncompleted($user->getActiveUser());
+ if ($not_completed_rec) {
+ // We have another not completed record.
+ $errors->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 ($errors->isEmpty()) {
+ if (ttTimeHelper::overlaps($user->getActiveUser(), $new_date->toString(DB_DATEFORMAT), $cl_start, $cl_finish))
+ $errors->add($i18n->getKey('error.overlap'));
+ }
+
+ // Now, a new insert.
+ if ($errors->isEmpty()) {
+
+ $id = ttTimeHelper::insert(array(
+ 'date'=>$new_date->toString(DB_DATEFORMAT),
+ '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.
+ $res = true;
+ if ($id && $custom_fields && $cl_cf_1) {
+ if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT)
+ $res = $custom_fields->insert($id, $custom_fields->fields[0]['id'], null, $cl_cf_1);
+ else if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN)
+ $res = $custom_fields->insert($id, $custom_fields->fields[0]['id'], $cl_cf_1, null);
+ }
+ if ($id && $res) {
+ header('Location: time.php?date='.$new_date->toString(DB_DATEFORMAT));
+ exit();
+ }
+ $errors->add($i18n->getKey('error.db'));
+ }
+ }
+
+ if ($request->getParameter('btn_delete')) {
+ header("Location: time_delete.php?id=$cl_id");
+ exit();
+ }
+} // End of if ($request->getMethod() == "POST")
+
+$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('title', $i18n->getKey('title.edit_time_record'));
+$smarty->assign('content_page_name', 'time_edit.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('form.ActionForm');
+import('ttReportHelper');
+
+// Access check.
+if (!ttAccessCheck(right_view_reports)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+// Use custom fields plugin if it is enabled.
+if (in_array('cf', explode(',', $user->plugins))) {
+ require_once('plugins/CustomFields.class.php');
+ $custom_fields = new CustomFields($user->team_id);
+}
+
+// Report settings are stored in session bean before we get here.
+$bean = new ActionForm('reportBean', new Form('reportForm'), $request);
+
+// At the moment, we distinguish 2 types of export to file:
+// 1) export to xml
+// 2) export to csv
+$type = $request->getParameter('type');
+
+// Also, there are 2 variations of report: totals only, or normal. Totals only means that the report
+// is grouped by (either date, user, client, project, task or cf_1) and user only needs to see subtotals by group.
+$totals_only = $bean->getAttribute('chtotalsonly');
+
+// Obtain items.
+if ($totals_only)
+ $subtotals = ttReportHelper::getSubtotals($bean);
+else
+ $items = ttReportHelper::getItems($bean);
+
+header('Pragma: public'); // This is needed for IE8 to download files over https.
+header('Content-Type: text/html; charset=utf-8');
+header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
+header('Cache-Control: no-store, no-cache, must-revalidate');
+header('Cache-Control: post-check=0, pre-check=0', false);
+header('Cache-Control: private', false);
+
+// Handle 2 cases of possible exports individually.
+
+// 1) entries exported to xml
+if ('xml' == $type) {
+ header('Content-Type: application/xml');
+ header('Content-Disposition: attachment; filename="timesheet.xml"');
+
+ print "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ print "<rows>\n";
+
+ $group_by = $bean->getAttribute('group_by');
+ if ($totals_only) {
+ // Totals only report. Print subtotals.
+ foreach ($subtotals as $subtotal) {
+ print "<row>\n";
+ print "\t<".$group_by."><![CDATA[".$subtotal['name']."]]></".$group_by.">\n";
+ if ($bean->getAttribute('chduration')) {
+ $val = $subtotal['time'];
+ if($val && defined('EXPORT_DECIMAL_DURATION') && isTrue(EXPORT_DECIMAL_DURATION))
+ $val = time_to_decimal($val);
+ print "\t<duration><![CDATA[".$val."]]></duration>\n";
+ }
+ if ($bean->getAttribute('chcost')) {
+ print "\t<cost><![CDATA[";
+ if ($user->canManageTeam() || $user->isClient())
+ print $subtotal['cost'];
+ else
+ print $subtotal['expenses'];
+ print "]]></cost>\n";
+ }
+ print "</row>\n";
+ }
+ } else {
+ // Normal report.
+ foreach ($items as $item) {
+ print "<row>\n";
+
+ print "\t<date><![CDATA[".$item['date']."]]></date>\n";
+ if ($user->canManageTeam() || $user->isClient()) print "\t<user><![CDATA[".$item['user']."]]></user>\n";
+ if ($bean->getAttribute('chclient')) print "\t<client><![CDATA[".$item['client']."]]></client>\n";
+ if ($bean->getAttribute('chproject')) print "\t<project><![CDATA[".$item['project']."]]></project>\n";
+ if ($bean->getAttribute('chtask')) print "\t<task><![CDATA[".$item['task']."]]></task>\n";
+ if ($bean->getAttribute('chcf_1')) print "\t<cf_1><![CDATA[".$item['cf_1']."]]></cf_1>\n";
+ if ($bean->getAttribute('chstart')) print "\t<start><![CDATA[".$item['start']."]]></start>\n";
+ if ($bean->getAttribute('chfinish')) print "\t<finish><![CDATA[".$item['finish']."]]></finish>\n";
+ if ($bean->getAttribute('chduration')) {
+ $duration = $item['duration'];
+ if($duration && defined('EXPORT_DECIMAL_DURATION') && isTrue(EXPORT_DECIMAL_DURATION))
+ $duration = time_to_decimal($duration);
+ print "\t<duration><![CDATA[".$duration."]]></duration>\n";
+ }
+ if ($bean->getAttribute('chnote')) print "\t<note><![CDATA[".$item['note']."]]></note>\n";
+ if ($bean->getAttribute('chcost')) {
+ print "\t<cost><![CDATA[";
+ if ($user->canManageTeam() || $user->isClient())
+ print $item['cost'];
+ else
+ print $item['expense'];
+ print "]]></cost>\n";
+ }
+ if ($bean->getAttribute('chinvoice')) print "\t<invoice><![CDATA[".$item['invoice']."]]></invoice>\n";
+
+ print "</row>\n";
+ }
+ }
+
+ print "</rows>";
+}
+
+// 2) entries exported to csv
+if ('csv' == $type) {
+ header('Content-Type: application/csv');
+ header('Content-Disposition: attachment; filename="timesheet.csv"');
+
+ // Print UTF8 BOM first to identify encoding.
+ $bom = chr(239).chr(187).chr(191); // 0xEF 0xBB 0xBF in the beginning of the file is UTF8 BOM.
+ print $bom; // Without this Excel does not display UTF8 characters properly.
+
+ $group_by = $bean->getAttribute('group_by');
+ if ($totals_only) {
+ // Totals only report.
+
+ // Determine group_by header.
+ if ('cf_1' == $group_by)
+ $group_by_header = $custom_fields->fields[0]['label'];
+ else {
+ $key = 'label.'.$group_by;
+ $group_by_header = $i18n->getKey($key);
+ }
+
+ // Print headers.
+ print '"'.$group_by_header.'"';
+ if ($bean->getAttribute('chduration')) print ',"'.$i18n->getKey('label.duration').'"';
+ if ($bean->getAttribute('chcost')) print ',"'.$i18n->getKey('label.cost').'"';
+ print "\n";
+
+ // Print subtotals.
+ foreach ($subtotals as $subtotal) {
+ print '"'.$subtotal['name'].'"';
+ if ($bean->getAttribute('chduration')) {
+ $val = $subtotal['time'];
+ if($val && defined('EXPORT_DECIMAL_DURATION') && isTrue(EXPORT_DECIMAL_DURATION))
+ $val = time_to_decimal($val);
+ print ',"'.$val.'"';
+ }
+ if ($bean->getAttribute('chcost')) {
+ if ($user->canManageTeam() || $user->isClient())
+ print ',"'.$subtotal['cost'].'"';
+ else
+ print ',"'.$subtotal['expenses'].'"';
+ }
+ print "\n";
+ }
+ } else {
+ // Normal report. Print headers.
+ print '"'.$i18n->getKey('label.date').'"';
+ if ($user->canManageTeam() || $user->isClient()) print ',"'.$i18n->getKey('label.user').'"';
+ if ($bean->getAttribute('chclient')) print ',"'.$i18n->getKey('label.client').'"';
+ if ($bean->getAttribute('chproject')) print ',"'.$i18n->getKey('label.project').'"';
+ if ($bean->getAttribute('chtask')) print ',"'.$i18n->getKey('label.task').'"';
+ if ($bean->getAttribute('chcf_1')) print ',"'.$custom_fields->fields[0]['label'].'"';
+ if ($bean->getAttribute('chstart')) print ',"'.$i18n->getKey('label.start').'"';
+ if ($bean->getAttribute('chfinish')) print ',"'.$i18n->getKey('label.finish').'"';
+ if ($bean->getAttribute('chduration')) print ',"'.$i18n->getKey('label.duration').'"';
+ if ($bean->getAttribute('chnote')) print ',"'.$i18n->getKey('label.note').'"';
+ if ($bean->getAttribute('chcost')) print ',"'.$i18n->getKey('label.cost').'"';
+ if ($bean->getAttribute('chinvoice')) print ',"'.$i18n->getKey('label.invoice').'"';
+ print "\n";
+
+ // Print items.
+ foreach ($items as $item) {
+ print '"'.$item['date'].'"';
+ if ($user->canManageTeam() || $user->isClient()) print ',"'.str_replace('"','""',$item['user']).'"';
+ if ($bean->getAttribute('chclient')) print ',"'.str_replace('"','""',$item['client']).'"';
+ if ($bean->getAttribute('chproject')) print ',"'.str_replace('"','""',$item['project']).'"';
+ if ($bean->getAttribute('chtask')) print ',"'.str_replace('"','""',$item['task']).'"';
+ if ($bean->getAttribute('chcf_1')) print ',"'.str_replace('"','""',$item['cf_1']).'"';
+ if ($bean->getAttribute('chstart')) print ',"'.$item['start'].'"';
+ if ($bean->getAttribute('chfinish')) print ',"'.$item['finish'].'"';
+ if ($bean->getAttribute('chduration')) {
+ $val = $item['duration'];
+ if($val && defined('EXPORT_DECIMAL_DURATION') && isTrue(EXPORT_DECIMAL_DURATION))
+ $val = time_to_decimal($val);
+ print ',"'.$val.'"';
+ }
+ if ($bean->getAttribute('chnote')) print ',"'.str_replace('"','""',$item['note']).'"';
+ if ($bean->getAttribute('chcost')) {
+ if ($user->canManageTeam() || $user->isClient())
+ print ',"'.$item['cost'].'"';
+ else
+ print ',"'.$item['expense'].'"';
+ }
+ if ($bean->getAttribute('chinvoice')) print ',"'.str_replace('"','""',$item['invoice']).'"';
+ print "\n";
+ }
+ }
+}
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttTeamHelper');
+import('ttUserHelper');
+import('form.Table');
+import('form.TableColumn');
+
+// Access check.
+if (!ttAccessCheck(right_manage_team)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+// Use the "limit" plugin if we have one. Ignore include errors.
+// The "limit" plugin is not required for normal operation of the Time Tracker.
+@include('plugins/limit/user_add.php');
+
+if (in_array('cl', explode(',', $user->plugins)))
+ $clients = ttTeamHelper::getActiveClients($user->team_id);
+
+$assigned_projects = array();
+if ($request->getMethod() == 'POST') {
+ $cl_name = trim($request->getParameter('name'));
+ $cl_login = trim($request->getParameter('login'));
+ if (!$auth->isPasswordExternal()) {
+ $cl_password1 = $request->getParameter('pas1');
+ $cl_password2 = $request->getParameter('pas2');
+ }
+ $cl_email = trim($request->getParameter('email'));
+ $cl_role = $request->getParameter('role');
+ if (!$cl_role) $cl_role = ROLE_USER;
+ $cl_client_id = $request->getParameter('client');
+ $cl_rate = $request->getParameter('rate');
+ $cl_projects = $request->getParameter('projects');
+ if (is_array($cl_projects)) {
+ foreach ($cl_projects as $p) {
+ if (ttValidFloat($request->getParameter('rate_'.$p), true)) {
+ $project_with_rate = array();
+ $project_with_rate['id'] = $p;
+ $project_with_rate['rate'] = $request->getParameter('rate_'.$p);
+ $assigned_projects[] = $project_with_rate;
+ } else
+ $errors->add($i18n->getKey('error.field'), 'rate_'.$p);
+ }
+ }
+}
+
+$form = new Form('userForm');
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','style'=>'width: 300px;','value'=>$cl_name));
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'login','style'=>'width: 300px;','value'=>$cl_login));
+if (!$auth->isPasswordExternal()) {
+ $form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'pas1','aspassword'=>true,'value'=>$cl_password1));
+ $form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'pas2','aspassword'=>true,'value'=>$cl_password2));
+}
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'email','value'=>$cl_email));
+
+$roles[ROLE_USER] = $i18n->getKey('label.user');
+$roles[ROLE_COMANAGER] = $i18n->getKey('form.users.comanager');
+if (in_array('cl', explode(',', $user->plugins)))
+ $roles[ROLE_CLIENT] = $i18n->getKey('label.client');
+$form->addInput(array('type'=>'combobox','onchange'=>'handleClientControl()','name'=>'role','value'=>$cl_role,'data'=>$roles));
+if (in_array('cl', explode(',', $user->plugins)))
+ $form->addInput(array('type'=>'combobox','name'=>'client','value'=>$cl_client_id,'data'=>$clients,'datakeys'=>array('id', 'name'),'empty'=>array(''=>$i18n->getKey('dropdown.select'))));
+
+$form->addInput(array('type'=>'floatfield','maxlength'=>'10','name'=>'rate','format'=>'.2','value'=>$cl_rate));
+
+$projects = ttTeamHelper::getActiveProjects($user->team_id);
+
+// Define classes for the projects table.
+class NameCellRenderer extends DefaultCellRenderer {
+ function render(&$table, $value, $row, $column, $selected = false) {
+ $this->setOptions(array('width'=>200,'valign'=>'top'));
+ $this->setValue('<label for = "'.$table->getName().'_'.$row.'">'.htmlspecialchars($value).'</label>');
+ return $this->toString();
+ }
+}
+class RateCellRenderer extends DefaultCellRenderer {
+ function render(&$table, $value, $row, $column, $selected = false) {
+ global $assigned_projects;
+ $field = new FloatField('rate_'.$table->getValueAtName($row, 'id'), $table->getValueAtName($row, 'p_rate'));
+ $field->setFormName($table->getFormName());
+ $field->setLocalization($GLOBALS['I18N']);
+ $field->setSize(5);
+ $field->setFormat('.2');
+ foreach ($assigned_projects as $p) {
+ if ($p['id'] == $table->getValueAtName($row,'id')) $field->setValue($p['rate']);
+ }
+ $this->setValue($field->toStringControl());
+ return $this->toString();
+ }
+}
+// Create projects table.
+$table = new Table('projects');
+$table->setIAScript('setDefaultRate');
+$table->setTableOptions(array('width'=>'100%','cellspacing'=>'1','cellpadding'=>'3','border'=>'0'));
+$table->setRowOptions(array('valign'=>'top','class'=>'tableHeader'));
+$table->setData($projects);
+$table->setKeyField('id');
+$table->setValue($cl_projects);
+$table->addColumn(new TableColumn('name', $i18n->getKey('label.project'), new NameCellRenderer()));
+$table->addColumn(new TableColumn('p_rate', $i18n->getKey('form.users.rate'), new RateCellRenderer()));
+$form->addInputElement($table);
+
+$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.submit')));
+
+if ($request->getMethod() == 'POST') {
+ // Validate user input.
+ if (!ttValidString($cl_name)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.person_name'));
+ if (!ttValidString($cl_login)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.login'));
+ if (!$auth->isPasswordExternal()) {
+ if (!ttValidString($cl_password1)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.password'));
+ if (!ttValidString($cl_password2)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.confirm_password'));
+ if ($cl_password1 !== $cl_password2)
+ $errors->add($i18n->getKey('error.not_equal'), $i18n->getKey('label.password'), $i18n->getKey('label.confirm_password'));
+ }
+ if (!ttValidEmail($cl_email, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.email'));
+ if (!ttValidFloat($cl_rate, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('form.users.default_rate'));
+
+ if ($errors->isEmpty()) {
+ if (!ttUserHelper::getUserByLogin($cl_login)) {
+ $fields = array(
+ 'name' => $cl_name,
+ 'login' => $cl_login,
+ 'password' => $cl_password1,
+ 'rate' => $cl_rate,
+ 'team_id' => $user->team_id,
+ 'role' => $cl_role,
+ 'client_id' => $cl_client_id,
+ 'projects' => $assigned_projects,
+ 'email' => $cl_email);
+ if (ttUserHelper::insert($fields)) {
+ header('Location: users.php');
+ exit();
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ } else
+ $errors->add($i18n->getKey('error.user_exists'));
+ }
+} // post
+
+$smarty->assign('auth_external', $auth->isPasswordExternal());
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('onload', 'onLoad="document.userForm.name.focus();handleClientControl();"');
+$smarty->assign('title', $i18n->getKey('title.add_user'));
+$smarty->assign('content_page_name', 'user_add.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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');
+
+// Access check.
+if (!ttAccessCheck(right_manage_team)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+// Get user id we are deleting from the request.
+// A cast to int is for safety against manipulation of request parameter (sql injection).
+$user_id = (int) $request->getParameter('id');
+
+// We need user name and login to display.
+$user_details = ttUserHelper::getUserDetails($user_id);
+
+// Security checks.
+$ok_to_go = $user->canManageTeam(); // Are we authorized for user deletes?
+if ($ok_to_go) $ok_to_go = $ok_to_go && $user_details; // Are we deleting a real user?
+if ($ok_to_go) $ok_to_go = $ok_to_go && ($user->team_id == $user_details['team_id']); // User belongs to our team?
+if ($ok_to_go && $user->isCoManager() && (ROLE_COMANAGER == $user_details['role']))
+ $ok_to_go = ($user->id == $user_details['id']); // Comanager is not allowed to delete other comanagers.
+if ($ok_to_go && $user->isCoManager() && (ROLE_MANAGER == $user_details['role']))
+ $ok_to_go = false; // Comanager is not allowed to delete a manager.
+
+if (!$ok_to_go)
+ die ($i18n->getKey('error.sys'));
+else
+ $smarty->assign('user_to_delete', $user_details['name']." (".$user_details['login'].")");
+
+// Create confirmation form.
+$form = new Form('userDeleteForm');
+$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$user_id));
+$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete')));
+$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->getKey('button.cancel')));
+
+if ($request->getMethod() == 'POST') {
+ if ($request->getParameter('btn_delete')) {
+ if (ttUserHelper::markDeleted($user_id)) {
+ // If we deleted the "on behalf" user reset its info in session.
+ if ($user_id == $user->behalf_id) {
+ unset($_SESSION['behalf_id']);
+ unset($_SESSION['behalf_name']);
+ }
+ // If we deleted our own account, do housekeeping and logout.
+ if ($user->id == $user_id) {
+ // Remove tt_login cookie that stores login name.
+ unset($_COOKIE['tt_login']);
+ setcookie('tt_login', NULL, -1);
+
+ $auth->doLogout();
+ header('Location: login.php');
+ } else {
+ header('Location: users.php');
+ }
+ exit();
+ } else {
+ $errors->add($i18n->getKey('error.db'));
+ }
+ }
+ if ($request->getParameter('btn_cancel')) {
+ header('Location: users.php');
+ exit();
+ }
+}
+
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('title', $i18n->getKey('title.delete_user'));
+$smarty->assign('content_page_name', 'user_delete.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttProjectHelper');
+import('ttTeamHelper');
+import('ttUserHelper');
+import('form.Table');
+import('form.TableColumn');
+
+// Access check.
+if (!ttAccessCheck(right_manage_team)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+// Get user id we are editing from the request.
+$user_id = (int) $request->getParameter('id');
+
+// Get user details.
+$user_details = ttUserHelper::getUserDetails($user_id);
+
+// Security checks.
+$ok_to_go = $user->canManageTeam(); // Are we authorized for user management?
+if ($ok_to_go) $ok_to_go = $ok_to_go && $user_details; // Are we editing a real user?
+if ($ok_to_go) $ok_to_go = $ok_to_go && ($user->team_id == $user_details['team_id']); // User belongs to our team?
+if ($ok_to_go && $user->isCoManager() && (ROLE_COMANAGER == $user_details['role']))
+ $ok_to_go = ($user->id == $user_details['id']); // Comanager is not allowed to edit other comanagers.
+if ($ok_to_go && $user->isCoManager() && (ROLE_MANAGER == $user_details['role']))
+ $ok_to_go = false; // Comanager is not allowed to edit a manager.
+if (!$ok_to_go) {
+ die ($i18n->getKey('error.sys'));
+}
+
+if (in_array('cl', explode(',', $user->plugins)))
+ $clients = ttTeamHelper::getActiveClients($user->team_id);
+
+$projects = ttTeamHelper::getActiveProjects($user->team_id);
+$assigned_projects = array();
+
+if ($request->getMethod() == 'POST') {
+ $cl_name = trim($request->getParameter('name'));
+ $cl_login = trim($request->getParameter('login'));
+ if (!$auth->isPasswordExternal()) {
+ $cl_password1 = $request->getParameter('pas1');
+ $cl_password2 = $request->getParameter('pas2');
+ }
+ $cl_email = trim($request->getParameter('email'));
+ $cl_role = $request->getParameter('role');
+ $cl_client_id = $request->getParameter('client');
+ $cl_status = $request->getParameter('status');
+ $cl_rate = $request->getParameter('rate');
+ $cl_projects = $request->getParameter('projects');
+ if (is_array($cl_projects)) {
+ foreach ($cl_projects as $p) {
+ if (ttValidFloat($request->getParameter('rate_'.$p), true)) {
+ $project_with_rate = array();
+ $project_with_rate['id'] = $p;
+ $project_with_rate['rate'] = $request->getParameter('rate_'.$p);
+ $assigned_projects[] = $project_with_rate;
+ } else
+ $errors->add($i18n->getKey('error.field'), 'rate_'.$p);
+ }
+ }
+} else {
+ $cl_name = $user_details['name'];
+ $cl_login = $user_details['login'];
+ $cl_email = $user_details['email'];
+ $cl_rate = str_replace('.', $user->decimal_mark, $user_details['rate']);
+ $cl_role = $user_details['role'];
+ $cl_client_id = $user_details['client_id'];
+ $cl_status = $user_details['status'];
+ $cl_projects = array();
+ $assigned_projects = ttProjectHelper::getAssignedProjects($user_id);
+ foreach($assigned_projects as $p) {
+ $cl_projects[] = $p['id'];
+ }
+}
+
+$form = new Form('userForm');
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','style'=>'width: 300px;','value'=>$cl_name));
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'login','style'=>'width: 300px;','value'=>$cl_login));
+if (!$auth->isPasswordExternal()) {
+ $form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'pas1','aspassword'=>true,'value'=>$cl_password1));
+ $form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'pas2','aspassword'=>true,'value'=>$cl_password2));
+}
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'email','style'=>'width: 300px;','value'=>$cl_email));
+
+$roles[ROLE_USER] = $i18n->getKey('label.user');
+$roles[ROLE_COMANAGER] = $i18n->getKey('form.users.comanager');
+if (in_array('cl', explode(',', $user->plugins)))
+ $roles[ROLE_CLIENT] = $i18n->getKey('label.client');
+$form->addInput(array('type'=>'combobox','onchange'=>'handleClientControl()','name'=>'role','value'=>$cl_role,'data'=>$roles));
+if (in_array('cl', explode(',', $user->plugins)))
+ $form->addInput(array('type'=>'combobox','name'=>'client','value'=>$cl_client_id,'data'=>$clients,'datakeys'=>array('id', 'name'),'empty'=>array(''=>$i18n->getKey('dropdown.select'))));
+
+$form->addInput(array('type'=>'combobox','name'=>'status','value'=>$cl_status,
+ 'data'=>array(ACTIVE=>$i18n->getKey('dropdown.status_active'),INACTIVE=>$i18n->getKey('dropdown.status_inactive'))));
+$form->addInput(array('type'=>'floatfield','maxlength'=>'10','name'=>'rate','format'=>'.2','value'=>$cl_rate));
+
+// Define classes for the projects table.
+class NameCellRenderer extends DefaultCellRenderer {
+ function render(&$table, $value, $row, $column, $selected = false) {
+ $this->setOptions(array('width'=>200,'valign'=>'top'));
+ $this->setValue('<label for = "'.$table->getName().'_'.$row.'">'.htmlspecialchars($value).'</label>');
+ return $this->toString();
+ }
+}
+class RateCellRenderer extends DefaultCellRenderer {
+ function render(&$table, $value, $row, $column, $selected = false) {
+ global $assigned_projects;
+ $field = new FloatField('rate_'.$table->getValueAtName($row,'id'), $table->getValueAtName($row, 'p_rate'));
+ $field->setFormName($table->getFormName());
+ $field->setLocalization($GLOBALS['I18N']);
+ $field->setSize(5);
+ $field->setFormat('.2');
+ foreach ($assigned_projects as $p) {
+ if ($p['id'] == $table->getValueAtName($row,'id')) $field->setValue($p['rate']);
+ }
+ $this->setValue($field->toStringControl());
+ return $this->toString();
+ }
+}
+// Create projects table.
+$table = new Table('projects');
+$table->setIAScript('setRate');
+$table->setTableOptions(array('width'=>'100%','cellspacing'=>'1','cellpadding'=>'3','border'=>'0'));
+$table->setRowOptions(array('valign'=>'top','class'=>'tableHeader'));
+$table->setData($projects);
+$table->setKeyField('id');
+$table->setValue($cl_projects);
+$table->addColumn(new TableColumn('name', $i18n->getKey('label.project'), new NameCellRenderer()));
+$table->addColumn(new TableColumn('p_rate', $i18n->getKey('form.users.rate'), new RateCellRenderer()));
+$form->addInputElement($table);
+
+$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$user_id));
+$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.save')));
+
+if ($request->getMethod() == 'POST') {
+ // Validate user input.
+ if (!ttValidString($cl_name)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.person_name'));
+ if (!ttValidString($cl_login)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.login'));
+ if (!$auth->isPasswordExternal() && ($cl_password1 || $cl_password2)) {
+ if (!ttValidString($cl_password1)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.password'));
+ if (!ttValidString($cl_password2)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.confirm_password'));
+ if ($cl_password1 !== $cl_password2)
+ $errors->add($i18n->getKey('error.not_equal'), $i18n->getKey('label.password'), $i18n->getKey('label.confirm_password'));
+ }
+ if (!ttValidEmail($cl_email, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.email'));
+ if (!ttValidFloat($cl_rate, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('form.users.default_rate'));
+
+ if ($errors->isEmpty()) {
+ $existing_user = ttUserHelper::getUserByLogin($cl_login);
+ if (!$existing_user || ($user_id == $existing_user['id'])) {
+
+ $fields = array(
+ 'name' => $cl_name,
+ 'login' => $cl_login,
+ 'password' => $cl_password1,
+ 'email' => $cl_email,
+ 'status' => $cl_status,
+ 'rate' => $cl_rate,
+ 'projects' => $assigned_projects);
+ if (right_assign_roles & $user->rights) {
+ $fields['role'] = $cl_role;
+ $fields['client_id'] = $cl_client_id;
+ }
+
+ if (ttUserHelper::update($user_id, $fields)) {
+
+ // If our own login changed, set new one in cookie to remember it.
+ if (($user_id == $user->id) && ($user->login != $cl_login)) {
+ setcookie('tt_login', $cl_login, time() + COOKIE_EXPIRE, '/');
+ }
+
+ // In case the name of the "on behalf" user has changed - set it in session.
+ if (($user->behalf_id == $user_id) && ($user->behalf_name != $cl_name)) {
+ $_SESSION['behalf_name'] = $cl_name;
+ }
+
+ // If we deactivated our own account, do housekeeping and logout.
+ if ($user->id == $user_id && !is_null($cl_status) && $cl_status == INACTIVE) {
+ // Remove tt_login cookie that stores login name.
+ unset($_COOKIE['tt_login']);
+ setcookie('tt_login', NULL, -1);
+
+ $auth->doLogout();
+ header('Location: login.php');
+ exit();
+ }
+
+ header('Location: users.php');
+ exit();
+
+ } else
+ $errors->add($i18n->getKey('error.db'));
+ } else
+ $errors->add($i18n->getKey('error.user_exists'));
+ }
+} // post
+
+$rates = ttProjectHelper::getRates($user_id);
+$smarty->assign('rates', $rates);
+
+$smarty->assign('auth_external', $auth->isPasswordExternal());
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('onload', 'onLoad="document.userForm.name.focus();handleClientControl();"');
+$smarty->assign('user_id', $user_id);
+$smarty->assign('title', $i18n->getKey('title.edit_user'));
+$smarty->assign('content_page_name', 'user_edit.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file
--- /dev/null
+<?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('ttTeamHelper');
+
+// Access check.
+if (!ttAccessCheck(right_data_entry)) {
+ header('Location: access_denied.php');
+ exit();
+}
+
+// Get users.
+$active_users = ttTeamHelper::getActiveUsers(array('getAllFields'=>true));
+if($user->canManageTeam()) {
+ $can_delete_manager = (1 == count($active_users));
+ $inactive_users = ttTeamHelper::getInactiveUsers($user->team_id, true);
+}
+
+$smarty->assign('active_users', $active_users);
+$smarty->assign('inactive_users', $inactive_users);
+$smarty->assign('can_delete_manager', $can_delete_manager);
+$smarty->assign('title', $i18n->getKey('title.users'));
+$smarty->assign('content_page_name', 'users.tpl');
+$smarty->display('index.tpl');
+?>
\ No newline at end of file