avatar chrisbloom7 Jan 27. 2010. 10:03 pm
We had a specific need in our company: Allow any employee of the company to work on any ticket (task, milestone, etc.) and track time in any project without having to actual assign everyone to everything. It turns out this is a very easy hack to implement and led to some other creative uses. Below is the HOW TO, followed by some of the extra things we could do afterward.

HOW TO enable any employee access to any project

The first step is to define just what an employee is and enable the system to detect this. The most obvious direction would be to just check if the user was created under the owner company. However, we decided to add an additional requirement: the user had to also be part of a system role with the can_see_private_objects permission enabled. This additional requirement meant we could still have contractors, temps, interns, etc. under the owner corp, but without giving them access to everything that a true employee had. (*)

Once you've got your requirements defined you can now create a new method in the User model class:
# User.class.php

    /**
     * Returns true if this user is member of owner company and an actual employee.
     * Employee status is based on whether their system role has the 
     * can_see_private_objects permission enabled. This allows us to create non-
     * employee roles under the owner company, such as might be required for 
     * an intern or contractor.
     *
     * @param void
     * @return boolean
     */
    function isEmployee() {
      if($this->is_employee === null) {
        $this->is_employee = ($this->isOwner() && $this->canSeePrivate());
      } // if
      return $this->is_employee;
    } // isEmployee


Now we can test any user object to see if it's an employee:
if ($user->isEmployee()) { ... }


The next step is to decide what level of access you want all of your employees to have? By default, non-admin users have PROJECT_PERMISSION_NONE access to projects they aren't assigned to. The other available permissions are as follows:

[list]
[*]PROJECT_PERMISSION_ACCESS - employees will be able to view existing project objects, but not create any new ones (including timerecords)
[*]PROJECT_PERMISSION_CREATE - employees can create new project objects such as tickets, tasks and timerecords, but won't be able to edit them (including changing the status or completing them)
[*]PROJECT_PERMISSION_MANAGE - employees will have the same level of access as a project manager. They can create, complete and edit all project objects.
[/list]

Now we need to modify two files to make sure our employees can access any project object. (As you can see below, we decided to use the MANAGE permission, but just replace with whatever permission level you feel is appropriate.)

First, go back into your User model and alter the getProjectPermission function to the following:
# User.class.php

    function getProjectPermission($name, $project) {
      if($this->isAdministrator() || $this->isProjectManager() || $this->isProjectLeader($project)) {
        return PROJECT_PERMISSION_MANAGE;
      } elseif ($this->isEmployee()) {
        return PROJECT_PERMISSION_MANAGE;
      } else {
        $project_user = $this->getProjectUserInstance($project);
        return instance_of($project_user, 'ProjectUser') ? $project_user->getPermissionValue($name) : PROJECT_PERMISSION_NONE;
      } // if
    } // getProjectPermission


Second, go into your ProjectUsers model and find the isProjectMember function. Change the following lines:
# ProjectUsers.class.php::isProjectMember

    	if($user->isAdministrator() || $user->isProjectManager()) {
    	  $cache[$project_id][$user_id] = true;
    	} else {


To:
# ProjectUsers.class.php::isProjectMember

    	if($user->isAdministrator() || $user->isProjectManager() || $user->isEmployee()) {
    	  $cache[$project_id][$user_id] = true;
    	} else {


And then.... That's it! Now any employee can access any project object without having to explicitly assign them!

Oh, there's one caveat: If a client clicks on the the name of an employee that's not explicitly assigned to one of their projects (such as if said employee leaves a comment or subscribes to an object) then they will get an HTTP_ERR_NOT_FOUND error message. You can handle this in one of two ways:

[list=1]
[*]Change it to a HTTP_ERR_FORBIDDEN error, which is more intuitive, though still not helpful
[*]Or, make sure any user can see any employee profile
[/list]

Either solution can be addressed by modifying the __construct function of the UsersController class. For the first approach, simply replace the former error code constant with the latter. For the second approach, modify the lines:

# UsersController.class.php

    	  if(!in_array($this->active_user->getId(), $this->logged_user->visibleUserIds())) {
      	  $this->httpError(HTTP_ERR_NOT_FOUND);
      	} // if


To:
# UsersController.class.php

    	  if(!$this->active_user->isEmployee() && !in_array($this->active_user->getId(), $this->logged_user->visibleUserIds())) {
      	  $this->httpError(HTTP_ERR_FORBIDDEN);
      	} // if


As you can see, we went with both solutions. (I believe the use of the original error code was just an oversight on the A51's end.)

And that is REALLY it!

Extras

So now that your application is employee-aware, you can start coding up features just for employees. Here are some of the things that we've done:

[list]
[*]Add a new checkbox to the assignment filter form that lets us override the default assignment filters for employees. (i.e. let employees see project objects from from across ALL projects. Handy for "unassigned" filters, so any employee can take any unassigned tickets)
[*]Show additional elements that only employees can see, such as a select box to change the status of a ticket when submitting a comment.
[*]Hide elements from non-employees, such as "complete" buttons (i.e. only employees can complete an object.)
[/list]

(*) You could also approach this by creating an new system permission such as "and_is_employee" which wouldn't limit you to users in the owner company, but we didn't have that requirement.
avatar matthewporter Pro Mar 22. 2010. 7:35 am
Hi Chris,

Nice hack.

I'm most interested in how you did the hack to allow employees to override the default assignment filters to show ALL projects.

Could you share these hacks as well, please?

Many thanks,

Matthew
avatar chrisbloom7 Mar 22. 2010. 4:28 pm
Hey Matt - I'll have to sift through the myriad other changes that I committed at the same time as this update, along with a few patches since our go-live, and then I'll post the general procedure. The framework itself, specifically the heavy reliance on reusable, inheritable model objects, makes it incredibly easy to accomplish, however. I think the total number of changes required for this amounts to under a dozen lines of code.
avatar matthewporter Pro Mar 22. 2010. 10:37 pm
Hi Chris,

Thanks for the quick response.

Understand you have to sift out changes ... but perhaps you could give me a "broad overview" of how you achieved it (which modules, functions, etc?).

Would greatly appreciate it. I have a rather urgent need.

Many thanks,

Matthew
avatar chrisbloom7 Mar 23. 2010. 2:52 am
Very quick overview:

Essentially you'll need to edit your AssignmentFilter class to override the default $conditions array if the user is an employee. Look for the prepareConditions function. You'll probably want to do this in the section marked with the comment "All Projects". That should be enough to let them see everything across all projects. (You may also want to modify the conditions so that only objects from active projects are returned. Best place is prob in the "Additional filters" section of the same function.)

Now, I can't recall off the top of my head, but you may also need to modify the ProjectUsers class (specifically the getVisibleTypesFilter and getVisibleTypesFilterByProject functions) in order to let an employee actually open/modify an otherwise unassigned ticket this way.
avatar matthewporter Pro Mar 23. 2010. 3:09 am
Thanks Chris.

You've confirmed my research to date in terms of where to look.

I must admit however I'm still struggling to understand how the permissions component is applied to projects / project items.

If you can post your own code from these sections when you have a minute, it would be most helpful.

Many thanks.
avatar matthewporter Pro Mar 23. 2010. 5:35 am
Solved it. :)

I changed this code:

$rows = db_execute_all("SELECT $project_users_table.project_id, $project_users_table.role_id, $project_users_table.permissions, $projects_table.leader_id FROM $project_users_table, $projects_table WHERE $project_users_table.user_id = ? AND $project_users_table.project_id = $projects_table.id AND $projects_table.status IN (?)", $user->getId(), $project_statuses);


Into this:

// Administrators and Project Managers can see all projects
      if($user->isAdministrator() || $user->isProjectManager()) {
	  	  $rows = db_execute_all("SELECT $projects_table.id AS project_id FROM $projects_table WHERE $projects_table.status IN (?)", $project_statuses);
	  } else {
	  	  $rows = db_execute_all("SELECT $project_users_table.project_id, $project_users_table.role_id, $project_users_table.permissions, $projects_table.leader_id FROM $project_users_table, $projects_table WHERE $project_users_table.user_id = ? AND $project_users_table.project_id = $projects_table.id AND $projects_table.status IN (?)", $user->getId(), $project_statuses);
      }


in the getVisibleTypesFilter function/method of the AssignmentFilter.class.php file.

That way, Administrators and Project Managers can see ALL tasks in the Assignment filter.
avatar chrisbloom7 Mar 23. 2010. 1:52 pm
Nice. I'm pretty sure that's exactly how I solved it too.