// | https://www.anuko.com/time_tracker/credits.htm
// +----------------------------------------------------------------------+
-import('ttUser');
+import('ttRoleHelper');
// ttAdmin class is used to perform admin tasks.
+// Used as namespace, as it is a collection of static functions that we call
+// from admin pages to administer the site as a whole.
class ttAdmin {
- var $err = null; // Error object, passed to us as reference.
- // We use it to communicate errors to caller.
-
- // Constructor.
- function __construct(&$err = null) {
- $this->err = $err;
- }
-
// getSubgroups rerurns an array of subgroups for a group.
- function getSubgroups($group_id) {
- return array(); // TODO: not yet implemented.
- }
-
- // getUsers obtains user ids in a group.
- function getUsers($group_id) {
+ static function getSubgroups($group_id) {
$mdb2 = getConnection();
- $sql = "select id from tt_users where team_id = $group_id";
+
+ $subgroups = array();
+ $sql = "select id from tt_groups where parent_id = $group_id";
$res = $mdb2->query($sql);
- $users = array();
if (!is_a($res, 'PEAR_Error')) {
while ($val = $res->fetchRow()) {
- $users[] = $val;
+ $subgroups[] = $val;
}
}
- return $users;
+ return $subgroups;
}
- // markUserDeleted marks a user and all things associated with user as deleted.
- function markUserDeleted($user_id) {
+ // markGroupDeleted marks a group and everything in it as deleted.
+ // This function is called in context of a logged on admin who may
+ // operate on any group.
+ static function markGroupDeleted($group_id) {
$mdb2 = getConnection();
- // Mark user binds as deleted.
- $sql = "update tt_user_project_binds set status = NULL where user_id = $user_id";
- $affected = $mdb2->exec($sql);
- if (is_a($affected, 'PEAR_Error'))
- return false;
+ // Keep the logic simple by returning false on first error.
- // Mark favorite reports as deleted.
- $sql = "update tt_fav_reports set status = NULL where user_id = $user_id";
- $affected = $mdb2->exec($sql);
- if (is_a($affected, 'PEAR_Error'))
- return false;
+ // Obtain subgroups and call self recursively on them.
+ $subgroups = ttAdmin::getSubgroups($group_id);
+ foreach($subgroups as $subgroup) {
+ if (!ttAdmin::markGroupDeleted($subgroup['id']))
+ return false;
+ }
+
+ // Now do actual work with all entities.
+
+ // Some things cannot be marked deleted as we don't have the status field for them.
+ // Just delete such things (until we have a better way to deal with them).
+ $tables_to_delete_from = array(
+ 'tt_config',
+ 'tt_predefined_expenses',
+ 'tt_client_project_binds',
+ 'tt_project_task_binds'
+ );
+ foreach($tables_to_delete_from as $table) {
+ if (!ttAdmin::deleteGroupEntriesFromTable($group_id, $table))
+ return false;
+ }
- // Mark user as deleted.
+ // Now mark status deleted where we can.
+ // Note: we don't mark tt_log, tt_custom_field_lod, or tt_expense_items deleted here.
+ // Reasoning is:
+ //
+ // 1) Users may mark some of them deleted during their work.
+ // If we mark all of them deleted here, we can't recover nicely
+ // as we'll lose track of what was accidentally deleted by user.
+ //
+ // 2) DB maintenance script (Clean up DB from inactive groups) should
+ // get rid of these items permanently eventually.
+ $tables_to_mark_deleted_in = array(
+ 'tt_cron',
+ 'tt_fav_reports',
+ // 'tt_expense_items',
+ // 'tt_custom_field_log',
+ 'tt_custom_field_options',
+ 'tt_custom_fields',
+ // 'tt_log',
+ 'tt_invoices',
+ 'tt_user_project_binds',
+ 'tt_users',
+ 'tt_clients',
+ 'tt_projects',
+ 'tt_tasks',
+ 'tt_roles'
+ );
+ foreach($tables_to_mark_deleted_in as $table) {
+ if (!ttAdmin::markGroupDeletedInTable($group_id, $table))
+ return false;
+ }
+
+ // Mark group deleted.
global $user;
- $modified_part = ', modified = now(), modified_ip = '.$mdb2->quote($_SERVER['REMOTE_ADDR']).', modified_by = '.$mdb2->quote($user->id);
- $sql = "update tt_users set status = NULL $modified_part where id = $user_id";
+ $modified_part = ', modified = now(), modified_ip = '.$mdb2->quote($_SERVER['REMOTE_ADDR']).', modified_by = '.$user->id;
+ $sql = "update tt_groups set status = null $modified_part where id = $group_id";
$affected = $mdb2->exec($sql);
- if (is_a($affected, 'PEAR_Error'))
- return false;
+ if (is_a($affected, 'PEAR_Error')) return false;
return true;
}
- // The markTasksDeleted deletes task binds and marks the tasks as deleted for a group.
- function markTasksDeleted($group_id) {
- $mdb2 = getConnection();
- $sql = "select id from tt_tasks where team_id = $group_id";
- $res = $mdb2->query($sql);
- if (is_a($res, 'PEAR_Error')) return false;
+ // updateGroup updates a (top) group with new information.
+ static function updateGroup($fields) {
+ $group_id = (int)$fields['group_id'];
+ if (!$group_id) return false; // Nothing to update.
- while ($val = $res->fetchRow()) {
- // Delete task binds.
- $task_id = $val['id'];
- $sql = "delete from tt_project_task_binds where task_id = $task_id";
- $affected = $mdb2->exec($sql);
- if (is_a($affected, 'PEAR_Error')) return false;
+ $mdb2 = getConnection();
+ global $user;
+ $modified_part = ', modified = now(), modified_ip = '.$mdb2->quote($_SERVER['REMOTE_ADDR']).', modified_by = '.$user->id;
- // Mark task as deleted.
- $sql = "update tt_tasks set status = NULL where id = $task_id";
+ // Update group name if it changed.
+ if ($fields['old_group_name'] != $fields['new_group_name']) {
+ $name_part = 'name = '.$mdb2->quote($fields['new_group_name']);
+ $sql = 'update tt_groups set '.$name_part.$modified_part.' where id = '.$group_id;
$affected = $mdb2->exec($sql);
if (is_a($affected, 'PEAR_Error')) return false;
}
+
+ // Update group manager.
+ $user_id = $fields['user_id'];
+ $login_part = 'login = '.$mdb2->quote($fields['new_login']);
+ if ($fields['password1'])
+ $password_part = ', password = md5('.$mdb2->quote($fields['password1']).')';
+ $name_part = ', name = '.$mdb2->quote($fields['user_name']);
+ $email_part = ', email = '.$mdb2->quote($fields['email']);
+ $sql = 'update tt_users set '.$login_part.$password_part.$name_part.$email_part.$modified_part.' where id = '.$user_id;
+ $affected = $mdb2->exec($sql);
+ if (is_a($affected, 'PEAR_Error')) return false;
+
return true;
}
- // markGroupDeleted marks the group and everything in it as deleted.
- function markGroupDeleted($group_id) {
+ // updateSelf updates admin account with new information.
+ static function updateSelf($fields) {
+ global $user;
+ $mdb2 = getConnection();
- // Keep the logic simple by returning false on first error.
+ // Update self.
+ $user_id = $user->id;
+ $login_part = 'login = '.$mdb2->quote($fields['login']);
+ if ($fields['password1'])
+ $password_part = ', password = md5('.$mdb2->quote($fields['password1']).')';
+ $name_part = ', name = '.$mdb2->quote($fields['name']);
+ $email_part = ', email = '.$mdb2->quote($fields['email']);
+ $modified_part = ', modified = now(), modified_ip = '.$mdb2->quote($_SERVER['REMOTE_ADDR']).', modified_by = '.$user->id;
+ $sql = 'update tt_users set '.$login_part.$password_part.$name_part.$email_part.$modified_part.' where id = '.$user_id;
+ $affected = $mdb2->exec($sql);
+ return (!is_a($affected, 'PEAR_Error'));
+ }
- // Obtain subgroups and call self recursively on them.
- $subgroups = $this->getSubgroups();
- foreach($subgroups as $subgroup) {
- if (!$this->markGroupDeleted($subgroup['id']))
- return false;
+ // getGroupName obtains group name.
+ static function getGroupName($group_id) {
+ $result = array();
+ $mdb2 = getConnection();
+
+ $sql = "select name from tt_groups where id = $group_id";
+
+ $res = $mdb2->query($sql);
+ if (!is_a($res, 'PEAR_Error')) {
+ $val = $res->fetchRow();
+ return $val['name'];
}
- // Now that we are done with subgroups, handle this group.
- $users = $this->getUsers($group_id);
+ return false;
+ }
+
+ // getOrgDetails obtains group name and its top manager details.
+ static function getOrgDetails($group_id) {
+ $result = array();
+ $mdb2 = getConnection();
- // Iterate through team users and mark them as deleted.
- foreach ($users as $one_user) {
- if (!$this->markUserDeleted($one_user['id']))
- return false;
+ // Note: current code works with properly set top manager (rank 512).
+ // However, we now allow export and import of subgroups, which seems to work well.
+ // In this situation, imported role is no longer "Top manager", and this call fails.
+ // Setting role id manually in database for top user to Top manager resolves the issue.
+ //
+ // TODO: assess whether it is safe / reasonable to promote role during export or import.
+ // The problem is that user having 'export_data' right is not necessarily top user.
+ // And if we do it by rank, what to do for multiple managers situation? First found?
+ // Leaving to manual fixing for now.
+ $sql = "select g.name as group_name, u.id as manager_id, u.name as manager_name, u.login as manager_login, u.email as manager_email".
+ " from tt_groups g".
+ " inner join tt_users u on (u.group_id = g.id)".
+ " inner join tt_roles r on (r.id = u.role_id and r.rank = 512)". // Fails for partially imported org. See comment above.
+ " where g.id = $group_id";
+
+ $res = $mdb2->query($sql);
+ if (!is_a($res, 'PEAR_Error')) {
+ $val = $res->fetchRow();
+ return $val;
}
- // Mark tasks deleted.
- if (!$this->markTasksDeleted($group_id)) return false;
+ return false;
+ }
+ // deleteGroupEntriesFromTable is a generic helper function for markGroupDeleted.
+ // It deletes entries in ONE table belonging to a given group.
+ static function deleteGroupEntriesFromTable($group_id, $table_name) {
$mdb2 = getConnection();
- // Mark roles deleted.
- $sql = "update tt_roles set status = NULL where team_id = $group_id";
+ $sql = "delete from $table_name where group_id = $group_id";
$affected = $mdb2->exec($sql);
- if (is_a($affected, 'PEAR_Error')) return false;
+ return (!is_a($affected, 'PEAR_Error'));
+ }
- // Mark projects deleted.
- $sql = "update tt_projects set status = NULL where team_id = $group_id";
- $affected = $mdb2->exec($sql);
- if (is_a($affected, 'PEAR_Error')) return false;
+ // markGroupDeletedInTable is a generic helper function for markGroupDeleted.
+ // It updates ONE table by setting status to NULL for all records belonging to a group.
+ static function markGroupDeletedInTable($group_id, $table_name) {
+ $mdb2 = getConnection();
- // Mark clients deleted.
- $sql = "update tt_clients set status = NULL where team_id = $group_id";
- $affected = $mdb2->exec($sql);
- if (is_a($affected, 'PEAR_Error')) return false;
+ // Add modified info to sql for some tables, depending on table name.
+ if ($table_name == 'tt_users') {
+ global $user;
+ $modified_part = ', modified = now(), modified_ip = '.$mdb2->quote($_SERVER['REMOTE_ADDR']).', modified_by = '.$user->id;
+ }
- // Mark invoices deleted.
- $sql = "update tt_invoices set status = NULL where team_id = $group_id";
+ $sql = "update $table_name set status = null $modified_part where group_id = $group_id";
$affected = $mdb2->exec($sql);
- if (is_a($affected, 'PEAR_Error')) return false;
+ return (!is_a($affected, 'PEAR_Error'));
+ }
- // Mark custom fields deleted.
- $sql = "update tt_custom_fields set status = NULL where team_id = $group_id";
- $affected = $mdb2->exec($sql);
- if (is_a($affected, 'PEAR_Error')) return false;
+ // createGroup creates a new top group and returns its id.
+ // It is a helper function for createOrg.
+ static function createGroup($fields) {
+ global $user;
+ $mdb2 = getConnection();
+
+ $name = $mdb2->quote($fields['group_name']);
+ $currency = $mdb2->quote($fields['currency']);
+ $lang = $mdb2->quote($fields['lang']);
+ $created = 'now()';
+ $created_ip = $mdb2->quote($_SERVER['REMOTE_ADDR']);
+ $created_by = $user->id;
- // Mark notifications deleted.
- $sql = "update tt_cron set status = NULL where team_id = $group_id";
+ $sql = "insert into tt_groups (name, currency, lang, created, created_ip, created_by)".
+ " values($name, $currency, $lang, $created, $created_ip, $created_by)";
$affected = $mdb2->exec($sql);
if (is_a($affected, 'PEAR_Error')) return false;
- // Note: we don't mark tt_log or tt_expense_items deleted here.
- // Reasoning is:
- //
- // 1) Users may mark some of them deleted during their work.
- // If we mark all of them deleted here, we can't recover nicely
- // as we'll lose track of what was deleted by user.
- //
- // 2) DB maintenance script (Clean up DB from inactive teams) should
- // get rid of these items permanently eventually.
+ $group_id = $mdb2->lastInsertID('tt_groups', 'id');
- // Mark group deleted.
- global $user;
- $modified_part = ', modified = now(), modified_ip = '.$mdb2->quote($_SERVER['REMOTE_ADDR']).', modified_by = '.$mdb2->quote($user->id);
- $sql = "update tt_teams set status = NULL $modified_part where id = $group_id";
+ // Update org_id with group_id.
+ $sql = "update tt_groups set org_id = $group_id where org_id is NULL and id = $group_id";
$affected = $mdb2->exec($sql);
if (is_a($affected, 'PEAR_Error')) return false;
- return true;
+ return $group_id;
}
- // validateTeamInfo validates team information entered by user.
- function validateTeamInfo($fields) {
- global $i18n;
- global $auth;
-
- $result = true;
-
- if (!ttValidString($fields['group_name'], true)) {
- $this->err->add($i18n->getKey('error.field'), $i18n->getKey('label.team_name'));
- $result = false;
- }
- if (!ttValidString($fields['user_name'])) {
- $this->err->add($i18n->getKey('error.field'), $i18n->getKey('label.manager_name'));
- $result = false;
- }
- if (!ttValidString($fields['new_login'])) {
- $this->err->add($i18n->getKey('error.field'), $i18n->getKey('label.manager_login'));
- $result = false;
- }
-
- // If we change login, it must be unique.
- if ($fields['new_login'] != $fields['old_login']) {
- if (ttUserHelper::getUserByLogin($fields['new_login'])) {
- $this->err->add($i18n->getKey('error.user_exists'));
- $result = false;
- }
- }
-
- if (!$auth->isPasswordExternal() && ($fields['password1'] || $fields['password2'])) {
- if (!ttValidString($fields['password1'])) {
- $this->err->add($i18n->getKey('error.field'), $i18n->getKey('label.password'));
- $result = false;
- }
- if (!ttValidString($fields['password2'])) {
- $this->err->add($i18n->getKey('error.field'), $i18n->getKey('label.confirm_password'));
- $result = false;
- }
- if ($fields['password1'] !== $fields['password2']) {
- $this->err->add($i18n->getKey('error.not_equal'), $i18n->getKey('label.password'), $i18n->getKey('label.confirm_password'));
- $result = false;
- }
- }
- if (!ttValidEmail($fields['email'], true)) {
- $this->err->add($i18n->getKey('error.field'), $i18n->getKey('label.email'));
- $result = false;
- }
+ // createOrgManager creates a new user (top manager role) in a group.
+ // It is a helper function for createOrg.
+ static function createOrgManager($fields) {
+ global $user;
+ $mdb2 = getConnection();
- return $result;
+ $role_id = ttRoleHelper::getTopManagerRoleID();
+ $login = $mdb2->quote($fields['login']);
+ $password = 'md5('.$mdb2->quote($fields['password']).')';
+ $name = $mdb2->quote($fields['user_name']);
+ $group_id = (int) $fields['group_id'];
+ $org_id = $group_id;
+ $email = $mdb2->quote($fields['email']);
+ $created = 'now()';
+ $created_ip = $mdb2->quote($_SERVER['REMOTE_ADDR']);
+ $created_by = $user->id;
+
+ $columns = '(login, password, name, group_id, org_id, role_id, email, created, created_ip, created_by)';
+ $values = "values($login, $password, $name, $group_id, $org_id, $role_id, $email, $created, $created_ip, $created_by)";
+
+ $sql = "insert into tt_users $columns $values";
+ $affected = $mdb2->exec($sql);
+ return (!is_a($affected, 'PEAR_Error'));
}
- // updateTeam validates user input and updates the team with new information.
- function updateTeam($team_id, $fields) {
- if (!$this->validateTeamInfo($fields)) return false; // Can't continue as user input is invalid.
+ // The createOrg function creates an organization in Time Tracker.
+ static function createOrg($fields) {
+ // There are 3 steps that we need to 2 when creating a new organization.
+ // 1. Create a new group with null parent_id.
+ // 2. Create pre-defined roles in it.
+ // 3. Create a top manager account for new group.
- global $user;
- $mdb2 = getConnection();
+ // Create a new group.
+ $group_id = ttAdmin::createGroup($fields);
+ if (!$group_id) return false;
- // Update group name if it changed.
- if ($fields['old_group_name'] != $fields['new_group_name']) {
- $name_part = 'name = '.$mdb2->quote($fields['new_group_name']);
- $modified_part = ', modified = now(), modified_ip = '.$mdb2->quote($_SERVER['REMOTE_ADDR']).', modified_by = '.$mdb2->quote($user->id);
- $sql = 'update tt_teams set '.$name_part.$modified_part.' where id = '.$team_id;
- $affected = $mdb2->exec($sql);
- if (is_a($affected, 'PEAR_Error')) return false;
- }
+ // Create predefined roles.
+ if (!ttRoleHelper::createPredefinedRoles($group_id, $fields['lang']))
+ return false;
- // Update group manager.
- $user_id = $fields['user_id'];
- $login_part = 'login = '.$mdb2->quote($fields['new_login']);
- if ($fields['password1'])
- $password_part = ', password = md5('.$mdb2->quote($fields['password1']).')';
- $name_part = ', name = '.$mdb2->quote($fields['user_name']);
- $email_part = ', email = '.$mdb2->quote($fields['email']);
- $modified_part = ', modified = now(), modified_ip = '.$mdb2->quote($_SERVER['REMOTE_ADDR']).', modified_by = '.$mdb2->quote($user->id);
- $sql = 'update tt_users set '.$login_part.$password_part.$name_part.$email_part.$modified_part.'where id = '.$user_id;
- $affected = $mdb2->exec($sql);
- if (is_a($affected, 'PEAR_Error')) return false;
+ // Create user.
+ $fields['group_id'] = $group_id;
+ if (!ttAdmin::createOrgManager($fields))
+ return false;
return true;
}