expands mobile page access (#9)
authorTaylor Hurt <taylor.a.hurt@gmail.com>
Fri, 26 Aug 2016 16:28:42 +0000 (12:28 -0400)
committeranuko <support@anuko.com>
Fri, 26 Aug 2016 16:28:42 +0000 (16:28 +0000)
* expands mobile page access

add mobile .tpl files;
modify mobile/header.tpl to accomodate;
add mobile php files;
fix template paths to mobile/ and path to initialize.php;
add mobile-table CSS class;
change main mobile/ pages to use mobile-table class;
fix path to subm_bg.gif;
remove Top Menu (the one with black backgroud);
remove Charts, Reports, Invoices, Export from submenu;
remove fixed width for many input elements;
remove nowrap attribute in header.tpl submenu;
add inline style for background-repeat in header.tpl submenu;
prevent textarea's from being resizable horizontally, allow them to be resizable vertically;
remove fixed width from textarea cols attribute;
add mobile-textarea CSS class;
fix bug in project_edit.php -- onload event referring to wrong property name;
change expenses date input to be like that of time date input;

* remove fixed width attribute in header table

* add mobile-table-details CSS for main pages

* remove Delete column from pages

* add redundant word-wrap rule for browser compatibility

* for expense_edit: fix template paths to mobile/ and path to initialize.php

42 files changed:
WEB-INF/templates/mobile/client_add.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/client_delete.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/client_edit.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/clients.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/expense_delete.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/expense_edit.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/expenses.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/header.tpl
WEB-INF/templates/mobile/project_add.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/project_delete.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/project_edit.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/projects.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/task_add.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/task_delete.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/task_edit.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/tasks.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/time.tpl
WEB-INF/templates/mobile/user_add.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/user_delete.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/user_edit.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/users.tpl [new file with mode: 0644]
default.css
mobile/client_add.php [new file with mode: 0644]
mobile/client_delete.php [new file with mode: 0644]
mobile/client_edit.php [new file with mode: 0644]
mobile/clients.php [new file with mode: 0644]
mobile/expense_delete.php [new file with mode: 0644]
mobile/expense_edit.php [new file with mode: 0644]
mobile/expenses.php [new file with mode: 0644]
mobile/project_add.php [new file with mode: 0644]
mobile/project_delete.php [new file with mode: 0644]
mobile/project_edit.php [new file with mode: 0644]
mobile/projects.php [new file with mode: 0644]
mobile/task_add.php [new file with mode: 0644]
mobile/task_delete.php [new file with mode: 0644]
mobile/task_edit.php [new file with mode: 0644]
mobile/tasks.php [new file with mode: 0644]
mobile/time_edit.php
mobile/user_add.php [new file with mode: 0644]
mobile/user_delete.php [new file with mode: 0644]
mobile/user_edit.php [new file with mode: 0644]
mobile/users.php [new file with mode: 0644]

diff --git a/WEB-INF/templates/mobile/client_add.tpl b/WEB-INF/templates/mobile/client_add.tpl
new file mode 100644 (file)
index 0000000..6814d1e
--- /dev/null
@@ -0,0 +1,37 @@
+{$forms.clientForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="1" cellpadding="2" border="0">
+        <tr>
+          <td align="right">{$i18n.label.client_name} (*):</td>
+          <td>{$forms.clientForm.name.control}</td>
+        </tr>
+        <tr>
+          <td align="right">{$i18n.label.client_address}:</td>
+          <td>{$forms.clientForm.address.control}</td>
+        </tr>
+        <tr>
+          <td align="right">{$i18n.label.tax}, %:</td>
+          <td>{$forms.clientForm.tax.control}&nbsp;(0{$user->decimal_mark}00)</td>
+        </tr>
+        <tr>
+          <td height="40"></td>
+          <td>{$i18n.label.required_fields}</td>
+        </tr>
+        <tr><td>&nbsp;</td></tr>
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+        <tr>
+          <td align="right">{$i18n.label.projects}:</td>
+          <td>{$forms.clientForm.projects.control}</td>
+        </tr>
+        <tr><td>&nbsp;</td></tr>
+{/if}
+        <tr>
+          <td colspan="2" align="center" height="50">{$forms.clientForm.btn_submit.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.clientForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/mobile/client_delete.tpl b/WEB-INF/templates/mobile/client_delete.tpl
new file mode 100644 (file)
index 0000000..8a0b877
--- /dev/null
@@ -0,0 +1,25 @@
+{$forms.clientDeleteForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="0" cellpadding="2" border="0">
+        <tr>
+          <td>{$i18n.form.client.client_to_delete}:</td>
+          <th>{$client_to_delete|escape:'html'}</th>
+        </tr>
+        <tr>
+          <td>{$i18n.form.client.client_entries}:</td>
+          <td>{$forms.clientDeleteForm.delete_client_entries.control}</td>
+        </tr>
+        <tr>
+          <td colspan="2" align="center">&nbsp;</td>
+        </tr>
+        <tr>
+          <td align="right">{$forms.clientDeleteForm.btn_delete.control}&nbsp;</td>
+          <td align="left">&nbsp;{$forms.clientDeleteForm.btn_cancel.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.clientDeleteForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/mobile/client_edit.tpl b/WEB-INF/templates/mobile/client_edit.tpl
new file mode 100644 (file)
index 0000000..578aa64
--- /dev/null
@@ -0,0 +1,41 @@
+{$forms.clientForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="1" cellpadding="2" border="0">
+        <tr>
+          <td align="right">{$i18n.label.client_name} (*):</td>
+          <td>{$forms.clientForm.name.control}</td>
+        </tr>
+        <tr>
+          <td align="right">{$i18n.label.client_address}:</td>
+          <td>{$forms.clientForm.address.control}</td>
+        </tr>
+        <tr>
+          <td align="right">{$i18n.label.tax}, %:</td>
+          <td>{$forms.clientForm.tax.control}&nbsp;(0{$user->decimal_mark}00)</td>
+        </tr>
+        <tr>
+          <td align = "right">{$i18n.label.status}:</td>
+          <td>{$forms.clientForm.status.control}</td>
+        </tr>
+        <tr>
+          <td height="40"></td>
+          <td>{$i18n.label.required_fields}</td>
+        </tr>
+        <tr><td>&nbsp;</td></tr>
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+        <tr>
+          <td align="right">{$i18n.label.projects}:</td>
+          <td>{$forms.clientForm.projects.control}</td>
+        </tr>
+{/if}
+        <tr><td>&nbsp;</td></tr>
+        <tr>
+          <td colspan="2" align="center" height="50">{$forms.clientForm.btn_save.control} {$forms.clientForm.btn_copy.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.clientForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/mobile/clients.tpl b/WEB-INF/templates/mobile/clients.tpl
new file mode 100644 (file)
index 0000000..ded17ba
--- /dev/null
@@ -0,0 +1,55 @@
+<script>
+  function chLocation(newLocation) { document.location = newLocation; }
+</script>
+
+<table class="mobile-table">
+  <tr>
+    <td valign="top">
+{if ($user->canManageTeam())}
+      <table class="mobile-table-details">
+  {if $inactive_clients}
+        <tr><td class="sectionHeaderNoBorder">{$i18n.form.clients.active_clients}</td></tr>
+  {/if}
+        <tr>
+          <td width="40%" class="tableHeader">{$i18n.label.person_name}</td>
+          <td width="40%" class="tableHeader">{$i18n.label.address}</td>
+          <td class="tableHeader">{$i18n.label.edit}</td>
+        </tr>
+  {foreach $active_clients as $client}
+        <tr valign="top" bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$client.name|escape:'html'}</td>
+          <td>{$client.address|escape:'html'}</td>
+          <td><a href="client_edit.php?id={$client.id}">{$i18n.label.edit}</a></td>
+        </tr>
+  {/foreach}
+      </table>
+
+      <table width="100%">
+        <tr><td align="center"><br><form><input type="button" onclick="chLocation('client_add.php');" value="{$i18n.button.add_client}"></form></td></tr>
+      </table>
+
+  {if $inactive_clients}
+      <table cellspacing="1" cellpadding="3" border="0" width="100%">
+        <tr><td class="sectionHeaderNoBorder">{$i18n.form.clients.inactive_clients}</td></tr>
+        <tr>
+          <td width="40%" class="tableHeader">{$i18n.label.person_name}</td>
+          <td width="40%" class="tableHeader">{$i18n.label.address}</td>
+          <td class="tableHeader">{$i18n.label.edit}</td>
+        </tr>
+    {foreach $inactive_clients as $client}
+        <tr valign="top" bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$client.name|escape:'html'}</td>
+          <td>{$client.address|escape:'html'}</td>
+          <td><a href="client_edit.php?id={$client.id}">{$i18n.label.edit}</a></td>
+        </tr>
+    {/foreach}
+      </table>
+
+      <table width="100%">
+        <tr><td align="center"><br><form><input type="button" onclick="chLocation('client_add.php');" value="{$i18n.button.add_client}"></form></td></tr>
+      </table>
+  {/if}
+{/if}
+    </td>
+  </tr>
+</table>
diff --git a/WEB-INF/templates/mobile/expense_delete.tpl b/WEB-INF/templates/mobile/expense_delete.tpl
new file mode 100644 (file)
index 0000000..54cd89c
--- /dev/null
@@ -0,0 +1,39 @@
+{$forms.expenseItemForm.open}
+<table class="mobile-table">
+<tr>
+  <td>
+  <table border='0' cellpadding='3' cellspacing='1' width="100%">
+  <tr>
+{if $user->isPluginEnabled('cl')}
+    <td class="tableHeader" align="center">{$i18n.label.client}</td>
+{/if}
+
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+    <td class="tableHeader" align="center">{$i18n.label.project}</td>
+{/if}
+    <td class="tableHeader" align="center">{$i18n.label.item}</td>
+    <td class="tableHeader" align="center">{$i18n.label.cost}</td>
+  </tr>
+  <tr bgcolor="{cycle values="#f5f5f5,#ccccce"}">
+{if $user->isPluginEnabled('cl')}
+  <td>{$expense_item.client_name|escape:'html'}</td>
+{/if}
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+    <td>{$expense_item.project_name|escape:'html'}</td>
+{/if}
+    <td>{$expense_item.name|escape:'html'}</td>
+    <td align="right">{$expense_item.cost}</td>
+  </tr>
+  </table>
+  <table width="100%">
+  <tr>
+    <td align="center">&nbsp;</td>
+  </tr>
+  <tr>
+    <td align="center">{$forms.expenseItemForm.delete_button.control}&nbsp;&nbsp;{$forms.expenseItemForm.cancel_button.control}</td>
+  </tr>
+  </table>
+  </td>
+</tr>
+</table>
+{$forms.expenseItemForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/mobile/expense_edit.tpl b/WEB-INF/templates/mobile/expense_edit.tpl
new file mode 100644 (file)
index 0000000..41e3c2e
--- /dev/null
@@ -0,0 +1,117 @@
+<script>
+// We need a few arrays to populate project dropdown.
+// When client selection changes, the project dropdown must be re-populated with only relevant projects.
+// Format:
+// project_ids[143] = "325,370,390,400";  // Comma-separated list of project ids for client.
+// project_names[325] = "Time Tracker";   // Project name.
+
+// Prepare an array of project ids for clients.
+project_ids = new Array();
+{foreach $client_list as $client}
+  project_ids[{$client.id}] = "{$client.projects}";
+{/foreach}
+// Prepare an array of project names.
+project_names = new Array();
+{foreach $project_list as $project}
+  project_names[{$project.id}] = "{$project.name|escape:'javascript'}";
+{/foreach}
+// We'll use this array to populate project dropdown when client is not selected.
+var idx = 0;
+projects = new Array();
+{foreach $project_list as $project}
+  projects[idx] = new Array("{$project.id}", "{$project.name|escape:'javascript'}");
+  idx++;
+{/foreach}
+
+// Mandatory top option for project dropdown.
+empty_label_project = '{$i18n.dropdown.select|escape:'javascript'}';
+
+// The fillProjectDropdown function populates the project combo box with
+// projects associated with a selected client (client id is passed here as id).
+function fillProjectDropdown(id) {
+  var str_ids = project_ids[id];
+  var dropdown = document.getElementById("project");
+  // Determine previously selected item.
+  var selected_item = dropdown.options[dropdown.selectedIndex].value;
+
+  // Remove existing content.
+  dropdown.length = 0;
+  // Add mandatory top option.
+  dropdown.options[0] = new Option(empty_label_project, '', true);
+
+  // Populate project dropdown.
+  if (!id) {
+    // If we are here, client is not selected.
+    var len = projects.length;
+    for (var i = 0; i < len; i++) {
+      dropdown.options[i+1] = new Option(projects[i][1], projects[i][0]);
+      if (dropdown.options[i+1].value == selected_item)
+        dropdown.options[i+1].selected = true;
+    }
+  } else if (str_ids) {
+    var ids = new Array();
+    ids = str_ids.split(",");
+    var len = ids.length;
+
+    for (var i = 0; i < len; i++) {
+      var p_id = ids[i];
+      dropdown.options[i+1] = new Option(project_names[p_id], p_id);
+      if (dropdown.options[i+1].value == selected_item)
+        dropdown.options[i+1].selected = true;
+    }
+  }
+}
+
+function get_date() {
+  var date = new Date();
+  return date.strftime("%Y-%m-%d");
+}
+</script>
+
+{$forms.expenseItemForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+<tr>
+  <td>
+  <table width = "100%">
+  <tr>
+    <td valign="top">
+    <table border="0">
+{if $user->isPluginEnabled('cl')}
+    <tr>
+      <td align="right">{$i18n.label.client} {if $user->isPluginEnabled('cm')}(*){/if}:</td>
+      <td>{$forms.expenseItemForm.client.control}</td>
+    </tr>
+{/if}
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+    <tr>
+      <td align="right">{$i18n.label.project} (*):</td>
+      <td>{$forms.expenseItemForm.project.control}</td>
+    </tr>
+{/if}
+    <tr>
+      <td align="right">{$i18n.label.item}:</td>
+      <td>{$forms.expenseItemForm.item_name.control}</td>
+    </tr>
+    <tr>
+      <td align="right">{$i18n.label.cost}:</td>
+      <td>{$forms.expenseItemForm.cost.control} {$user->currency|escape:'html'}</td>
+    </tr>
+    <tr>
+      <td align="right">{$i18n.label.date}:</td>
+      <td>{$forms.expenseItemForm.date.control}</td>
+    </tr>
+    <tr>
+      <td colspan="2">&nbsp;</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td align="left">{$forms.expenseItemForm.btn_save.control} {$forms.expenseItemForm.btn_copy.control} {$forms.expenseItemForm.btn_delete.control}</td>
+    </tr>
+    </table>
+    </td>
+    </tr>
+  </table>
+  </td>
+  </tr>
+</table>
+{$forms.expenseItemForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/mobile/expenses.tpl b/WEB-INF/templates/mobile/expenses.tpl
new file mode 100644 (file)
index 0000000..bf99d80
--- /dev/null
@@ -0,0 +1,169 @@
+<script>
+// We need a few arrays to populate project dropdown.
+// When client selection changes, the project dropdown must be re-populated with only relevant projects.
+// Format:
+// project_ids[143] = "325,370,390,400";  // Comma-separated list of project ids for client.
+// project_names[325] = "Time Tracker";   // Project name.
+
+// Prepare an array of project ids for clients.
+project_ids = new Array();
+{foreach $client_list as $client}
+  project_ids[{$client.id}] = "{$client.projects}";
+{/foreach}
+// Prepare an array of project names.
+project_names = new Array();
+{foreach $project_list as $project}
+  project_names[{$project.id}] = "{$project.name|escape:'javascript'}";
+{/foreach}
+// We'll use this array to populate project dropdown when client is not selected.
+var idx = 0;
+projects = new Array();
+{foreach $project_list as $project}
+  projects[idx] = new Array("{$project.id}", "{$project.name|escape:'javascript'}");
+  idx++;
+{/foreach}
+
+// Mandatory top option for project dropdown.
+empty_label_project = '{$i18n.dropdown.select|escape:'javascript'}';
+
+// The fillProjectDropdown function populates the project combo box with
+// projects associated with a selected client (client id is passed here as id).
+function fillProjectDropdown(id) {
+  var str_ids = project_ids[id];
+  var dropdown = document.getElementById("project");
+  // Determine previously selected item.
+  var selected_item = dropdown.options[dropdown.selectedIndex].value;
+
+  // Remove existing content.
+  dropdown.length = 0;
+  // Add mandatory top option.
+  dropdown.options[0] = new Option(empty_label_project, '', true);
+
+  // Populate project dropdown.
+  if (!id) {
+    // If we are here, client is not selected.
+    var len = projects.length;
+    for (var i = 0; i < len; i++) {
+      dropdown.options[i+1] = new Option(projects[i][1], projects[i][0]);
+      if (dropdown.options[i+1].value == selected_item)
+        dropdown.options[i+1].selected = true;
+    }
+  } else if (str_ids) {
+    var ids = new Array();
+    ids = str_ids.split(",");
+    var len = ids.length;
+
+    for (var i = 0; i < len; i++) {
+      var p_id = ids[i];
+      dropdown.options[i+1] = new Option(project_names[p_id], p_id);
+      if (dropdown.options[i+1].value == selected_item)
+        dropdown.options[i+1].selected = true;
+    }
+  }
+}
+
+function get_date() {
+  var date = new Date();
+  return date.strftime("%Y-%m-%d");
+}
+</script>
+
+<!-- Inserted from time.tpl -->
+<table cellspacing="3" cellpadding="0" border="0" width="100%">
+  <tr>
+    <td class="sectionHeaderNoBorder" align="right"><a href="expenses.php?date={$prev_date}">&lt;&lt;</a></td>
+    <td class="sectionHeaderNoBorder" align="center">{$timestring}</td>
+    <td class="sectionHeaderNoBorder" align="left"><a href="expenses.php?date={$next_date}">&gt;&gt;</a></td>
+  </tr>
+</table>
+
+{$forms.expensesForm.open}
+<table cellspacing="4" cellpadding="0" border="0">
+  <tr>
+    <td valign="top">
+      <table>
+{if $on_behalf_control}
+        <tr>
+          <td align="right">{$i18n.label.user}:</td>
+          <td>{$forms.expensesForm.onBehalfUser.control}</td>
+        </tr>
+{/if}
+{if $user->isPluginEnabled('cl')}
+        <tr>
+          <td align="right">{$i18n.label.client}{if $user->isPluginEnabled('cm')} (*){/if}:</td>
+          <td>{$forms.expensesForm.client.control}</td>
+        </tr>
+{/if}
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+        <tr>
+          <td align="right">{$i18n.label.project} (*):</td>
+          <td>{$forms.expensesForm.project.control}</td>
+        </tr>
+{/if}
+        <tr>
+          <td align="right">{$i18n.label.item} (*):</td>
+          <td>{$forms.expensesForm.item_name.control}</td>
+        </tr>
+        <tr>
+          <td align="right">{$i18n.label.cost} (*):</td>
+          <td>{$forms.expensesForm.cost.control} {$user->currency|escape:'html'}</td>
+        </tr>
+      </table>
+    </td>
+    <!--
+    <td valign="top">
+      <table>
+        <tr><td>{$forms.expensesForm.date.control}</td></tr>
+      </table>
+    </td>
+    -->
+  </tr>
+</table>
+
+<table>
+  <tr>
+    <td align="center" colspan="2">{$forms.expensesForm.btn_submit.control}</td>
+  </tr>
+</table>
+
+<table class="mobile-table">
+<tr>
+  <td valign="top">
+{if $expense_items}
+      <table class="mobile-table-details">
+      <tr>
+  {if $user->isPluginEnabled('cl')}
+        <td width="20%" class="tableHeader">{$i18n.label.client}</td>
+  {/if}
+  {if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+        <td class="tableHeader">{$i18n.label.project}</td>
+  {/if}
+        <td class="tableHeader">{$i18n.label.item}</td>
+        <td width="5%" class="tableHeaderCentered">{$i18n.label.cost}</td>
+        <td width="5%" class="tableHeader">{$i18n.label.edit}</td>
+      </tr>
+  {foreach $expense_items as $item}
+      <tr bgcolor="{cycle values="#f5f5f5,#ccccce"}">
+    {if $user->isPluginEnabled('cl')}
+        <td valign='top'>{$item.client|escape:'html'}</td>
+    {/if}
+    {if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+        <td valign='top'>{$item.project|escape:'html'}</td>
+    {/if}
+        <td valign='top'>{$item.item|escape:'html'}</td>
+        <td valign='top' align='right'>{$item.cost}</td>
+        <td valign='top' align='center'>{if $item.invoice_id}&nbsp;{else}<a href='expense_edit.php?id={$item.id}'>{$i18n.label.edit}</a>{/if}</td>
+      </tr>
+  {/foreach}
+    </table>
+    <table border="0" cellpadding="3" cellspacing="1" width="100%">
+      <tr>
+        <td nowrap align="right">{$i18n.label.day_total}: {$user->currency|escape:'html'} {$day_total}</td>
+      </tr>
+    </table>
+{/if}
+  </td>
+</tr>
+</table>
+
+{$forms.expensesForm.close}
index 03bfa18..cd266ae 100644 (file)
@@ -21,7 +21,7 @@
 
 {assign var="tab_width" value="300"}
 
-<table height="100%" cellspacing="0" cellpadding="0" width="320" border="0">
+<table height="100%" cellspacing="0" cellpadding="0" border="0">
   <tr>
     <td valign="top" align="center"> <!-- This is to centrally align all our content. -->
 
         </tr>
       </table>
       <!-- End of top image -->
+      
+{if $authenticated}
+  {if $user->isAdmin()}
+  
+      <!-- Sub menu for admin -->
+      <table cellspacing="0" cellpadding="3" width="100%" border="0">
+        <tr>
+          <td align="center" bgcolor="#d9d9d9" height="17" style="background-repeat: repeat-x;" background="../images/subm_bg.gif">&nbsp;
+            <a class="mainMenu" href="admin_teams.php">{$i18n.menu.teams}</a> &middot;
+            <a class="mainMenu" href="admin_options.php">{$i18n.menu.options}</a>
+          </td>
+        </tr>
+      </table>
+      <!-- End of sub menu for admin -->
+  {else}
 
+      <!-- Sub menu for authorized user -->
+      <table cellspacing="0" cellpadding="3" width="100%" border="0">
+        <tr>
+          <td align="center" bgcolor="#d9d9d9" height="17" style="background-repeat: repeat-x;" background="../images/subm_bg.gif">&nbsp;
+    {if !$user->isClient()}
+           <a class="mainMenu" href="time.php">{$i18n.menu.time}</a>
+    {/if}
+    {if $user->isPluginEnabled('ex') && !$user->isClient()}
+            &middot; <a class="mainMenu" href="expenses.php">{$i18n.menu.expenses}</a>
+    {/if}
+    {if !$user->isClient() && ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+            &middot; <a class="mainMenu" href="projects.php">{$i18n.menu.projects}</a>
+    {/if}
+    {if $user->canManageTeam() && ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+            &middot; <a class="mainMenu" href="tasks.php">{$i18n.menu.tasks}</a>
+    {/if}
+    {if !$user->isClient()}
+            &middot; <a class="mainMenu" href="users.php">{$i18n.menu.users}</a>
+    {/if}
+    {if $user->canManageTeam() && $user->isPluginEnabled('cl')}
+            &middot; <a class="mainMenu" href="clients.php">{$i18n.menu.clients}</a>
+    {/if}
+          </td>
+        </tr>
+      </table>
+      <!-- End of sub menu for authorized user -->
+  {/if}
+{/if}
+      
       <!-- Output errors -->
 {if $err->yes()}
       <table cellspacing="4" cellpadding="7" width="{$tab_width}" border="0">
diff --git a/WEB-INF/templates/mobile/project_add.tpl b/WEB-INF/templates/mobile/project_add.tpl
new file mode 100644 (file)
index 0000000..2bd7f6f
--- /dev/null
@@ -0,0 +1,42 @@
+{$forms.projectForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="1" cellpadding="2" border="0">
+        <tr>
+          <td align="right">{$i18n.label.thing_name} (*):</td>
+          <td>{$forms.projectForm.project_name.control}</td>
+        </tr>
+        <tr>
+          <td align = "right">{$i18n.label.description}:</td>
+          <td>{$forms.projectForm.description.control}</td>
+        </tr>
+        <tr><td>&nbsp;</td></tr>
+        <tr>
+          <td align="right">{$i18n.label.users}:</td>
+          <td>{$forms.projectForm.users.control}</td>
+        </tr>
+{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+        <tr><td>&nbsp;</td></tr>
+        <tr>
+          <td align="right">{$i18n.label.tasks}:</td>
+          <td>{$forms.projectForm.tasks.control}</td>
+        </tr>
+{/if}
+        <tr>
+          <td></td>
+          <td>{$i18n.label.required_fields}</td>
+        </tr>
+        <tr>
+          <td></td>
+          <td>&nbsp;</td>
+        </tr>
+        <tr>
+          <td colspan="2" align="center" height="50">{$forms.projectForm.btn_add.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.projectForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/mobile/project_delete.tpl b/WEB-INF/templates/mobile/project_delete.tpl
new file mode 100644 (file)
index 0000000..bf1fc95
--- /dev/null
@@ -0,0 +1,18 @@
+{$forms.projectDeleteForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="0" cellpadding="0" border="0">
+        <tr>
+          <td colspan="2" align="center"><b>{$project_to_delete|escape:'html'}</b></td>
+        </tr>
+        <tr><td colspan="2" align="center">&nbsp;</td></tr>
+        <tr>
+          <td align="right">{$forms.projectDeleteForm.btn_delete.control}&nbsp;</td>
+          <td align="left">&nbsp;{$forms.projectDeleteForm.btn_cancel.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.projectDeleteForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/mobile/project_edit.tpl b/WEB-INF/templates/mobile/project_edit.tpl
new file mode 100644 (file)
index 0000000..c94e475
--- /dev/null
@@ -0,0 +1,45 @@
+{$forms.projectForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="1" cellpadding="2" border="0">
+        <tr>
+          <td align = "right">{$i18n.label.thing_name} (*):</td>
+          <td>{$forms.projectForm.project_name.control}</td>
+        </tr>
+        <tr>
+          <td align = "right">{$i18n.label.description}:</td>
+          <td>{$forms.projectForm.description.control}</td>
+        </tr>
+        <tr>
+          <td align="right">{$i18n.label.status}:</td>
+          <td>{$forms.projectForm.status.control}</td>
+        </tr>
+        <tr><td>&nbsp;</td></tr>
+        <tr>
+          <td align="right">{$i18n.label.users}:</td>
+          <td>{$forms.projectForm.users.control}</td>
+        </tr>
+{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+        <tr><td>&nbsp;</td></tr>
+        <tr>
+          <td align="right">{$i18n.label.tasks}:</td>
+          <td>{$forms.projectForm.tasks.control}</td>
+        </tr>
+{/if}
+        <tr>
+          <td></td>
+          <td>{$i18n.label.required_fields}</td>
+        </tr>
+        <tr>
+          <td></td>
+          <td>&nbsp;</td>
+        </tr>
+        <tr>
+          <td colspan="2" align="center" height="50">{$forms.projectForm.btn_save.control} {$forms.projectForm.btn_copy.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.projectForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/mobile/projects.tpl b/WEB-INF/templates/mobile/projects.tpl
new file mode 100644 (file)
index 0000000..45cd184
--- /dev/null
@@ -0,0 +1,80 @@
+<script>
+  function chLocation(newLocation) { document.location = newLocation; }
+</script>
+
+<table class="mobile-table">
+  <tr>
+    <td valign="top">
+{if $user->canManageTeam()}
+      <table class="mobile-table-details">
+  {if $inactive_projects}
+        <tr><td class="sectionHeaderNoBorder">{$i18n.form.projects.active_projects}</td></tr>
+  {/if}
+        <tr>
+          <td width="35%" class="tableHeader">{$i18n.label.thing_name}</td>
+          <td width="35%" class="tableHeader">{$i18n.label.description}</td>
+          <td class="tableHeader">{$i18n.label.edit}</td>
+        </tr>
+  {if $active_projects}
+    {foreach $active_projects as $project}
+        <tr bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$project.name|escape:'html'}</td>
+          <td>{$project.description|escape:'html'}</td>
+          <td><a href="project_edit.php?id={$project.id}">{$i18n.label.edit}</a></td>
+        </tr>
+    {/foreach}
+  {/if}
+      </table>
+
+      <table width="100%">
+        <tr>
+          <td align="center"><br>
+            <form><input type="button" onclick="chLocation('project_add.php');" value="{$i18n.button.add_project}"></form>
+          </td>
+        </tr>
+      </table>
+
+  {if $inactive_projects}
+      <table cellspacing="1" cellpadding="3" border="0" width="100%">
+        <tr><td class="sectionHeaderNoBorder">{$i18n.form.projects.inactive_projects}</td></tr>
+        <tr>
+          <td width="35%" class="tableHeader">{$i18n.label.thing_name}</td>
+          <td width="35%" class="tableHeader">{$i18n.label.description}</td>
+          <td class="tableHeader">{$i18n.label.edit}</td>
+        </tr>
+    {foreach $inactive_projects as $project}
+        <tr bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$project.name|escape:'html'}</td>
+          <td>{$project.description|escape:'html'}</td>
+          <td><a href="project_edit.php?id={$project.id}">{$i18n.label.edit}</a></td>
+        </tr>
+    {/foreach}
+      </table>
+
+      <table width="100%">
+        <tr>
+          <td align="center"><br>
+            <form><input type="button" onclick="chLocation('project_add.php');" value="{$i18n.button.add_project}"></form>
+          </td>
+        </tr>
+      </table>
+  {/if}
+{else}
+      <table cellspacing="1" cellpadding="3" border="0" width="100%">
+        <tr>
+          <td class="tableHeader">{$i18n.label.thing_name}</td>
+          <td class="tableHeader">{$i18n.label.description}</td>
+        </tr>
+  {if $active_projects}
+    {foreach $active_projects as $project}
+        <tr bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$project.name|escape:'html'}</td>
+          <td>{$project.description|escape:'html'}</td>
+        </tr>
+    {/foreach}
+  {/if}
+      </table>
+{/if}
+    </td>
+  </tr>
+</table>
diff --git a/WEB-INF/templates/mobile/task_add.tpl b/WEB-INF/templates/mobile/task_add.tpl
new file mode 100644 (file)
index 0000000..53ce483
--- /dev/null
@@ -0,0 +1,33 @@
+{$forms.taskForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="1" cellpadding="2" border="0">
+        <tr>
+          <td align="right">{$i18n.label.thing_name} (*):</td>
+          <td>{$forms.taskForm.name.control}</td>
+        </tr>
+        <tr>
+          <td align = "right">{$i18n.label.description}:</td>
+          <td>{$forms.taskForm.description.control}</td>
+        </tr>
+        <tr valign="top">
+          <td align="right">{$i18n.label.projects}:</td>
+          <td>{$forms.taskForm.projects.control}</td>
+        </tr>
+        <tr>
+          <td></td>
+          <td>{$i18n.label.required_fields}</td>
+        </tr>
+        <tr>
+          <td></td>
+          <td>&nbsp;</td>
+        </tr>
+        <tr>
+          <td colspan="2" align="center" height="50">{$forms.taskForm.btn_submit.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.taskForm.close}
diff --git a/WEB-INF/templates/mobile/task_delete.tpl b/WEB-INF/templates/mobile/task_delete.tpl
new file mode 100644 (file)
index 0000000..f25cc95
--- /dev/null
@@ -0,0 +1,20 @@
+{$forms.taskDeleteForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="0" cellpadding="0" border="0">
+        <tr>
+          <td colspan="2" align="center"><b>{$task_to_delete|escape:'html'}</b></td>
+        </tr>
+        <tr>
+          <td colspan="2" align="center">&nbsp;</td>
+        </tr>
+        <tr>
+          <td align="right">{$forms.taskDeleteForm.btn_delete.control}&nbsp;</td>
+          <td align="left">&nbsp;{$forms.taskDeleteForm.btn_cancel.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.taskDeleteForm.close}
diff --git a/WEB-INF/templates/mobile/task_edit.tpl b/WEB-INF/templates/mobile/task_edit.tpl
new file mode 100644 (file)
index 0000000..5af8d93
--- /dev/null
@@ -0,0 +1,37 @@
+{$forms.taskForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="1" cellpadding="2" border="0">
+        <tr>
+          <td align="right">{$i18n.label.thing_name} (*):</td>
+          <td>{$forms.taskForm.name.control}</td>
+        </tr>
+        <tr>
+          <td align = "right">{$i18n.label.description}:</td>
+          <td>{$forms.taskForm.description.control}</td>
+        </tr>
+        <tr>
+          <td align = "right">{$i18n.label.status}:</td>
+          <td>{$forms.taskForm.status.control}</td>
+        </tr>
+        <tr valign="top">
+          <td align="right">{$i18n.label.projects}:</td>
+          <td>{$forms.taskForm.projects.control}</td>
+        </tr>
+        <tr>
+          <td></td>
+          <td>{$i18n.label.required_fields}</td>
+        </tr>
+        <tr>
+          <td></td>
+          <td>&nbsp;</td>
+        </tr>
+        <tr>
+          <td colspan="2" align="center" height="50">{$forms.taskForm.btn_save.control} {$forms.taskForm.btn_copy.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.taskForm.close}
diff --git a/WEB-INF/templates/mobile/tasks.tpl b/WEB-INF/templates/mobile/tasks.tpl
new file mode 100644 (file)
index 0000000..0a52107
--- /dev/null
@@ -0,0 +1,80 @@
+<script>
+  function chLocation(newLocation) { document.location = newLocation; }
+</script>
+
+<table class="mobile-table">
+  <tr>
+    <td valign="top">
+{if $user->canManageTeam()}
+      <table class="mobile-table-details">
+  {if $inactive_tasks}
+        <tr><td class="sectionHeaderNoBorder">{$i18n.form.tasks.active_tasks}</td></tr>
+  {/if}
+        <tr>
+          <td width="35%" class="tableHeader">{$i18n.label.thing_name}</td>
+          <td width="35%" class="tableHeader">{$i18n.label.description}</td>
+          <td class="tableHeader">{$i18n.label.edit}</td>
+        </tr>
+  {if $active_tasks}
+    {foreach $active_tasks as $task}
+        <tr bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$task.name|escape:'html'}</td>
+          <td>{$task.description|escape:'html'}</td>
+          <td><a href="task_edit.php?id={$task.id}">{$i18n.label.edit}</a></td>
+        </tr>
+    {/foreach}
+  {/if}
+      </table>
+
+      <table width="100%">
+        <tr>
+          <td align="center"><br>
+            <form><input type="button" onclick="chLocation('task_add.php');" value="{$i18n.button.add_task}"></form>
+          </td>
+        </tr>
+      </table>
+
+  {if $inactive_tasks}
+      <table cellspacing="1" cellpadding="3" border="0" width="100%">
+        <tr><td class="sectionHeaderNoBorder">{$i18n.form.tasks.inactive_tasks}</td></tr>
+        <tr>
+          <td width="35%" class="tableHeader">{$i18n.label.thing_name}</td>
+          <td width="35%" class="tableHeader">{$i18n.label.description}</td>
+          <td class="tableHeader">{$i18n.label.edit}</td>
+        </tr>
+    {foreach $inactive_tasks as $task}
+        <tr bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$task.name|escape:'html'}</td>
+          <td>{$task.description|escape:'html'}</td>
+          <td><a href="task_edit.php?id={$task.id}">{$i18n.label.edit}</a></td>
+        </tr>
+    {/foreach}
+      </table>
+
+      <table width="100%">
+        <tr>
+          <td align="center"><br>
+            <form><input type="button" onclick="chLocation('task_add.php');" value="{$i18n.button.add_task}"></form>
+          </td>
+        </tr>
+      </table>
+  {/if}
+{else}
+      <table cellspacing="1" cellpadding="3" border="0" width="100%">
+        <tr>
+          <td class="tableHeader">{$i18n.label.thing_name}</td>
+          <td class="tableHeader">{$i18n.label.description}</td>
+        </tr>
+  {if $active_tasks}
+    {foreach $active_tasks as $task}
+        <tr bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$task.name|escape:'html'}</td>
+          <td>{$task.description|escape:'html'}</td>
+        </tr>
+    {/foreach}
+  {/if}
+      </table>
+  {/if}
+    </td>
+  </tr>
+</table>
index ed83c9c..c3e9b59 100644 (file)
@@ -220,7 +220,7 @@ function get_date() {
 <tr>
   <td align="center">
     {if $time_records}
-      <table border='0' cellpadding='4' cellspacing='1' width="100%">
+      <table class="mobile-table-details">
       {foreach $time_records as $record}
       <tr bgcolor="{cycle values="#ccccce,#f5f5f5"}" {if !$record.billable} class="not_billable" {/if}>
 {if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
diff --git a/WEB-INF/templates/mobile/user_add.tpl b/WEB-INF/templates/mobile/user_add.tpl
new file mode 100644 (file)
index 0000000..2971404
--- /dev/null
@@ -0,0 +1,83 @@
+<script>
+// The setDefaultRate function sets / unsets default rate for a project
+// when a corresponding checkbox is ticked.
+function setDefaultRate(element) {
+  var default_rate = document.userForm.rate.value;
+  if (default_rate == '') {
+    // No default rate, nothing to do!
+    return;
+  }
+  // Iterate through elements of the form to find and set the project rate. 
+  for (var i = 0; i < userForm.elements.length; i++) {
+    if ((userForm.elements[i].type == 'text') && (userForm.elements[i].name == ('rate_'+element.value))) {
+      if (element.checked) {
+        userForm.elements[i].value = default_rate;
+      } else {
+        userForm.elements[i].value = '';
+      }
+      break; // Element is found and set, nothing more to do, break out of the loop.
+    }
+  }
+}
+
+// handleClientControl - controls visibility of the client dropdown depending on the selected user role.
+// We need to show it only when the "Client" user role is selected.
+function handleClientControl() {
+  var clientControl = document.getElementById("client");
+  if ("16" == document.getElementById("role").value)
+    clientControl.style.visibility = "visible";
+  else
+    clientControl.style.visibility = "hidden";
+}
+</script>
+
+{$forms.userForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <table cellspacing="1" cellpadding="2" border="0">
+    <tr>
+      <td align="right">{$i18n.label.person_name} (*):</td>
+      <td>{$forms.userForm.name.control}</td>
+    </tr>
+    <tr>
+      <td align="right">{$i18n.label.login} (*):</td>
+      <td>{$forms.userForm.login.control}</td>
+    </tr>
+{if !$auth_external}
+    <tr>
+      <td align="right">{$i18n.label.password} (*):</td>
+      <td>{$forms.userForm.pas1.control}</td>
+    </tr>
+    <tr>
+      <td align="right">{$i18n.label.confirm_password} (*):</td>
+      <td>{$forms.userForm.pas2.control}</td>
+    </tr>
+{/if}
+    <tr>
+      <td align="right" nowrap>{$i18n.label.email}:</td>
+      <td>{$forms.userForm.email.control}</td>
+    </tr>
+{if $user->isManager()}
+    <tr>
+      <td align="right">{$i18n.form.users.role}:</td>
+      <td>{$forms.userForm.role.control} {$forms.userForm.client.control}</td>
+    </tr>
+{/if}
+    <tr>
+      <td align="right">{$i18n.form.users.default_rate}&nbsp;(0{$user->decimal_mark}00):</td>
+      <td>{$forms.userForm.rate.control}</td>
+    </tr>
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+    <tr valign="top">
+      <td align="right">{$i18n.label.projects}:</td>
+      <td>{$forms.userForm.projects.control}</td>
+    </tr>
+    <tr>
+      <td colspan="2" align="center">{$i18n.label.required_fields}</td>
+    </tr>
+{/if}
+    <tr>
+      <td colspan="2" align="center" height="50">{$forms.userForm.btn_submit.control}</td>
+    </tr>
+  </table>
+</table>
+{$forms.userForm.close}
diff --git a/WEB-INF/templates/mobile/user_delete.tpl b/WEB-INF/templates/mobile/user_delete.tpl
new file mode 100644 (file)
index 0000000..1f5b442
--- /dev/null
@@ -0,0 +1,20 @@
+{$forms.userDeleteForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="0" cellpadding="0" border="0">
+        <tr>
+          <td colspan="2" align="center"><b>{$user_to_delete|escape:'html'}</b></td>
+        </tr>
+        <tr>
+          <td colspan="2" align="center">&nbsp;</td>
+        </tr>
+        <tr>
+          <td align="right">{$forms.userDeleteForm.btn_delete.control}&nbsp;</td>
+          <td align="left">&nbsp;{$forms.userDeleteForm.btn_cancel.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.userDeleteForm.close}
diff --git a/WEB-INF/templates/mobile/user_edit.tpl b/WEB-INF/templates/mobile/user_edit.tpl
new file mode 100644 (file)
index 0000000..7f81ca0
--- /dev/null
@@ -0,0 +1,112 @@
+<script>
+// Prepare an array of rates.
+// Format: project_rates[0] = Array(100, '25.00'), project_rates[1] = Array(120, '30.00'), etc...
+// First element = project_id, second element = rate for project. Quotes needed for string representation of rates.
+project_rates = new Array();
+var idx = 0;
+{foreach $rates as $rate}
+project_rates[idx] = new Array({$rate.id}, '{$rate.rate}');
+idx++;
+{/foreach}
+
+// getRate - returns a rate for the project. If rate was set for user previously we'll get this old rate
+// if project time entries for user exists. Otherwise return user default rate.
+function getRate(project_id) {
+  var length = project_rates.length;
+  for(var i = 0; i < length; i++) {
+    if(project_rates[i][0] == project_id) {
+      return project_rates[i][1];
+    }
+  }
+  var default_rate = document.userForm.rate.value;
+  return default_rate;
+}
+
+// The setRate function sets / unsets user rate for a project when a corresponding checkbox is ticked.
+function setRate(element) {
+  var default_rate = document.userForm.rate.value;
+  if (default_rate == '') {
+    // No default rate, nothing to do!
+    return;
+  }
+  // Iterate through elements of the form to find and set the project rate. 
+  for (var i = 0; i < userForm.elements.length; i++) {
+    if ((userForm.elements[i].type == 'text') && (userForm.elements[i].name == ('rate_'+element.value))) {
+      if (element.checked) {
+        userForm.elements[i].value = getRate(element.value);
+      } else {
+        userForm.elements[i].value = '';
+      }
+      break; // Element is found and set, nothing more to do, break out of the loop.
+    }
+  }
+}
+
+// handleClientControl - controls visibility of the client dropdown depending on the selected user role.
+// We need to show it only when the "Client" user role is selected.
+function handleClientControl() {
+  var clientControl = document.getElementById("client");
+  if ("16" == document.getElementById("role").value)
+    clientControl.style.visibility = "visible";
+  else
+    clientControl.style.visibility = "hidden";
+}
+</script>
+
+{$forms.userForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <table cellspacing="1" cellpadding="2" border="0">
+    <tr>
+      <td align="right">{$i18n.label.person_name} (*):</td>
+      <td>{$forms.userForm.name.control}</td>
+    </tr>
+    <tr>
+      <td align="right">{$i18n.label.login} (*):</td>
+      <td>{$forms.userForm.login.control}</td>
+    </tr>
+{if !$auth_external}
+    <tr>
+      <td align="right">{$i18n.label.password} (*):</td>
+      <td>{$forms.userForm.pas1.control}</td>
+    </tr>
+    <tr>
+      <td align="right">{$i18n.label.confirm_password} (*):</td>
+      <td>{$forms.userForm.pas2.control}</td>
+    </tr>
+{/if}
+    <tr>
+      <td align="right" nowrap>{$i18n.label.email}:</td>
+      <td>{$forms.userForm.email.control}</td>
+    </tr>
+{if $user->isManager() && ($user->id != $user_id)}
+    <tr>
+      <td align="right">{$i18n.form.users.role}:</td>
+      <td>{$forms.userForm.role.control} {$forms.userForm.client.control}</td>
+    </tr>
+{/if}
+{* Prohibit deactivating team manager. Deactivating others is ok. *}
+{if $user->canManageTeam() && !($user->isManager() && $user->id == $user_id)}
+    <tr>
+      <td align="right">{$i18n.label.status}:</td>
+      <td>{$forms.userForm.status.control}</td>
+    </tr>
+{/if}
+    <tr>
+      <td align="right">{$i18n.form.users.default_rate}&nbsp;(0{$user->decimal_mark}00):</td>
+      <td>{$forms.userForm.rate.control}</td>
+    </tr>
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+    <tr valign="top">
+      <td align="right">{$i18n.label.projects}:</td>
+      <td>{$forms.userForm.projects.control}</td>
+    </tr>
+{/if}
+    <tr>
+      <td colspan="2" align="center">{$i18n.label.required_fields}</td>
+    </tr>
+    <tr>
+      <td colspan="2" align="center" height="50">{$forms.userForm.btn_submit.control}</td>
+    </tr>
+  </table>
+</table>
+{$forms.userForm.close}
diff --git a/WEB-INF/templates/mobile/users.tpl b/WEB-INF/templates/mobile/users.tpl
new file mode 100644 (file)
index 0000000..aab86d8
--- /dev/null
@@ -0,0 +1,124 @@
+<script>
+  function chLocation(newLocation) { document.location = newLocation; }
+</script>
+
+<table class="mobile-table">
+  <tr>
+    <td valign="top">
+{if $user->canManageTeam()}
+      <table class="mobile-table-details">
+  {if $inactive_users}
+        <tr><td class="sectionHeaderNoBorder">{$i18n.form.users.active_users}</td></tr>
+  {/if}
+        <tr>
+          <td width="35%" class="tableHeader">{$i18n.label.person_name}</td>
+          <td width="35%" class="tableHeader">{$i18n.label.login}</td>
+          <td width="10%" class="tableHeader">{$i18n.form.users.role}</td>
+          <td width="10%" class="tableHeader">{$i18n.label.edit}</td>
+        </tr>
+  {if $active_users}
+    {foreach $active_users as $u}
+        <tr bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$u.name|escape:'html'}</td>
+          <td>{$u.login|escape:'html'}</td>
+      {if $smarty.const.ROLE_MANAGER == $u.role}
+            <td>{$i18n.form.users.manager}</td>
+      {elseif $smarty.const.ROLE_COMANAGER == $u.role}
+            <td>{$i18n.form.users.comanager}</td>
+      {elseif $smarty.const.ROLE_CLIENT == $u.role}
+            <td>{$i18n.label.client}</td>
+      {elseif $smarty.const.ROLE_USER == $u.role}
+            <td>{$i18n.label.user}</td>
+      {/if}
+      {if $user->isManager()}
+          <!-- Manager can edit everybody. -->
+          <td><a href="user_edit.php?id={$u.id}">{$i18n.label.edit}</a></td>
+      {else}
+          <!--  Comanager can edit self and clients or users but not manager and other comanagers. -->
+          <td>{if ($user->id == $u.id) || ($smarty.const.ROLE_CLIENT == $u.role) || ($smarty.const.ROLE_USER == $u.role)}<a href="user_edit.php?id={$u.id}">{$i18n.label.edit}</a>{/if}</td>
+      {/if}
+        </tr>
+    {/foreach}
+  {/if}
+      </table>
+
+      <table width="100%">
+        <tr>
+          <td align="center"><br>
+            <form><input type="button" onclick="chLocation('user_add.php');" value="{$i18n.button.add_user}"></form>
+          </td>
+        </tr>
+      </table>
+
+  {if $inactive_users}
+      <table cellspacing="1" cellpadding="3" border="0" width="100%">
+        <tr><td class="sectionHeaderNoBorder">{$i18n.form.users.inactive_users}</td></tr>
+        <tr>
+          <td width="35%" class="tableHeader">{$i18n.label.person_name}</td>
+          <td width="35%" class="tableHeader">{$i18n.label.login}</td>
+          <td width="10%" class="tableHeader">{$i18n.form.users.role}</td>
+          <td width="10%" class="tableHeader">{$i18n.label.edit}</td>
+          <td width="10%" class="tableHeader">{$i18n.label.delete}</td>
+        </tr>
+    {foreach $inactive_users as $u}
+        <tr bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$u.name|escape:'html'}</td>
+          <td>{$u.login|escape:'html'}</td>
+      {if $smarty.const.ROLE_MANAGER == $u.role}
+            <td>{$i18n.form.users.manager}</td>
+      {elseif $smarty.const.ROLE_COMANAGER == $u.role}
+            <td>{$i18n.form.users.comanager}</td>
+      {elseif $smarty.const.ROLE_CLIENT == $u.role}
+            <td>{$i18n.label.client}</td>
+      {elseif $smarty.const.ROLE_USER == $u.role}
+            <td>{$i18n.label.user}</td>
+      {/if}
+      {if $user->isManager()}
+          <!-- Manager can edit everybody. -->
+          <td><a href="user_edit.php?id={$u.id}">{$i18n.label.edit}</a></td>
+          <td>{if $smarty.const.ROLE_MANAGER != $u.role || $can_delete_manager}<a href="user_delete.php?id={$u.id}">{$i18n.label.delete}</a>{/if}</td>
+      {else}
+          <!--  Comanager can edit self and clients or users but not manager and other comanagers. -->
+          <td>{if ($user->id == $u.id) || ($smarty.const.ROLE_CLIENT == $u.role) || ($smarty.const.ROLE_USER == $u.role)}<a href="user_edit.php?id={$u.id}">{$i18n.label.edit}</a>{/if}</td>
+          <td>{if ($user->id == $u.id) || ($smarty.const.ROLE_CLIENT == $u.role) || ($smarty.const.ROLE_USER == $u.role)}<a href="user_delete.php?id={$u.id}">{$i18n.label.delete}</a>{/if}</td>
+      {/if}
+        </tr>
+    {/foreach}
+
+      </table>
+
+      <table width="100%">
+        <tr>
+          <td align="center" height="50">
+            <form><input type="button" onclick="chLocation('user_add.php');" value="{$i18n.button.add_user}"></form>
+          </td>
+        </tr>
+      </table>
+  {/if}
+{else}
+      <table cellspacing="1" cellpadding="3" border="0" width="100%">
+        <tr>
+          <td width="35%" class="tableHeader">{$i18n.label.person_name}</td>
+          <td width="35%" class="tableHeader">{$i18n.label.login}</td>
+          <td class="tableHeader">{$i18n.form.users.role}</td>
+        </tr>
+  {foreach $active_users as $u}
+        <tr bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$u.name|escape:'html'}</td>
+          <td>{$u.login|escape:'html'}</td>
+    {if $smarty.const.ROLE_MANAGER == $u.role}
+            <td>{$i18n.form.users.manager}</td>
+    {elseif $smarty.const.ROLE_COMANAGER == $u.role}
+            <td>{$i18n.form.users.comanager}</td>
+    {elseif $smarty.const.ROLE_CLIENT == $u.role}
+            <td>{$i18n.label.client}</td>
+    {elseif $smarty.const.ROLE_USER == $u.role}
+            <td>{$i18n.label.user}</td>
+    {/if}
+        </tr>
+  {/foreach}
+      </table>
+{/if}
+    </td>
+  </tr>
+</table>
index 62032fc..161f61f 100644 (file)
@@ -143,3 +143,34 @@ table.divider {
 }
 
 div#LoginAboutText { width:400px; }
+
+/* Mobile styles */
+.mobile-table {
+  border: 0;
+  width: 100%;
+  border-spacing: 0;
+}
+
+.mobile-textarea {
+  width: 100%; 
+  resize: vertical;
+  height: 5em;
+}
+
+.mobile-input {
+  width: 100%;
+}
+
+.mobile-table-details {
+  width: 100%;
+  table-layout: fixed;
+  overflow-wrap: break-word;
+  word-wrap: break-word;
+  border-spacing: 1px;
+  border: 0;
+}
+
+.mobile-table-details td {
+  padding: 3px;
+}
+
diff --git a/mobile/client_add.php b/mobile/client_add.php
new file mode 100644 (file)
index 0000000..56f5d08
--- /dev/null
@@ -0,0 +1,89 @@
+<?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->isPost()) {
+  $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','value'=>$cl_name));
+$form->addInput(array('type'=>'textarea','name'=>'address','maxlength'=>'255','class'=>'mobile-textarea','value'=>$cl_address));
+$form->addInput(array('type'=>'floatfield','name'=>'tax','size'=>'10','format'=>'.2','value'=>$cl_tax));
+if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode)
+  $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->isPost()) {
+  // Validate user input.
+  if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.client_name'));
+  if (!ttValidString($cl_address, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.client_address'));
+  if (!ttValidFloat($cl_tax, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.tax'));
+
+  if ($err->no()) {
+    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
+        $err->add($i18n->getKey('error.db'));
+     } else
+       $err->add($i18n->getKey('error.client_exists'));
+  }
+} // isPost
+
+$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', 'mobile/client_add.tpl');
+$smarty->display('mobile/index.tpl');
diff --git a/mobile/client_delete.php b/mobile/client_delete.php
new file mode 100644 (file)
index 0000000..a0caf4f
--- /dev/null
@@ -0,0 +1,73 @@
+<?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->isPost()) {
+  if(ttClientHelper::getClient($id)) {
+    if ($request->getParameter('btn_delete')) {
+      if (ttClientHelper::delete($id, $request->getParameter('delete_client_entries'))) {
+        header('Location: clients.php');
+        exit();
+      } else
+        $err->add($i18n->getKey('error.db'));
+    }
+  } else 
+      $err->add($i18n->getKey('error.db'));
+
+  if ($request->getParameter('btn_cancel')) {
+    header('Location: clients.php');
+    exit();
+  }
+} // isPost
+
+$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', 'mobile/client_delete.tpl');
+$smarty->display('mobile/index.tpl');
diff --git a/mobile/client_edit.php b/mobile/client_edit.php
new file mode 100644 (file)
index 0000000..2c553da
--- /dev/null
@@ -0,0 +1,121 @@
+<?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->isPost()) {
+  $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','value'=>$cl_name));
+$form->addInput(array('type'=>'textarea','name'=>'address','maxlength'=>'255','class'=>'mobile-textarea','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'))));
+if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode)
+  $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->isPost()) {
+  // Validate user input.
+  if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.client_name'));
+  if (!ttValidString($cl_address, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.client_address'));
+  if (!ttValidFloat($cl_tax, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.tax'));
+
+  if ($err->no()) {
+    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
+          $err->add($i18n->getKey('error.db'));
+      } else
+        $err->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
+          $err->add($i18n->getKey('error.db'));
+      } else
+        $err->add($i18n->getKey('error.client_exists'));
+    }
+  }
+} // isPost
+
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('title', $i18n->getKey('title.edit_client'));
+$smarty->assign('content_page_name', 'mobile/client_edit.tpl');
+$smarty->display('mobile/index.tpl');
diff --git a/mobile/clients.php b/mobile/clients.php
new file mode 100644 (file)
index 0000000..d6ebbe4
--- /dev/null
@@ -0,0 +1,43 @@
+<?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', 'mobile/clients.tpl');
+$smarty->display('mobile/index.tpl');
diff --git a/mobile/expense_delete.php b/mobile/expense_delete.php
new file mode 100644 (file)
index 0000000..38800b6
--- /dev/null
@@ -0,0 +1,78 @@
+<?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->isPost()) {
+  if ($request->getParameter('delete_button')) { // Delete button pressed.
+
+    // Determine if it is okay to delete the record.
+    $item_date = new DateAndTime(DB_DATEFORMAT, $expense_item['date']);
+    if ($user->isDateLocked($item_date))
+      $err->add($i18n->getKey('error.range_locked'));
+
+    if ($err->no()) {
+      // Mark the record as deleted.
+      if (ttExpenseHelper::markDeleted($cl_id, $user->getActiveUser())) {
+        header('Location: expenses.php');
+        exit();
+      } else
+        $err->add($i18n->getKey('error.db'));
+    }
+  }
+  if ($request->getParameter('cancel_button')) { // Cancel button pressed.
+    header('Location: expenses.php');
+    exit();
+  }
+} // isPost
+
+$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');
diff --git a/mobile/expense_edit.php b/mobile/expense_edit.php
new file mode 100644 (file)
index 0000000..e14372e
--- /dev/null
@@ -0,0 +1,207 @@
+<?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->isPost()) {
+  $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 && $user->isPluginEnabled('cl')) {
+  $active_clients = ttTeamHelper::getActiveClients($user->team_id, true);
+  $form->addInput(array('type'=>'combobox',
+    'onchange'=>'fillProjectDropdown(this.value);',
+    'name'=>'client',
+    'style'=>'width: 250px;',
+    'value'=>$cl_client,
+    'data'=>$active_clients,
+    'datakeys'=>array('id', 'name'),
+    'empty'=>array(''=>$i18n->getKey('dropdown.select'))));
+  // Note: in other modes the client list is filtered to relevant clients only. See below.
+}
+
+if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) {
+  // Dropdown for projects assigned to user.
+  $project_list = $user->getAssignedProjects();
+  $form->addInput(array('type'=>'combobox',
+    'name'=>'project',
+    'style'=>'width: 250px;',
+    'value'=>$cl_project,
+    'data'=>$project_list,
+    'datakeys'=>array('id','name'),
+    'empty'=>array(''=>$i18n->getKey('dropdown.select'))));
+
+  // Dropdown for clients if the clients plugin is enabled.
+  if ($user->isPluginEnabled('cl')) {
+    $active_clients = ttTeamHelper::getActiveClients($user->team_id, true);
+    // We need an array of assigned project ids to do some trimming. 
+    foreach($project_list as $project)
+      $projects_assigned_to_user[] = $project['id'];
+
+    // Build a client list out of active clients. Use only clients that are relevant to user.
+    // Also trim their associated project list to only assigned projects (to user).
+    foreach($active_clients as $client) {
+      $projects_assigned_to_client = explode(',', $client['projects']);
+      $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->isPost()) {
+  // Validate user input.
+  if ($user->isPluginEnabled('cl') && $user->isPluginEnabled('cm') && !$cl_client)
+    $err->add($i18n->getKey('error.client'));
+  if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) {
+    if (!$cl_project) $err->add($i18n->getKey('error.project'));
+  }
+  if (!ttValidString($cl_item_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.item'));
+  if (!ttValidFloat($cl_cost)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.cost'));
+  if (!ttValidDate($cl_date)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.date'));
+
+  // 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))
+      $err->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 range).
+    // 2) Prohibit saving unlocked entries into locked range.
+
+    // Now, step by step.
+    // 1) Prohibit saving locked entries in any form.
+    if ($user->isDateLocked($item_date))
+      $err->add($i18n->getKey('error.range_locked'));
+
+    // 2) Prohibit saving unlocked entries into locked range.
+    if ($err->no() && $user->isDateLocked($new_date))
+      $err->add($i18n->getKey('error.range_locked'));
+
+    // Now, an update.
+    if ($err->no()) {
+      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 ($user->isDateLocked($new_date))
+      $err->add($i18n->getKey('error.range_locked'));
+
+    // Now, a new insert.
+    if ($err->no()) {
+      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
+        $err->add($i18n->getKey('error.db'));
+    }
+  }
+
+  if ($request->getParameter('btn_delete')) {
+    header("Location: expense_delete.php?id=$cl_id");
+    exit();
+  }
+} // isPost
+
+$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', 'mobile/expense_edit.tpl');
+$smarty->display('mobile/index.tpl');
diff --git a/mobile/expenses.php b/mobile/expenses.php
new file mode 100644 (file)
index 0000000..4246f58
--- /dev/null
@@ -0,0 +1,198 @@
+<?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;
+
+// Determine previous and next dates for simple navigation.
+$prev_date = date('Y-m-d', strtotime('-1 day', strtotime($cl_date)));
+$next_date = date('Y-m-d', strtotime('+1 day', strtotime($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 && $user->isPluginEnabled('cl')) {
+    $active_clients = ttTeamHelper::getActiveClients($user->team_id, true);
+    $form->addInput(array('type'=>'combobox',
+      'onchange'=>'fillProjectDropdown(this.value);',
+      'name'=>'client',
+      'style'=>'width: 250px;',
+      'value'=>$cl_client,
+      'data'=>$active_clients,
+      'datakeys'=>array('id', 'name'),
+      'empty'=>array(''=>$i18n->getKey('dropdown.select'))));
+  // Note: in other modes the client list is filtered to relevant clients only. See below.
+}
+
+if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) {
+  // Dropdown for projects assigned to user.
+  $project_list = $user->getAssignedProjects();
+  $form->addInput(array('type'=>'combobox',
+    // 'onchange'=>'fillTaskDropdown(this.value);',
+    'name'=>'project',
+    'style'=>'width: 250px;',
+    'value'=>$cl_project,
+    'data'=>$project_list,
+    'datakeys'=>array('id','name'),
+    'empty'=>array(''=>$i18n->getKey('dropdown.select'))));
+
+  // Dropdown for clients if the clients plugin is enabled.
+  if ($user->isPluginEnabled('cl')) {
+    $active_clients = ttTeamHelper::getActiveClients($user->team_id, true);
+    // We need an array of assigned project ids to do some trimming. 
+    foreach($project_list as $project)
+      $projects_assigned_to_user[] = $project['id'];
+
+    // Build a client list out of active clients. Use only clients that are relevant to user.
+    // Also trim their associated project list to only assigned projects (to user).
+    foreach($active_clients as $client) {
+      $projects_assigned_to_client = explode(',', $client['projects']);
+      $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')));
+
+// Submit.
+if ($request->isPost()) {
+  if ($request->getParameter('btn_submit')) {
+    // Validate user input.
+    if ($user->isPluginEnabled('cl') && $user->isPluginEnabled('cm') && !$cl_client)
+      $err->add($i18n->getKey('error.client'));
+    if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) {
+      if (!$cl_project) $err->add($i18n->getKey('error.project'));
+    }
+    if (!ttValidString($cl_item_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.item'));
+    if (!ttValidFloat($cl_cost)) $err->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))
+        $err->add($i18n->getKey('error.future_date'));
+    }
+    // Finished validating input data.
+
+    // Prohibit creating entries in locked range.
+    if ($user->isDateLocked($selected_date))
+      $err->add($i18n->getKey('error.range_locked'));
+
+    // Insert record.
+    if ($err->no()) {
+      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
+        $err->add($i18n->getKey('error.db'));
+    }
+  } elseif ($request->getParameter('onBehalfUser')) {
+    if($user->canManageTeam()) {
+      unset($_SESSION['behalf_id']);
+      unset($_SESSION['behalf_name']);
+
+      if($on_behalf_id != $user->id) {
+        $_SESSION['behalf_id'] = $on_behalf_id;
+        $_SESSION['behalf_name'] = ttUserHelper::getUserName($on_behalf_id);
+      }
+      header('Location: expenses.php');
+      exit();
+    }
+  }
+}
+
+$smarty->assign('next_date', $next_date);
+$smarty->assign('prev_date', $prev_date);
+$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', 'mobile/expenses.tpl');
+$smarty->display('mobile/index.tpl');
diff --git a/mobile/project_add.php b/mobile/project_add.php
new file mode 100644 (file)
index 0000000..4c9ad18
--- /dev/null
@@ -0,0 +1,95 @@
+<?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->isPost()) {
+  $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','value'=>$cl_name));
+$form->addInput(array('type'=>'textarea','name'=>'description','class'=>'mobile-textarea','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->isPost()) {
+  // Validate user input.
+  if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name'));
+  if (!ttValidString($cl_description, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.description'));
+
+  if ($err->no()) {
+    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
+          $err->add($i18n->getKey('error.db'));
+    } else
+      $err->add($i18n->getKey('error.project_exists'));
+  }
+} // isPost
+
+$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', 'mobile/project_add.tpl');
+$smarty->display('mobile/index.tpl');
diff --git a/mobile/project_delete.php b/mobile/project_delete.php
new file mode 100644 (file)
index 0000000..79ed438
--- /dev/null
@@ -0,0 +1,69 @@
+<?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->isPost()) {
+  if ($request->getParameter('btn_delete')) {
+    if(ttProjectHelper::get($cl_project_id)) {
+      if (ttProjectHelper::delete($cl_project_id)) {
+        header('Location: projects.php');
+        exit();
+      } else
+        $err->add($i18n->getKey('error.db'));
+    } else
+      $err->add($i18n->getKey('error.db'));
+  } elseif ($request->getParameter('btn_cancel')) {
+    header('Location: projects.php');
+    exit();
+  }
+} // isPost
+
+$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', 'mobile/project_delete.tpl');
+$smarty->display('mobile/index.tpl');
diff --git a/mobile/project_edit.php b/mobile/project_edit.php
new file mode 100644 (file)
index 0000000..c1eb180
--- /dev/null
@@ -0,0 +1,133 @@
+<?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->isPost()) {
+  $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','value'=>$cl_name));
+$form->addInput(array('type'=>'textarea','name'=>'description','class'=>'mobile-textarea','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->isPost()) {
+  // Validate user input.
+  if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name'));
+  if (!ttValidString($cl_description, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.description'));
+
+  if ($err->no()) {
+    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
+           $err->add($i18n->getKey('error.db'));
+      } else
+        $err->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
+          $err->add($i18n->getKey('error.db'));
+      } else
+        $err->add($i18n->getKey('error.project_exists'));
+    }
+  }
+} // isPost
+
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('onload', 'onLoad="document.projectForm.project_name.focus()"');
+$smarty->assign('title', $i18n->getKey('title.edit_project'));
+$smarty->assign('content_page_name', 'mobile/project_edit.tpl');
+$smarty->display('mobile/index.tpl');
diff --git a/mobile/projects.php b/mobile/projects.php
new file mode 100644 (file)
index 0000000..33d1d51
--- /dev/null
@@ -0,0 +1,49 @@
+<?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', 'mobile/projects.tpl');
+$smarty->display('mobile/index.tpl');
diff --git a/mobile/task_add.php b/mobile/task_add.php
new file mode 100644 (file)
index 0000000..9319e2a
--- /dev/null
@@ -0,0 +1,84 @@
+<?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->isPost()) {
+  $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','value'=>$cl_name));
+$form->addInput(array('type'=>'textarea','name'=>'description','class'=>'mobile-textarea','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->isPost()) {
+  // Validate user input.
+  if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name'));
+  if (!ttValidString($cl_description, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.description'));
+
+  if ($err->no()) {
+    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
+          $err->add($i18n->getKey('error.db'));
+    } else
+      $err->add($i18n->getKey('error.task_exists'));
+  }
+} // isPost
+
+$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', 'mobile/task_add.tpl');
+$smarty->display('mobile/index.tpl');
diff --git a/mobile/task_delete.php b/mobile/task_delete.php
new file mode 100644 (file)
index 0000000..aa74be1
--- /dev/null
@@ -0,0 +1,69 @@
+<?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->isPost()) {
+  if ($request->getParameter('btn_delete')) {
+    if(ttTaskHelper::getTask($cl_task_id)) {
+      if (ttTaskHelper::delete($cl_task_id)) {
+        header('Location: tasks.php');
+        exit();
+      } else
+        $err->add($i18n->getKey('error.db'));
+    } else
+      $err->add($i18n->getKey('error.db'));
+  } elseif ($request->getParameter('btn_cancel')) {
+    header('Location: tasks.php');
+    exit();
+  }
+} // isPost
+
+$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', 'mobile/task_delete.tpl');
+$smarty->display('mobile/index.tpl');
diff --git a/mobile/task_edit.php b/mobile/task_edit.php
new file mode 100644 (file)
index 0000000..fd5b35b
--- /dev/null
@@ -0,0 +1,114 @@
+<?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->isPost()) {
+  $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','value'=>$cl_name));
+$form->addInput(array('type'=>'textarea','name'=>'description','class'=>'mobile-textarea','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->isPost()) {
+  // Validate user input.
+  if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name'));
+  if (!ttValidString($cl_description, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.description'));
+
+  if ($err->no()) {
+    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
+          $err->add($i18n->getKey('error.db'));
+      } else
+        $err->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
+          $err->add($i18n->getKey('error.db'));
+      } else
+        $err->add($i18n->getKey('error.task_exists'));
+    }
+  }
+} // isPost
+
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('title', $i18n->getKey('title.edit_task'));
+$smarty->assign('content_page_name', 'mobile/task_edit.tpl');
+$smarty->display('mobile/index.tpl');
diff --git a/mobile/tasks.php b/mobile/tasks.php
new file mode 100644 (file)
index 0000000..9b778bb
--- /dev/null
@@ -0,0 +1,43 @@
+<?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', 'mobile/tasks.tpl');
+$smarty->display('mobile/index.tpl');
index d0d3f44..a99835a 100644 (file)
@@ -184,7 +184,7 @@ if (!$user->canManageTeam() && defined('READONLY_START_FINISH') && isTrue(READON
 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: 60px;','value'=>$cl_note));
+$form->addInput(array('type'=>'textarea','name'=>'note','class'=>'mobile-textarea','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.
diff --git a/mobile/user_add.php b/mobile/user_add.php
new file mode 100644 (file)
index 0000000..f42dd4d
--- /dev/null
@@ -0,0 +1,174 @@
+<?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 ($user->isPluginEnabled('cl'))
+  $clients = ttTeamHelper::getActiveClients($user->team_id);
+
+$assigned_projects = array();
+if ($request->isPost()) {
+  $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
+        $err->add($i18n->getKey('error.field'), 'rate_'.$p);
+    }
+  }
+}
+
+$form = new Form('userForm');
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>$cl_name));
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'login','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 ($user->isPluginEnabled('cl'))
+  $roles[ROLE_CLIENT] = $i18n->getKey('label.client');
+$form->addInput(array('type'=>'combobox','onchange'=>'handleClientControl()','name'=>'role','value'=>$cl_role,'data'=>$roles));
+if ($user->isPluginEnabled('cl'))
+  $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->isPost()) {
+  // Validate user input.
+  if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.person_name'));
+  if (!ttValidString($cl_login)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.login'));
+  if (!$auth->isPasswordExternal()) {
+    if (!ttValidString($cl_password1)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.password'));
+    if (!ttValidString($cl_password2)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.confirm_password'));
+    if ($cl_password1 !== $cl_password2)
+      $err->add($i18n->getKey('error.not_equal'), $i18n->getKey('label.password'), $i18n->getKey('label.confirm_password'));
+  }
+  if (!ttValidEmail($cl_email, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.email'));
+  if (!ttValidFloat($cl_rate, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('form.users.default_rate'));
+
+  if ($err->no()) {
+    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
+        $err->add($i18n->getKey('error.db'));
+    } else
+      $err->add($i18n->getKey('error.user_exists'));
+  }
+} // isPost
+
+$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', 'mobile/user_add.tpl');
+$smarty->display('mobile/index.tpl');
diff --git a/mobile/user_delete.php b/mobile/user_delete.php
new file mode 100644 (file)
index 0000000..aa25a5b
--- /dev/null
@@ -0,0 +1,99 @@
+<?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->isPost()) {
+  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 {
+      $err->add($i18n->getKey('error.db'));
+    }
+  }
+  if ($request->getParameter('btn_cancel')) {
+    header('Location: users.php');
+    exit();
+  }
+} // isPost
+
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('title', $i18n->getKey('title.delete_user'));
+$smarty->assign('content_page_name', 'mobile/user_delete.tpl');
+$smarty->display('mobile/index.tpl');
diff --git a/mobile/user_edit.php b/mobile/user_edit.php
new file mode 100644 (file)
index 0000000..156e320
--- /dev/null
@@ -0,0 +1,237 @@
+<?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 ($user->isPluginEnabled('cl'))
+  $clients = ttTeamHelper::getActiveClients($user->team_id);
+
+$projects = ttTeamHelper::getActiveProjects($user->team_id);
+$assigned_projects = array();
+
+if ($request->isPost()) {
+  $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
+        $err->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','value'=>$cl_name));
+$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'login','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 ($user->isPluginEnabled('cl'))
+  $roles[ROLE_CLIENT] = $i18n->getKey('label.client');
+$form->addInput(array('type'=>'combobox','onchange'=>'handleClientControl()','name'=>'role','value'=>$cl_role,'data'=>$roles));
+if ($user->isPluginEnabled('cl'))
+  $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->isPost()) {
+  // Validate user input.
+  if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.person_name'));
+  if (!ttValidString($cl_login)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.login'));
+  if (!$auth->isPasswordExternal() && ($cl_password1 || $cl_password2)) {
+    if (!ttValidString($cl_password1)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.password'));
+    if (!ttValidString($cl_password2)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.confirm_password'));
+    if ($cl_password1 !== $cl_password2)
+      $err->add($i18n->getKey('error.not_equal'), $i18n->getKey('label.password'), $i18n->getKey('label.confirm_password'));
+  }
+  if (!ttValidEmail($cl_email, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.email'));
+  if (!ttValidFloat($cl_rate, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('form.users.default_rate'));
+
+  if ($err->no()) {
+    $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
+        $err->add($i18n->getKey('error.db'));
+    } else
+      $err->add($i18n->getKey('error.user_exists'));
+  }
+} // isPost
+
+$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', 'mobile/user_edit.tpl');
+$smarty->display('mobile/index.tpl');
diff --git a/mobile/users.php b/mobile/users.php
new file mode 100644 (file)
index 0000000..71caa6e
--- /dev/null
@@ -0,0 +1,51 @@
+<?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', 'mobile/users.tpl');
+$smarty->display('mobile/index.tpl');