Allowing regular users access to create projects instead of just Admins

Hi everyone -- we just started using PP and we really needed normal users to be able to create projects. My boss, who would be the most appropriate person to create projects, doesn't need administrative access. Our team is also small enough that most of my co-workers would need to be able to create their own projects and incorporate each other where necessary.

Anyways -- so I spent a few days hacking the code, and I was successful. PP now works in this way:
Admins & Normal users can create projects, but normal users only have control over the projects they create ("own") themselves. They can edit, delete, add users, etc, to their own projects. Admin is added by default. This particular feature can be turned on or off, by the admin, in the user profile manager.

Here are the changes I made -- I'm pretty sure I got them all below.

First -- I had to make a small modification to the database:

Edit table "pp_users", add a column called "can_create_projects", TINYINT(1), ALLOW_NULLS(0)

Now we have to edit the BaseUsers class. The file is located in /application/models/users/base/BaseUsers.class.php; changes begin on line 17. This change adds the "can_create_projects" property to the columns stored from the database query.

static private $columns = array('id' => DATA_TYPE_INTEGER, 'company_id' => DATA_TYPE_INTEGER, 'username' => DATA_TYPE_STRING, 'email' => DATA_TYPE_STRING, 'token' => DATA_TYPE_STRING, 'salt' => DATA_TYPE_STRING, 'twister' => DATA_TYPE_STRING, 'display_name' => DATA_TYPE_STRING, 'title' => DATA_TYPE_STRING, 'avatar_file' => DATA_TYPE_STRING, 'office_number' => DATA_TYPE_STRING, 'fax_number' => DATA_TYPE_STRING, 'mobile_number' => DATA_TYPE_STRING, 'home_number' => DATA_TYPE_STRING, 'timezone' => DATA_TYPE_FLOAT, 'created_on' => DATA_TYPE_DATETIME, 'created_by_id' => DATA_TYPE_INTEGER, 'updated_on' => DATA_TYPE_DATETIME, 'last_login' => DATA_TYPE_DATETIME, 'last_visit' => DATA_TYPE_DATETIME, 'last_activity' => DATA_TYPE_DATETIME, 'is_admin' => DATA_TYPE_BOOLEAN, 'auto_assign' => DATA_TYPE_BOOLEAN, 'can_create_projects'=> DATA_TYPE_BOOLEAN);

Next is the addition of a field, and its accessors in the BaseUser (singular) class. This file is located in: /application/models/users/base/BaseUser.class.php, on line 498. This change adds methods that we can use to access the values from that database column we created earlier.

/**
    * Return value of 'can_create_projects' field
    *
    * @access public
    * @param void
    * @return boolean
    */
    function getCanCreateProjects() {
      return $this->getColumnValue('can_create_projects');
    } // getCanCreateProjects()
   
    /**
    * Set value of 'can_create_projects' field
    *
    * @access public  
    * @param boolean $value
    * @return boolean
    */
    function setCanCreateProjects($value) {
      return $this->setColumnValue('can_create_projects', $value);
    } // setCanCreateProjects()

Next, we have the basic boolean method to check whether they can do it. Insert this next function into /application/models/users/base/BaseUser.class.php, line 182. This includes a qualifier to ensure that the user isn't just ANY user, but one of the main users.

/**
* If the user has been specified to be able to add projects, in spite of not
* being an owner/admin. User must be a member of the ownercompany to do it.
*
* @param void
* @return boolean
*/
function canCreateProjects() {
return ($this->can_create_projects && $this->isMemberOfOwnerCompany());
} // canCreateProjects

Now, we need to add some code to the Account Controller class. This change is located in /application/controllers/AccountController.class.php, on line 69. This $user_data array stores the results of the changes we've created so far.

$user_data = array(
          'username'      => $user->getUsername(),
          'email'         => $user->getEmail(),
          'display_name'  => $user->getDisplayName(),
          'title'         => $user->getTitle(),
          'office_number' => $user->getOfficeNumber(),
          'fax_number'    => $user->getFaxNumber(),
          'mobile_number' => $user->getMobileNumber(),
          'home_number'   => $user->getHomeNumber(),
          'timezone'      => $user->getTimezone(),
          'is_admin'      => $user->getIsAdmin(),
          'auto_assign'   => $user->getAutoAssign(),
          'company_id'    => $user->getCompanyId(),
  'can_create_projects' => $user->getCanCreateProjects()
        ); // array

This next change modifies the project class so that we "turn on" a regular user's ability to create projects, if they have that property enabled. This change is at /application/models/projects/Project.class.php, line 1002. This method is used by several of the other changes to come.

/**
    * Check if user can add project
    *
    * @access public
    * @param void
    * @return null
    */
function canAdd(User $user) {
  return $user->isAccountOwner()
      || $user->isAdministrator(owner_company())
      || $user->canCreateProjects();
} // canAdd

Users can now "create" projects, but cannot yet edit their properties or do anything else. If you stop now, when a user creates a project, they'll be greeted with an unfriendly error message. So the first change to make is to allow regular users to edit projects they've created ("own"). The second change allows them to delete those projects. Both of these methods exist already, but are modified to also check to see if the user is the owner of the project. This change is in /application/models/projects/Project.class.php, line 1013

/**
    * Returns true if user can update speicific project
    *
    * @access public
    * @param User $user
    * @return boolean
    */
function canEdit(User $user) {
    return $user->isAccountOwner()
    || $user->isAdministrator(owner_company())
    || ($this->getCreatedBy() == $user->getId());
} // canEdit

    /**
    * Returns true if user can delete specific project
    *
    * @access public
    * @param User $user
    * @return boolean
    */
function canDelete(User $user) {
    return $user->isAccountOwner()
    || $user->isAdministrator(owner_company())
    || ($this->getCreatedBy() == $user->getId());
} // canDelete

At this point, users can add/delete/edit their projects. The only thing left to do in the backend is to allow them to add other people to their projects. This change is in /application/models/projects/Project.class.php, line 1046. It works basically the same as the previous changes.

/**
  * Returns true if user can access permissions page and can update permissions
  *
  * @access public
  * @param User $user
  * @return boolean
*/
function canChangePermissions(User $user) {
   return $user->isAccountOwner()
     || $user->isAdministrator(owner_company())
     || ($this->getCreatedBy() == $user->getId());
} // canChangePermissions

The next two changes are merely to keep things consistent. It's unlikely they would ever be encountered, but since we're updating everything else... This change is also in /application/models/projects/Project.class.php, line 1057.

/**
    * Check if specific user can remove company from project
    *
    * @access public
    * @param User $user
    * @param Company $remove_company Remove this company
    * @return boolean
    */
function canRemoveCompanyFromProject(User $user, Company $remove_company) {
  if ($remove_company->isOwner()) {
    return false;
  }
  return $user->isAccountOwner()
    || $user->isAdministrator()
    || ($this->getCreatedBy() == $user->getId());
} // canRemoveCompanyFromProject

/**
  * Check if this user can remove other user from project
  *
  * @access public
  * @param User $user
  * @param User $remove_user User that need to be removed
  * @return boolean
*/
function canRemoveUserFromProject(User $user, User $remove_user) {
  if ($remove_user->isAccountOwner()) {
     return false;
  }
  return $user->isAccountOwner()
    || $user->isAdministrator()
    || ($this->getCreatedBy() == $user->getId());
} // canRemoveUserFromProject

Now we're going to make a couple changes to the UI to make our changes actionable through the interface. The first one is to allow project owners the ability to delete their projects. Normally, an admin would have to go to the "administration" panel, but we don't want to give users access to that, so instead we'll just add that to the project overview page (with a javascript failsafe). These changes are in /application/views/project/overview_sidebar.php, on line 57.

<?php if (active_project()->canDelete(logged_user()) { ?>
<li><a href="<?php echo active_project()->getDeleteUrl(); ?>" onclick="return confirm('Are you sure you want to delete?');">Delete Project</a></li>
<?php } // if ?>

This change fixes a redirect -- the existing redirect points to the admin panel, which obviously isn't going to work for us. Instead, we'll comment that out and add in a line that redirects to the project overview. It makes slightly more sense in this context anyways... This change is in /application/controllers/ProjectController.class.php, line 434

$this->redirectTo('dashboard');

Now, we need to add some checkboxes to allow these options to be enabled / disabled through the interface. This first one enables the option during the user creation process. This change is in /application/views/user/add_user.php, line 89

<div>
<?php echo label_tag("Can create new projects?:", null, true); ?>
<?php echo yes_no_widget('user[can_create_projects]', 'userFormCanCreate', array_var($user_data, 'can_create_projects'), lang('yes'), lang('no')); ?>
  </div>

Another similar change, this time for the "edit profile" area. This change is in /application/views/account/edit_profile.php, line 68

<div>
<?php echo label_tag("Can Create Projects", null, true); ?>
<?php echo yes_no_widget('user[can_create_projects]', 'userFormCanCreate', array_var($user_data, 'can_create_projects'), lang('yes'), lang('no')); ?>
</div>

And this last change is purely cosmetic -- in the master "users" list, it will now display the canCreateProjects property in addition to other properties. This change is in /application/views/administration/list_users.php, line 13.

<div class="userCanCreate"><span style="font-weight:bolder; color:rgb(51,51,51); font-size:90%;">Can Create Projects:</span> <?php echo $user->canCreateProjects() ? lang('yes') : lang('no'); ?></div>

That's it! Apologies if the line numbers weren't precisely the same as yours -- the code should at least be nearby. It *does* work though, which is awesome. :) If someone wants to turn this into some kind of automated patch or something, feel free.

I'm looking for this feature too but whoaw, looks like there a lot of changes to be made, and all has to be redone on next update...

If we could make another role, like "Project Manager", he could have autorisations to create projects without having all the admistration stuff. That would be nice.

yup

Yeah it is a lot of changes -- but it does what it's supposed to and doesn't require any significant changes to the User model. (a couple conditionals and a property, that's it) it also integrates well with the existing code.

Sorry that I don't have any more automated way of incorporating these changes. Perhaps if the powers that be find this modification satisfactory they'll incorporate it in future releases? :) In the meantime though, you'll have to do it manually :/

Here are the problems I'm having (I went through your steps a couple of times to make sure I didn't make any errors):
1. Member is unable to create projects as the UI doesn't provide an option to do this (UI and DB have the flag set correctly for can_create_new_projects)
2. Clicking on an existing project (admin or member) gives a blank screen

Otherwise I seem pretty close.

Thanks for taking the time to share your mods with the community. I'd love to see this make it's way into the base code.

ProjectPier 0.8.0.3
RHEL 5.2
Apache 2.2.3
MySQL 5.0.45
PHP 5.2

Thanks for posting Jay -- I think I found the problem.

Try changing the BaseUser.class.php (~line 182) so that it says:

$this->getCanCreateProjects() && ...

instead of

$this->can_create_projects && ...

I'm not sure why I wrote it that way, but that would explain why the option shows up correctly during user creation / management, and not anywhere else.

I'll correct it in my OP. Let me know if that fixes it!

Cheers

Thanks for the documentation on how to implement this great feature. I followed the steps and was successful in adding projects from regular users, but I hit a little bit of a snag on the right side of the overview page for any project. I keep seeing the following message:

Notice: Object of class User could not be converted to int in /nw-dealer-d02.0/www/projects/projectpier-0.8.0.3/application/models/projects/Project.class.php on line 1023

Any ideas on why this would happen and hopefully how to fix it?

Thanks

Check this method:

function canEdit(User $user) {
return $user->isAccountOwner()
|| $user->isAdministrator(owner_company())
|| ($this->getCreatedBy() == $user->getId());
} // canEdit

Is the syntax correct in your version? It sounds like you may have forgotten to reference the method getId(), since it's comparing an integer (getCreatedBy) to a User object ($user).

Check that first...

I checked the canEdit method and everything looks ok.

function canEdit(User $user) {
return $user->isAccountOwner()
|| $user->isAdministrator(owner_company())
|| ($this->getCreatedBy() == $user->getId());
} // canEdit

Can you think of anything else that would cause this error?

In this tutorial, all instances of
-----
($this->getCreatedBy() == $user->getId());
-----
should be changed to:
-----
($this->getCreatedById() == $user->getId());
-----
$this->getCreatedBy() returns username where getCreatedById() returns userid

that would be great because it is an essential feature for us.

nils

Hi, love the feature and just implemented it. The tutorial is very well done. I had one small problem for the changes in /application/views/project/overview_sidebar.php, on line 57.--there is a missing close parenthesis in the opening "if" statement. There should be 3 instead of the 2.

Original:

<?php if (active_project()->canDelete(logged_user()) { ?>
<li><a href="<?php echo active_project()->getDeleteUrl(); ?>" onclick="return confirm('Are you sure you want to delete?');">Delete Project</a></li>
<?php } // if ?>

Corrected:

<?php if (active_project()->canDelete(logged_user())) { ?>
<li><a href="<?php echo active_project()->getDeleteUrl(); ?>" onclick="return confirm('Are you sure you want to delete?');">Delete Project</a></li>
<?php } // if ?>

thanks, great modification

Ok so there is an option for me to activate "can create projects" in a user's profile, but when I click "Yes" and go back to the Members summary it will still say, "Can Create Projects: No." So for some reason it is not quite registering.

Also, it's a little unclear in the tutorial whether or not the new code is replacing existing code or just adding to it. Looks like for the most part it is adding to it (unfortunately I don't know php well), but wondering if I missed something.

Another little issue is that when I created the new column in my database, it would not let me sql in both TINYINT(1) and ALLOW_NULLS(0) so I did just tinyint. Seems like nulls is working right though.

Thank you for this great article which will help me learning to customise or extend PP.
By the way, phpfreak, or someone else currently active with PP development , is this feature due for a future version of PP ? I'm using 0.8.0.3 stable release, and I have tested the alpha2 0.8.6 and it doesn't seem to be integrated ?
Great to see that this project seems to revive...

The functionality to have regular users create projects will be in. Not as it is in this patch as the whole permissions solution has been changed. But you will find checkbox 'can manage projects' in 0.8.6 ;-)

The version after 0.8.6 will have 'projectadministrators' who will be handling project permissions and project users.