Sample

User (user_id:28358) with profile “Sales” (group_id:473) can’t see Employees:

Principal routine to check permissions

create function acs_permission__permission_p(int4,int4,varchar) returns bool as '
         declare
             permission_p__object_id           alias for $1;
             permission_p__party_id            alias for $2;
             permission_p__privilege           alias for $3;
             exists_p                          boolean;
         begin
           return exists (select 1
                          from
					acs_permissions p,
					party_approved_member_map m,
                            	acs_object_context_index c,
					acs_privilege_descendant_map h
                         where
					p.object_id = c.ancestor_id
                           	and h.descendant = permission_p__privilege
                           	and c.object_id = permission_p__object_id
                           	and m.member_id = permission_p__party_id
                           	and p.privilege = h.privilege
                           	and p.grantee_id = m.party_id);
        end;' language 'plpgsql';

Check pre-condition:

a) Check existence of privilige “View users”
b) Check User Matrix: Sale should be allowed to see Employees

Digging in’

a) Check permission:

select im_object_permission_p(463, 28358, ‘view_users’);
returns ‘f’

b) Check if member is (approved) member of “Sales”

select
	*
from
	group_approved_member_map
where
	member_id = 28358 and
	group_id = 473;

 group_id | member_id | rel_id | container_id |    rel_type
----------+-----------+--------+--------------+----------------
      473 |     28358 |  35900 |          473 | membership_rel
(1 row)

c) Check for corresponding entry in “party_approved_member_map”


select
		m.*
from
		party_approved_member_map m
where
		m.member_id = 28358;

 party_id | member_id |  tag
----------+-----------+-------
      473 |     28358 | 35900
       -2 |     28358 | 35900
      438 |     28358 | 35900
    28358 |     28358 |     0
       -2 |     28358 | 28359
      438 |     28358 | 28359
       -1 |     28358 | 28359
      463 |     28358 | 28361
       -2 |     28358 | 28361
      438 |     28358 | 28361
       -1 |     28358 | 28361
       -1 |     28358 | 35900
(12 rows)

d) Check acs_privilege_descendant_map

select
		m.*
from 

		acs_permissions p,
		party_approved_member_map m
where
		m.member_id = 28358 and
		m.oid = 463;	

tbc.

Debugging Stored Procedures

create or replace function workflow_case__add_task_assignment(int4,int4,bool) returns int4 as '
       declare
         add_task_assignment__task_id                alias for $1;
         add_task_assignment__party_id               alias for $2;
         add_task_assignment__permanent_p	      alias for $3;
         v_count                                    integer;
         v_workflow_key                             wf_workflows.workflow_key%TYPE;
         v_context_key                              wf_contexts.context_key%TYPE;
         v_case_id                                  wf_cases.case_id%TYPE;
         v_role_key				     wf_roles.role_key%TYPE;
         v_transition_key                           wf_transitions.transition_key%TYPE;
         v_notification_callback     			wf_context_transition_info.notification_callback%TYPE;
         v_notification_custom_arg   wf_context_transition_info.notification_custom_arg%TYPE;
         callback_rec                record;
         v_assigned_user             record;
       begin
               -- get some needed information

	       RAISE NOTICE ''KHD: workflow_case__add_task_assignment: enter'';

               select ta.case_id, ta.workflow_key, ta.transition_key, tr.role_key, c.context_key
               into   v_case_id, v_workflow_key, v_transition_key, v_role_key, v_context_key
               from   wf_tasks ta, wf_transitions tr, wf_cases c
               where  ta.task_id = add_task_assignment__task_id
                 and  tr.workflow_key = ta.workflow_key
                 and  tr.transition_key = ta.transition_key
                 and  c.case_id = ta.case_id;

               -- make the same assignment as a manual assignment

               IF add_task_assignment__permanent_p = ''t'' and v_role_key is not null THEN
       	    /* We do this up-front, because
       	     * even though the user already had a task assignment,
       	     * he might not have a case assignment.
       	     */
                   perform workflow_case__add_manual_assignment (
                       v_case_id,
                       v_role_key,
                       add_task_assignment__party_id
                   );
               end if;

               -- check that we do not hit the unique constraint

               select count(*) into v_count
               from   wf_task_assignments
               where  task_id = add_task_assignment__task_id
               and    party_id = add_task_assignment__party_id;

               if v_count > 0 then
                   return null;
               end if;

               -- get callback information

               select notification_callback,
       		   notification_custom_arg into callback_rec
       	    from   wf_context_transition_info
       	    where  context_key = v_context_key
       	    and    workflow_key = v_workflow_key
       	    and    transition_key = v_transition_key;

		RAISE NOTICE ''KHD: workflow_case__add_task_assignment -> notification_callback is %: '', callback_rec;

               if FOUND then
                   v_notification_callback := callback_rec.notification_callback;
                   v_notification_custom_arg := callback_rec.notification_custom_arg;
               else
                   v_notification_callback := null;
                   v_notification_custom_arg := null;
               end if;

               -- notify any new assignees

	       RAISE NOTICE ''KHD: workflow_case__add_task_assignment-> add_task_assignment__task_id is %: '', add_task_assignment__task_id;
	       RAISE NOTICE ''KHD: workflow_case__add_task_assignment-> add_task_assignment__party_id is %: '', add_task_assignment__party_id;

               for v_assigned_user in
                   select distinct u.user_id
                   from   users u
                   where  u.user_id not in (
       	            select distinct u2.user_id
       	            from   wf_task_assignments tasgn2,
       	                   party_approved_member_map m2,
       	                   users u2
       	            where  tasgn2.task_id = add_task_assignment__task_id
       	            and    m2.party_id = tasgn2.party_id
       	            and    u2.user_id = m2.member_id)
                   and exists (
                       select 1
                       from   party_approved_member_map m
                       where  m.member_id = u.user_id
                       and    m.party_id = add_task_assignment__party_id
                   )
               LOOP
			RAISE NOTICE ''KHD: workflow_case__add_task_assignment-> add_task_assignment__task_id: %: '', add_task_assignment__task_id;
			RAISE NOTICE ''KHD: workflow_case__add_task_assignment-> v_assigned_user.user_id: %: '', v_assigned_user.user_id;
			RAISE NOTICE ''KHD: workflow_case__add_task_assignment-> v_notification_callback: %: '', v_notification_callback;
			RAISE NOTICE ''KHD: workflow_case__add_task_assignment-> v_notification_custom_arg: %: '', v_notification_custom_arg;

                   PERFORM workflow_case__notify_assignee (
                       add_task_assignment__task_id,
                       v_assigned_user.user_id,
                       v_notification_callback,
                       v_notification_custom_arg
                   );
		RAISE NOTICE ''KHD: workflow_case__add_task_assignment-> CALLED workflow_case__notify_assignee: %: '', v_assigned_user.user_id;

               end loop;

               -- do the insert

               insert into wf_task_assignments (
                   task_id,
                   party_id
               ) values (
                   add_task_assignment__task_id,
                   add_task_assignment__party_id
               );

               return 0;
       end;' language 'plpgsql';

See also:

Control flow / Conditions

Check for existing variable

  • [exists_and_not_null project_id]
  • [info exists project_id]

Localization

Find message in DB:

select * from lang_messages where message like '%Type%'

OpenACS string localization:

[lang::message::lookup "" intranet-core.Execution_Project "Create an 'Execution Project'"]

OpenACS string localization (short version):

[_ intranet-core.Name]
#intranet-core.Filter_Projects#

Localize date to user-locale:

[lc_time_fmt '2009-12-15' %x]

Add message:

SELECT im_lang_add_message('en_US','intranet-audit','Earned_Value','Earned Value');

Localize numbers / beautifying them for output:

/intranet-invoices/www/view.tcl

set rounding_precision 2
set locale [lang::user::locale]
set amount_pretty [lc_numeric [im_numeric_add_trailing_zeros [expr $amount+0] $rounding_precision] "" $locale]
set val_hours_output [format "%0.2f" $val_hours]

Localize labels etc.

[lang::message::lookup "" intranet-core.Execution_Project "Create an Execution Project"]

Localize categories
Pls. see below

Categories

Create new Category

SELECT im_category_new ('71', 'Potential', 'Intranet Project Status');

Create new Category Hierarchy

SELECT im_category_hierarchy_new (58, 56);

Return id of categories with name CATEGORY_NAME

[im_sub_categories CATEGORY_NAME]

Returns name of category

[im_category_from_id CATEGORY_ID]

Transitive closure – Returns categories including sub-categories

im_sub_categories [ -include_disabled_p include_disabled_p ] category_list

Note: category_list is a list of category id’s (integer)

Category drop-down in ad_form – Default
Currently return an empty entry for ‘All’

    ...
    -form {
        {absence_type_id:text(im_category_tree),optional {label "[_ intranet-timesheet2.Absence_Type]"} {value $absence_type_id} {custom {category_type "Intranet Absence Type" translate_p 1} } }
     .. 
    }

Category drop-down in ad_form – Custom, using VIEW

 
    set absences_types [im_memoize_list select_absences_types "select absence_type_id, absence_type from im_absence_types order by lower(ABSENCE_TYPE)"]
    set absences_types [linsert $absences_types 0 "All"]
    set absences_types [linsert $absences_types 0 -1]
    set absence_type_list [list]
    foreach { value text } $absences_types {
        regsub -all " " $text "_" category_key
        set text [lang::message::lookup "" intranet-core.$category_key $text]
        lappend absence_type_list [list $text $value]
    }
    ...
    -form {
        {absence_type_id:text(select),optional {label "[_ intranet-timesheet2.Absence_Type]"} {options $absence_type_list }}     .. 
    }

Plain SQL

SELECT
     im_categories.category_id,
     im_categories.category AS absence_type
FROM
     im_categories
WHERE
     im_categories.category_type::text = 'Intranet Absence Type'::text;

Permissions for pages

Is User logged in?:

set current_user_id [ad_maybe_redirect_for_registration]

Using existing permission procedures:

im_timesheet_task_permissions $current_user_id $project_id view read write admin

sets variables $read $write $admin to true/false

Checking privilege:

if {![im_permission $current_user_id "view_projects_all"]} {
    ad_return_complaint 1 "[_ intranet-core.lt_You_dont_have_suffici_2]"
}

Search for privileges:

SELECT * FROM acs_privileges WHERE pretty_name LIKE '%assign%';

Checking for menu entry:

...

Packages

Parameters

[parameter::get -package_id [apm_package_id_from_key intranet-mail-import] -parameter "ScanMails" -default 60]

Does PAckage exist

set im_survey_installed_p [llength [info proc [db_string get_view_id "select package_id from apm_packages where package_key = 'intranet-simple-survey'" -default 0]]]

OR

set survey_exists_p [llength [info commands im_package_survsimp_id]]

Dynfields

Create DynField

SELECT im_dynfield_attribute_new ('im_ticket', 'ticket_prio_id', 'Priority', 'ticket_priority', 'integer', 'f');

Set DynField permissions

PERFORM acs_permission__grant_permission(v_dynfield_id, (select group_id from groups where group_name=''Employees''), ''read'');
create or replace function inline_0 ()
returns integer as '
declare
        v_dynfield_id           integer;
begin

        SELECT INTO v_dynfield_id im_dynfield_attribute_new (''im_employees'', ''rwh_days_per_year'', ''RWH days per year'', ''numeric'', ''integer'', ''f'');

        PERFORM acs_permission__grant_permission(v_dynfield_id, (select group_id from groups where group_name=''Employees''), ''read'');
        PERFORM acs_permission__grant_permission(v_dynfield_id, (select group_id from groups where group_name=''Employees''), ''write'');

        return 0;

end;' language 'plpgsql';
select inline_0 ();
drop function inline_0 ();

Views

Create View

insert into im_views (view_id, view_name, visible_for, view_type_id)
values (80, 'office_list', 'view_offices', 1400);

Create View Columns

insert into im_view_columns (column_id, view_id, group_id, column_name, column_render_tcl,
extra_select, extra_where, sort_order, visible_for) values (2221,22,NULL,'Quote',
'$quote_date','','',18,'');

User Management

Is user member of a project?

[im_biz_object_member_p $user_id $project_id]

Is user employee?

[im_user_is_employee_p $user_id]

Is user employee?

[im_profile::member_p -profile_id [im_employee_group_id] -user_id $user_id]

Is user customer?

[im_profile::member_p -profile_id [im_customer_group_id] -user_id $user_id]

Is user freelancer?

[im_profile::member_p -profile_id [im_freelance_group_id] -user_id $user_id]

Is user customer?

[im_profile::member_p -profile_id [im_customer_group_id] -user_id $user_id]

Is user Project Manager?

[im_biz_object_admin_p $user_id $project_id]

Is user admin?

[im_is_user_site_wide_or_intranet_admin $user_id]

Current user

po34klaus=# select current_user;
 current_user
--------------
 po34klaus
(1 row)

Database

db_foreach

    set column_sql "
        select
                *
        from
                im_view_columns
        where
                view_id=:view_id
                and group_id is null
        order by
                sort_order
    "
    db_foreach column_list_sql $column_sql {
        if {"" == $visible_for || [eval $visible_for]} {
            lappend column_headers "$column_name"
            lappend column_vars "$column_render_tcl"
        } 
        ....
    }

db_1row
Use: Set variables for a query returning exactly one row.

Sample:

         db_1row sender_get_info_1 "
 
                select
                        pe.first_names as accounting_first_name,
                        pe.last_name as accounting_last_name
                from
                        persons
                where
                        person_id = $accounting_contact_id
 
                       "

db_0or1row
Like db_1row but returns an error if more than one row is returned.

db_string
Use: Getting a single string value from the db

Sample:

set view_id [db_string get_view_id "select view_id from im_views where view_name=:view_name" -default 0]

db_dml – performs a dml statement

db_dml delete "
                delete from im_object_freelance_skill_map
                where
                        object_id = :object_id
                        and skill_type_id = :skill_type_id
                        and skill_id in ([join $skill_id ","])
"

Create a sequence

create sequence im_inquiries_files_seq start 1;

Get id from sequence

set inquiry_files_id [db_string nextval "select nextval('im_inquiries_files_seq');"]

Check if table exists

<ul>
  <li>db_table_exists TABLE_NAME </li>
  <li>[im_table_exists im_freelance_rfqs]</li>
</ul>

DB Transactions

    db_transaction {
        db_dml test "nonsense"
    } on_error {
        ad_return_error "Error in blah/foo/bar" "The error was: $errmsg"
    }

String operations

String concatenation

Alternative to concat & append – in case var’s are ambigious:

set var_amount "amount.${user_id}_$project_type_id"
</p>
 
<strong>Check for empty string</strong>
<ul>
	<li>empty_string_p [VAR]</li>
</ul>

Sample:

if { ![empty_string_p $where_clause] } {
    set where_clause " and $where_clause"
}

Compare strings in tcl

if { -1 != [string compare $list_order_by "min(p.price)"] } {
                ad_return_complaint 1 "here"
 
                set sorted_table_rows [qsort $table_rows [lambda {s} { lindex $s 2 }]]
        } else {
 
                set sorted_table_rows $table_rows
        }

Formating for amounts, etc.

set amount_pretty [im_numeric_add_trailing_zeros [expr $amount+0] 2]

Error handling

Handling expr error

         if {"" != $visible_tcl} {
            set visible 0
            set errmsg ""
            if [catch {
                set visible [expr $visible_tcl]
            } errmsg] {
                ad_return_complaint 1 "$visible_tcl\n$errmsg"
            }
            if {!$visible} { continue }
        }

Handling db error

    if {[catch { db_dml target_languages " select * from im_projects" } errmsg ]} {
        append errors "..."
    }

Page contract

Optional parameters

ad_proc im_costs_base_component { user_id {company_id ""} {project_id ""} } {
    Returns a HTML table containing a list of costs for a particular
    company or project.
} {
...
    }

Passing URL parameters

  1. set primary_contact_link "<a href=primary-contact?[export_url_vars company_id limit_to_users_in_group_id]>Add primary contact</a>\n"

Forms

Arrays – Create form

  1. <input type='checkbox' name='payed_p.$cost_id'>

Arrays – Handle form

ad_proc im_costs_base_component { user_id {company_id ""} {project_id ""} } {
   ...
} {
    payed_p:array,optional
}

Add hidden vars:

  1. [export_form_vars company_id return_url]

Create list of form vars:

set payed_p_list [array names payed_p]

Create list of variables transmitted:

set payed_p_list [array names payed_p]

Sanity checks

if {![im_column_exists im_hours internal_note]} {
    ad_return_complaint 1 "Internal error in intranet-timesheet2:<br>
        The field im_hours.internal_note is missing.<br>
        Please notify your system administrator to upgrade
        your system to the latest version.<br>
    "
    ad_script_abort
}

Date & Time operations

Convert format using tcl

clock format [clock scan "20120128"] -format {%Y-%m-%d}

Date today

[clock format [clock seconds] -format {%Y-%m-%d}]

Number of days of a month

set number_days_month [db_string get_number_days_month "SELECT date_part('day','$first_day_of_month'::date + '1 month'::interval - '1 day'::interval)" -default 0]

Last day of a month:

set last_day_of_month [db_string get_number_days_month "select to_date( '$cap_year' || '-' || '$cap_month' || '-' || '$number_days_month','yyyy-mm-dd')+1 from dual;" -default 0]

Get current day month:

set current_date_ansi [db_string julian_date_select "select to_char( to_date(:julian_date,'J'), 'YYYY-MM-DD') from dual"]

Localize Date (user locale):

set form_start_date [lc_time_fmt [parameter::get_from_package_key -package_key "intranet-cost" -parameter DefaultStartDate -default "2010-01-01"] "%x" locale]

Other:

set end_date_ansi [append "" [string range $current_date_ansi 0 7] $number_days_month]
set end_date_ansi [db_string get_previous_month "SELECT '$start_date_ansi'::date+'1 month'::interval-'1 day'::interval" -default 0]
set start_date_julian [db_string get_previous_month "select to_char('$start_date_ansi'::date,'J')" -default 0]

Get date from 'Julian Date'

set date [db_string julian_date_select "select to_char( to_date(:julian_date,'J'), 'YYYY-MM-DD') from dual"]

Get date from DB with specific format

    set hours_start_date [db_string get_new_start_at "
        select  to_char(max(day), 'YYYYMMDD')
        from    im_hours
        where   project_id = :project_id
    " -default ""]

Math operations


using round in sql

        /packages/intranet-reporting-finance/www/finance-expenses.tcl
        select
                ...
                round((c.paid_amount * im_exchange_rate(c.effective_date::date, c.currency, 'EUR')) :: numeric, 2) as paid_amount_converted,
        from ...

Menus


Add menu:

create or replace function inline_1 ()
returns integer as '
declare
        v_menu                  integer;
        v_parent_menu         integer;
        v_employees             integer;
begin
        select group_id into v_employees from groups where group_name = ''Employees'';
 
        select menu_id into v_parent_menu
        from im_menus where label = ''projects_admin'';
 
        v_menu := im_menu__new (
                null,                                   -- p_menu_id
                ''im_menu'',                            -- object_type
                now(),                                  -- creation_date
                null,                                   -- creation_user
                null,                                   -- creation_ip
                null,                                   -- context_id
                ''intranet-cust-kw'',   -- package_name
                ''project_closure_wf'', -- label
                ''Close Project'',      -- name
                ''/intranet-cust-kw/workflow-close-project?project_id=$project_id'',   -- url
                -10,                                    -- sort_order
                v_parent_menu,                          -- parent_menu_id
                null                                    -- p_visible_tcl
        );
 
        PERFORM acs_permission__grant_permission(v_menu, v_employees, ''read'');
        return 0;
end;' language 'plpgsql';
select inline_1 ();
drop function inline_1();

Get menu items for side menu:

[im_menu_ul_list -no_uls 1 "projects_admin" {}]

Setting Subnavbar:

# Setup the subnavbar
set bind_vars [ns_set create]
ns_set put $bind_vars project_id $project_id
set project_menu_id [db_string parent_menu "select menu_id from im_menus where label='project'" -default 0]
 
set sub_navbar [im_sub_navbar \
    -components \
    -base_url "/intranet/projects/view?project_id=$project_id" \
    $project_menu_id \
    $bind_vars "" "pagedesriptionbar" "project_timesheet_task"]

Setting menu label active:

set menu_label "reporting-finance-expenses"

Context Help

a) Add to [file].tcl

set show_context_help_p 1

b) Add to [file].adp

<property name="show_context_help_p">@show_context_help_p;noquote@</property>

AOL Server API

ns_returnredirect "/intranet-customer-portal/upload-files.tcl?inq_id=$security_token"
(preferable: ad_returnredirect)
ns_returnfile 200 "application/zip" $filepath
ns_return 200 text/html 1
set result [ad_parse_template -params $params "/packages/intranet-core/www/related-objects-component"]
[ns_conn url] #returns for example: /intranet/projects
[im_url_with_query] # returns for example: /intranet/projects/view?project%5fid=34352

Portlet/Plugin

Create Plugin

SELECT  im_component_plugin__new (
        null,                           -- plugin_id
        'acs_object',                -- object_type
        now(),                        -- creation_date
        null,                           -- creation_user
        null,                           -- creation_ip
        null,                           -- context_id
        'Home Random Portrait', -- plugin_name
        'intranet-core',             -- package_name
        'right',                        -- location
        '/intranet/index',           -- page_url
        null,                           -- view_name
        5,                              -- sort_order
        'im_random_employee_component'  -- component_tcl
);

Set permissions for plugin

        select  plugin_id
        into    v_plugin_id
        from    im_component_plugins pl
        where   plugin_name = ''Requests for Quote'';
 
        PERFORM im_grant_permission(v_plugin_id, v_cust_id, ''read'');

Workflow

Actions

im_workflow__set_object_status_id
im_workflow__assign_to_owner

Templates / Control structures in ADP pages

VAR replacements

<%= [eval ad_context_bar $context_bar] %>

Control Structures & Loops

  <multiple name="user_stuff">
     <if @user_stuff.age@ le 21>
         <tr bgcolor="red">
     </if>
     <else>
         <tr bgcolor="green">
     </else>
         <td>@user_stuff.user_name@</td>
         <td>@user_stuff.age@</td>
         <td>@user_stuff.shoesize@</td>
     </tr>
  </multiple>

Callbacks

Include callback:

callback project_create $project_id

Add to call back function *-procs.tcl:

ad_proc -public -callback << CALLBACK NAME>> -impl << PACKAGE NAME >>  {
    { << PARAMETER AS DEFINED IN INIT >> }
} {
    << EXPLAIN >>
} {
	<< FUNCTION BODY >>
}

Requires call back definition in *-init.tcl:
Example: intranet-core-init.tcl

ad_proc -public -callback im_dynfield_attribute_after_update {
    {-object_type:required}
    {-attribute_name:required}
} {
    Callback to be executed after an attribute has been changed
} -

Include JS/CSS ( ]po[ V4.0 )

 
template::head::add_css -href "/intranet-sencha/css/ext-all.css" -media "screen" -order 1
template::head::add_javascript -src "/intranet-sencha/js/ext-all-debug-w-comments.js" -order 1

Parameters

UPDATE
    apm_parameter_values
SET
    attr_value=0
WHERE
    parameter_id in (
        select
            parameter_id
        from
            apm_parameters
        where
            parameter_name = 'SuppressHttpPort' and
            package_key = 'acs-tcl'
      )

Before a user can create or view a Financial Document, a permission check is performed. As a sample we will have a closer look what is checks are performed when a user wants to create a new quote.

Permission checks creation of a new quote: /intranet-invoices/new?cost_type_id=3702

1) Depending on the required permission type (read/write) a list of cost_type_ids is generated:

set create_cost_types [im_cost_type_write_permissions $user_id]

“im_cost_type_write_permissions” performs the following checks:

    set can_write [expr [im_permission $user_id add_costs] || [im_permission $user_id add_invoices]]
    if {!$can_write} { return [list] }

    set result [db_list writable_cost_centers "
        select distinct
                ct.cost_type_id
        from
                im_cost_centers cc,
                im_cost_types ct,
                acs_permissions p,
                party_approved_member_map m,
                acs_object_context_index c,
                acs_privilege_descendant_map h
        where
                cc.cost_center_id = c.object_id
                and ct.write_privilege = h.descendant
                and p.object_id = c.ancestor_id
                and m.member_id = :user_id
                and p.privilege = h.privilege
                and p.grantee_id = m.party_id
    "]
    return $result

Let’s have a closer look at this sql statement and the tables and views involved.

Table: im_cost_centers

… all Cost Centers …

im_cost_centers.png

View: im_cost_types

im_cost_types contains all privileges related to the “Financial Documents”

im_cost_types.png

Table: acs_permissions

Our well known acs_permissions, mapping object_id (1st column), grantee_id (2nd column) and privilege (3rd column).
More information here.

acs_permissions.png

Records in acs_permissions are added/removed setting values in /intranet/admin/profiles/
In case a new privilege is set, a record will be added to this table, if a privilege is removed, the respective record will be removed.

Table: party_approved_member_map

If user is a member of more than one profile, his accumulates the permissions of all profile he’s a member of. User/Profile relationship is managed in table “parties”.
Table “party_approved_member_map” maps a party to the fully expanded list of parties represented by that party including the party itself. So if a party is an individual this view will have exactly one mapping hat is from that party to itself. If a view is a group containing three individuals,this view will have four rows for that party, one for each member,and one from the party to itself. As the table name indicates, only approved members are considered. More information on Open ACS parties here

party_approved_memeber_map.png

Table: acs_privilege_descendant_map

.. pretty obvious too, manages the relationship between related parent/child privileges:

| oid | privilege | descendant |
acs_privilege_descendant_map.png

Table: acs_object_context_index

This table manages the inheritance of privileges. See OpenACS Permissions Tediously Explained – by Vadim Nasardinov for further information.

| object_id | ancestor_id | n_generations |

acs_object_context_index.png

Based from what we have learned about the tables involved we now investigate further the “where” clause:

...
        where
                cc.cost_center_id = c.object_id
                and ct.write_privilege = h.descendant
                and p.object_id = c.ancestor_id
                and m.member_id = :user_id
                and p.privilege = h.privilege
                and p.grantee_id = m.party_id

a) If a permission is granted for the parent “Cost Center”, it should also be granted for its childs:

im_cost_centers.cost_center_id = acs_object_context_index.object_id" 

b) The same is true for cost types, a privilege granted on a higher level should be inherited to its childs:

im_cost_types.write_privilege = acs_privilege_descendant_map.descendant

c) … and for parties (user):

acs_permissions.object_id = acs_object_context_index.ancestor_id

d) Get (approved ) parties related to user to look up:

party_approved_member_map.member_id = :user_id

e) Include all sub privileges:

acs_permissions.privilege = acs_privilege_descendant_map.privilege

f) Limit acs_permissions to approved members

acs_permissions.grantee_id = party_approved_member_map.party_id

You might want to take some time to digest that …

2) In a second step we check, if the Financial Document to be shown/created is member of this list:

    if {[lsearch -exact $create_cost_types $cost_type_id] == -1} {
        ad_return_complaint "Insufficient Privileges" "
  • You don't have sufficient privileges to create a [db_string t "select im_category_from_id(:cost_type_id)"]." return }
  • What are ]po[ privileges?

    Privileges allow a user to perform a certain operation in the system.
    Find more information about privileges in the po-dev-openacs-developersguide.061201a.doc or in OpenACS Permissions Tediously Explained.

    Sample:Setting privileges on a component

    
    -- -----------------------------------------------------
    -- Add privileges for freelance_skills
    --
    
    create or replace function inline_0 ()
    returns integer as '
    declare
            v_count                 integer;
    begin
            select  count(*) into v_count
            from    acs_privileges
            where   privilege = ''view_freelance_skills'';
            IF v_count > 0 THEN return 0; END IF;
    
            PERFORM acs_privilege__create_privilege(''view_freelance_skills'',''View Freelance Skills'',''View Freelance Skills'');
            PERFORM acs_privilege__add_child(''admin'', ''view_freelance_skills'');
    
            PERFORM im_priv_create(''view_freelance_skills'',''Accounting'');
            PERFORM im_priv_create(''view_freelance_skills'',''P/O Admins'');
            PERFORM im_priv_create(''view_freelance_skills'',''Project Managers'');
            PERFORM im_priv_create(''view_freelance_skills'',''Senior Managers'');
            PERFORM im_priv_create(''view_freelance_skills'',''Freelance Managers'');
            PERFORM im_priv_create(''view_freelance_skills'',''Employees'');
    
            return 0;
    end;' language 'plpgsql';
    select inline_0 ();
    drop function inline_0 ();
    

    Sample: Setting/revoking permissions as done in /intranet/admin/components/

    /packages/intranet-core/www/admin/toggle.tcl
    
    ...
    switch $action {
        add_viewable {
            im_exec_dml grant_viewable "im_grant_permission($object_id,$horiz_group_id,'view')"
        }
        add_readable {
            im_exec_dml grant_readable "im_grant_permission($object_id,$horiz_group_id,'read')"
        }
        add_writable {
            im_exec_dml grant_writable "im_grant_permission($object_id,$horiz_group_id,'write')"
        }
        add_administratable {
            im_exec_dml grant_administratable "im_grant_permission($object_id,$horiz_group_id,'admin')"
        }
        remove_viewable {
            im_exec_dml revoke_viewable "im_revoke_permission($object_id,$horiz_group_id,'view')"
        }
        remove_readable {
            im_exec_dml revoke_readable "im_revoke_permission($object_id,$horiz_group_id,'read')"
        }
        remove_writable {
            im_exec_dml revoke_writable "im_revoke_permission($object_id,$horiz_group_id,'write')"
        }
        remove_administratable {
            im_exec_dml revoke_administratable "im_revoke_permission($object_id,$horiz_group_id,'admin')"
        }
        default {
            ad_return_complaint 1 "Unknown action: '$action'"
            return
        }
    }