community.egroupware.org: Community wiki

  
Community wiki
The Day 8th
the day before | Up | the day after


Following our goals from the Day 7th, today we would to continue by working with the category and action's setting.


Like the custom fields, we need to add category link on the !Sidebox menu. Simply we add the below codes into the $file = array( at class.test_hooks.inc.php

---
'Global Categories'  => egw::link('/index.php',array( // initialize the category in the menu 
                'menuaction' => 'admin.admin_categories.index',
		'appname'    => $appname,
		'global_cats'=> True)
                 ),
---


(Figure 8.0)

So far we managed to get the global categories link at the SideBox menu (See Figure 8.1) on our test application which makes us able to create categories for the application (see Figure 8.2).

(Figure 8.1)


(Figure 8.2)

Right! We don't see our category filter on the NextMatch widget yet. First we need to go to etemplate again and change the Category filter on top left corner of the NextMatch widget by adding cat_id as name and Category as label (see Figure 8.3). And also, enable the cat_id parameter by changing its value to False in the NextMatch $content[nm] initialization on the index method like this:

---
'no_cat'  => False, // I  disable the cat-selectbox 
---





(Figure 8.3)

Now if we look at the our NextMatch widget, the category filter is appeared on the top left corner of NextMatch widget (See Figure 8.4).

(Figure 8.4)

The next step is to display the selected category on the NextMatch widget. In order to approach to this goal, we need to set the $query['col_filter']['cat_id'] in the get_rows method , based on if we do have cat_id, All categories, or no categories, like this:

class.test_ui.inc.php method get_rows
---
// category filter: cat_id or ''=All cats or 0=No cat 
if ($query['cat_id'])
{
         $cats = $GLOBALS['egw']->categories->return_all_children((int)$query['cat_id']);
       	 $query['col_filter']['cat_id'] = count($cats) > 1 ? $cats : $query['cat_id'];
}
elseif ((string)$query['cat_id'] == '0')	// no category
{
       	 $query['col_filter']['cat_id'] = null;
}
else	// all cats --> no filter
{
          unset($query['col_filter']['cat_id'])
}
---


The $GLOBALS['egw']->categories->return_all_children((int)$query['cat_id']) returns an array including all categories children, which in our case we do not have any children categories under neither private nor business, therefore, it does return the array with single entity as cat_id.

At the next step we need to add categories on our action menu. Like the other action menus that we have added in the Day 6th, we have to add the new action menu on the get_actions() method,and we can call it cat.

---
'cat' => nextmatch_widget::category_action( 
				'test',++$group,'Change category','cat_'
 ),
---

Since we would change the category of the record through the action menu, we need to get list of all categories on the menu, which we could handle it by nextmatch_widget::category_action(APP Name, Group, Caption, Prefix_) method.

Okay! now lets go to define some actions for the Change category menu. You are thinking quite right! We need to define a new case in the actions() method, and call it cat. Just before that, if we look at the what we get as a category action is like cat_{cat_id} since we define a prefix. Therefore, before going to our switch in the actions() method, simply need to separate the action name and id, because it needs to be recognised by our case clause, look at the line in below:

---
list($content['nm']['action'], $settings) = explode('_', $content['nm']['action'], 2);
---

Here is the case clause:

---
//Category Change  
case "cat": 
         $entry = $this->read($content['nm']['selected'][0]);
         $entry['cat_id'] = $settings;
         $this->save($entry);
          break;
---

If we run the test application now, we will get a nice gategory menu on our actions menu like this (see Figure 8.5):

(Figure 8.5)

It would be the time for our application to use Access Control List (ACL) and be able to act according to user's permissions. Maybe it is good to have scenario to define how complicated would our access control be with our application.

ACL Scenario:
Let say, we want to be able to set permissions for user(s) in order to can/can not do READ,EDIT and DELETE on a record. The user as owner of a record have full access, but should get limited access to records as non-owner user.

Let's start the first step by adding Grant Access menu to our !Sidebox menu.

As we know already, in order to add links in our SideBox menu, we need to define it on hooks. So, let's add this code on class.test_hooks.inc.php:

class.test_hooks.inc.php

---
if ($GLOBALS['egw_info']['user']['apps']['preferences'] && $location != 'admin')
{
	$file = array(
         	'Grant Access'    => egw::link('/index.php','menuaction=preferences.uiaclprefs.index&acl_app='.$appname),
	);

	if ($location == 'preferences')
	{
		display_section($appname,$file);
	}
	else
	{
		display_sidebox($appname,lang('Preferences'),$file);
	}
}
---

Run the application and will get the Grant Access menu on SideBox menu like(See Figure 8.6), and if we click on it, we will redirect to test -> preferences -> ACL setting page for the current user (see Figure 8.7), where we can set permissions on group(s) and/or user(s).

(Figure 8.6)


(Figure 8.7)

After setting the Grant access on our hooks and being able to set permissions, the next step would be to check the permissions of user in the code for all actions that we have created so far.

First of all let define our new database field, through the DB-tools, on egw_test as record owner tracker, we can call it test_owner and since we are saving only the id number of the user, we can define its type as int with length=11 and NOT NULL= True.

We can set the owner with the current user id while we create new record in the edit method.

---
$this->data = array (
                    'test_owner' => $GLOBALS['egw_info']['user']['account_id'],
                );
---

In order to check permissions, we can define a method in our test_bo class to do that. Let's call it get_perms. We need also to get the grants for current user.

class.test_bo.inc.php
---
// Get grants for the current user of the test application 
$this->grants   = $GLOBALS['egw']->acl->get_grants('test');
---

get_grants($app='',$enum_group_acls=true,$user=null) method from acl class, can handle to get grants. If we do not set the user, then it considers the user as current user.

class.test_bo.inc.php

---
/**
 * checks if the user has enough rights for a certain operation
 *
 * Rights are given via owner grants or role based acl
 *
 * @param int $required EGW_ACL_READ, EGW_ACL_WRITE, EGW_ACL_ADD, EGW_ACL_DELETE
 * @param array|int $data=null test_id to use, default the record in the $this->data
 * @param int $user=null for which user to check, default current user
 * @return boolean true if the rights are ok, null if not found, false if no rights
 */
 function get_perms ($required,$data=null,$user=null)
{
	if (is_null($data) || (int)$data == $this->data['test_id'])
	{
               $data =& $this->data;
	}
	if (!is_array($data))
	{
		$save_data = $this->data;
		$data = $this->read($data,true);
		$this->data = $save_data;
		if (!$data) return null; 	// entry not found
	}
	if (!$user) $user = $this->user;
	if ($user == $this->user)
	{
		$grants = $this->grants;
	}
	else
	{
		$grants = $GLOBALS['egw']->acl->get_grants('test',true,$user);
	}
	$ret = $data && !!($grants[$data['test_owner']] & $required);
	return $ret;
}

---

Our get_perms method checks the called permission to the $required and if the user has the right then it returns True otherwise, it will return either null in case of entry not found, or false if no rights.

Okay! Now we need to check the rights when we do read, save, and delete by adding some clauses on our re-implemented read(), save() and delete() methods in the bo class.

class.test_bo.inc.php

---
/** 
 * read a test entry
 *
 * @param int $test_id
 * @param boolean $ignore_acl=false should the acl be checked
 * @return array|boolean array with test record entry, null if test record not found or false if no rights
 */
function read($test_id,$ignore_acl=false)
{
	if (!(int)$test_id || (int)$test_id != $this->data['test_id'] && !parent::read($test_id))
	{
		return null;	// entry not found
	}
	if (!$ignore_acl && !($ret = $this->get_perms(EGW_ACL_READ)))
	{
		return false;	// no read rights
	}
	return $this->data;
}
    
/**
 * saves a test record entry
 *
 * reimplemented to notify the link-class
 * @param array $keys if given $keys are copied to data before saveing => allows a save as
 * @param boolean $ignore_acl=false should the acl be checked, returns true if no edit-rigts
 * @return int 0 on success and errno != 0 else
 */
function save($keys=null,$ignore_acl=false)
{
	if ($keys) $this->data_merge($keys);
	if (!$ignore_acl && $this->data['test_id'] && !$this->get_perms(EGW_ACL_EDIT))
	{
		return true;
	}
        if (!($err = parent::save()))
	{
		// notify the link-class about the update, as other apps may be subscribt to it
		egw_link::notify_update('test',$this->data['test_id'],$this->data);
	}
	return $err;
}

/** 
 * deletes a test record entry identified by $keys or the loaded one, reimplemented to notify the link class (unlink)
 * @param array $keys if given array with col => value pairs to characterise the rows to delete
 * @param boolean $ignore_acl=false should the acl be checked, returns false if no delete-rigts
 * @return int affected rows, should be 1 if ok, 0 if an error
 */
function delete($keys=null,$ignore_acl=false)
{
	if (!is_array($keys) && (int) $keys)
	{
		$keys = array('test_id' => (int) $keys);
	}
	$test_id = is_null($keys) ? $this->data['test_id'] : $keys['test_id'];
	if (!$this->get_perms(EGW_ACL_DELETE,$test_id))
	{
		return false;
	}
        if ($ret = parent::delete($keys) && $test_id)
        {
               // delete all links to test entry $test_id
               egw_link::unlink(0,'test',$test_id);
	}
	return $ret;
}

---

As we would show the owner name on our popup window, we can get the owner name by common::grab_owner_name($content['test_owner']) method, and add it in our edit method in test_ui.

class.test_ui.inc.php

---
 $edit_grants[$content['test_owner']] = common::grab_owner_name($content['test_owner']);
 $sel_options['test_owner']  = $edit_grants;
---

Now if you click on the Grant access link on the SideBox menu, and for example, set READ permission for the demo user, and if we log in as demo user and run the test application, we should be able to see all the records but not be able to see edit or delete them, which we do see them all(See figure 8.8).

(Figure 8.8)

As you may have noticed, we still get the edit, Change Category, and Delete menus on our action menus, which we should disable them while we have no rights.

Firstly, add another parameter, called disableClass to edit and delete array in the get_actions() method.

get_actions() Method in the test_ui class

---

function get_actions()
{
     $actions = array(
  		'open' => array( // setting 'open' array's parameters 
			'caption'  => 'open',
			'default'  => true,
			'allowOnMultiple' => false,
			'group' => 1,
			'url' => 'menuaction=test.test_ui.edit&test_id=$id',
			'popup' => egw_link::get_registry('test', 'edit_popup'),
			),
		'edit' => array(
			'caption' => 'edit',
			'allowOnMultiple' => false, //  it does not allow to multiple selected popup
			'url' => 'menuaction=test.test_ui.edit&test_id=$id',
			'popup' => egw_link::get_registry('test', 'edit_popup'),
			'group'  => 1,
                        'disableClass' => 'rowNoEdit',
			),
                'cat' => nextmatch_widget::category_action( 
			'test',++$group,'Change category','cat_'
                        ),
		'delete' => array(	
			'caption' => 'delete',
			'group'  => 1,
                        'disableClass' => 'rowNoDelete',
          		),
	);
        

        // enable additonal edit check for following actions, if they are generally available
	foreach(array('cat') as $action)
	{
		if ($actions[$action]['enabled'])
		{
			$actions[$action]['enabled'] = 'javaScript:nm_not_disableClass';	// required!
			$actions[$action]['disableClass'] = 'rowNoEdit';
		}
	}
        
	return $actions;
}
---

And, secondly, we have to check the permissions when we read the rows of the NextMatch widget. We can add it like this on get_rows method in test_ui class.

---
// Set the rights for the actions on action menu
$readonlys = array();
foreach($rows as &$row)
{
        if (!$this->get_perms(EGW_ACL_EDIT,$row))
        {
		$readonlys["edit[$row[test_id]]"] = true;
		$row['class'] .= ' rowNoEdit ';
	}
	if (!$this->get_perms(EGW_ACL_DELETE,$row))
	{
		$readonlys["delete[$row[test_id]]"] = true;
		$row['class'] .= ' rowNoDelete ';
	}
}
---

Therefore, for each rows, we check the permission by our get_perms method then set the CSS class as rowNoDelete or rowNoEdit. And now, if we log in as demo user, we should see our action menus disabled on the records which demo has no right to edit (See Figure 8.9).

(Figure 8.9)

We can also, do the same for the buttons on popup menu, means when the user has no right to edit or delete the record, the buttons need to be disabled, which in etemplate, when we set disable a button, hides it from the form.

---

// Set the buttons acording to the rights [hn-cc-D8-ACL-st009] 
$readonlys = array(
        'test_owner'       => !$this->get_perms(EGW_ACL_EDIT), 
        'button[delete]'   => !$this->data['test_id'] || !$this->get_perms(EGW_ACL_DELETE),
        'button[save]'     => !$this->get_perms(EGW_ACL_EDIT),
	'button[apply]'    => !$this->get_perms(EGW_ACL_EDIT),
        );
---


The outcome of Day 8 as ZiP File



the day before | Up | the day after
You are here