Get ancestor:
select tree_ancestor_key('1000000000000000111010101100110000000000000000000000000100001001', 1);

Get level:
select tree_level('1000000000000000111010101100110000000000000000000000000100001001');

Get direct parent:

select
parent.project_id
from
im_projects parent,
im_projects child
where child.project_id = :project_id and
tree_ancestor_key(child.tree_sortkey, tree_level(child.tree_sortkey)-1) = parent.tree_sortkey

Links:
Using tree_sortkey for hierarchical queries in Postgres
OpenACS Doku: Hierarchical data

xowiki pages are managed with the OpenACS Content repository.

XOWIKI pages:

For each page there’s an entry in cr_items

projop=# \d cr_items
                                   Table "public.cr_items"
      Column       |          Type          |                   Modifiers
-------------------+------------------------+------------------------------------------------
 item_id           | integer                | not null
 parent_id         | integer                | not null
 name              | character varying(400) | not null
 locale            | character varying(4)   |
 live_revision     | integer                |
 latest_revision   | integer                |
 publish_status    | character varying(40)  |
 content_type      | character varying(100) |
 storage_type      | character varying(10)  | not null default 'text'::character varying
 storage_area_key  | character varying(100) | not null default 'CR_FILES'::character varying
 tree_sortkey      | bit varying            | not null
 max_child_sortkey | bit varying            |

Page content

Page content is stored in table cr_revisions

projop=# \d cr_revisions
                             Table "public.cr_revisions"
     Column     |           Type           |                Modifiers
----------------+--------------------------+-----------------------------------------
 revision_id    | integer                  | not null
 item_id        | integer                  | not null
 title          | character varying(1000)  |
 description    | text                     |
 publish_date   | timestamp with time zone |
 mime_type      | character varying(200)   | default 'text/plain'::character varying
 nls_language   | character varying(50)    |
 lob            | integer                  |
 content        | text                     |
 content_length | integer                  |

Finding content that can’t be found using ‘search’ such as links etc.

select
		r.item_id,
		r.title,
		i.name
from
		cr_revisions r,
		cr_items i
where
		r.content like '%en:install_main%' and
		r.revision_id = i.live_revision;

Object Model

Pls. see: http://openacs.org/xotcl/show-object?show_source=0&object=%3A%3Axowiki%3A%3APage&show_methods=1

Content

  • General Information
  • Tables and views used
  • Time sheet Portlets
  • Sample Queries

General information

When time sheet form (/intranet-timesheet2/hours/new) is send to the server,

  • … ]po[ deletes affected time sheet entries for affected day and user and creates new ones
  • ... ]po[ updates im_projects::reported_hours_cache

Please note that

  • ... Paramaters have in impact on values shown in time sheet plugins. In case there's mismath regarding hours reported, check parameter SyncHoursInterval and SyncHoursP
  • If hours don't sum up correctly on the time sheet component in a sub project, there might be hours logged on the main project that are not considered.
fullquery name: update_timesheet_task

        update im_projects
        set reported_hours_cache = (
                select  sum(h.hours)
                from    im_hours h
                where   h.project_id = :project_id
        )
        where project_id = :project_id

fullquery name: delete_timesheet_costs

      DECLARE
         row RECORD;
      BEGIN
         for row in
                select  cost_id
                from    im_costs
                where   cost_type_id = [im_cost_type_timesheet]
                        and project_id = :project_id
                        and cause_object_id = :user_id_from_search
                        and effective_date = to_date(:julian_date, ‘J’)
         loop
                PERFORM im_cost__delete(row.cost_id);
         end loop;
         return 0;
      END;

Tables used

  • im_timesheet_tasks
  • im_hours
  • im_costs

im_timesheet_tasks

  • Contains all timesheet tasks
projop=# \d im_timesheet_tasks
       Table "public.im_timesheet_tasks"
     Column     |       Type       | Modifiers
----------------+------------------+-----------
 task_id        | integer          | not null
 material_id    | integer          | not null
 uom_id         | integer          | not null
 planned_units  | double precision |
 billable_units | double precision |
 cost_center_id | integer          |
 invoice_id     | integer          |
 priority       | integer          |
 sort_order     | integer          |
 bt_bug_id      | integer          |
Indexes:
    "im_timesheet_tasks_pk" PRIMARY KEY, btree (task_id)
Foreign-key constraints:
    "im_times_tasks_bt_bug_fk" FOREIGN KEY (bt_bug_id) REFERENCES bt_bugs(bug_id)
    "im_timesheet_task_fk" FOREIGN KEY (task_id) REFERENCES im_projects(project_id)
    "im_timesheet_tasks_cost_center_fk" FOREIGN KEY (cost_center_id) REFERENCES im_cost_centers(cost_center_id)
    "im_timesheet_tasks_invoice_fk" FOREIGN KEY (invoice_id) REFERENCES im_invoices(invoice_id)
    "im_timesheet_tasks_material_fk" FOREIGN KEY (material_id) REFERENCES im_materials(material_id)
    "im_timesheet_tasks_uom_fk" FOREIGN KEY (uom_id) REFERENCES im_categories(category_id)

im_costs

A cost item will be created for each time sheet entry

                    Table "public.im_costs"
         Column         |           Type           | Modifiers
------------------------+--------------------------+-----------
 cost_id                | integer                  | not null
 cost_name              | character varying(400)   | not null
 cost_nr                | character varying(400)   | not null
 project_id             | integer                  |
 customer_id            | integer                  | not null
 cost_center_id         | integer                  |
 provider_id            | integer                  | not null
 investment_id          | integer                  |
 cost_status_id         | integer                  | not null
 cost_type_id           | integer                  | not null
 cause_object_id        | integer                  |
 template_id            | integer                  |
 effective_date         | timestamp with time zone |
 start_block            | timestamp with time zone |
 payment_days           | integer                  |
 amount                 | numeric(12,3)            |
 currency               | character(3)             |
 paid_amount            | numeric(12,3)            |
 paid_currency          | character(3)             |
 vat                    | numeric(12,5)            |
 tax                    | numeric(12,5)            |
 variable_cost_p        | character(1)             |
 needs_redistribution_p | character(1)             |
 parent_id              | integer                  |
 redistributed_p        | character(1)             |
 planning_p             | character(1)             |
 planning_type_id       | integer                  |
 description            | character varying(4000)  |
 note                   | character varying(4000)  |
 last_modified          | timestamp with time zone |
 last_modifying_user    | integer                  |
 last_modifying_ip      | character varying(20)    |
Indexes:
    "im_costs_pk" PRIMARY KEY, btree (cost_id)
    "im_costs_cause_object_idx" btree (cause_object_id)
    "im_costs_start_block_idx" btree (start_block)
Check constraints:
    "im_costs_needs_redist_ck" CHECK (needs_redistribution_p = 't'::bpchar OR needs_redistribution_p = 'f'::bpchar)
    "im_costs_planning_ck" CHECK (planning_p = 't'::bpchar OR planning_p = 'f'::bpchar)
    "im_costs_redist_ck" CHECK (redistributed_p = 't'::bpchar OR redistributed_p = 'f'::bpchar)
    "im_costs_var_ck" CHECK (variable_cost_p = 't'::bpchar OR variable_cost_p = 'f'::bpchar)
Foreign-key constraints:
    "im_cost_template_fk" FOREIGN KEY (template_id) REFERENCES im_categories(category_id)
    "im_costs_cause_fk" FOREIGN KEY (cause_object_id) REFERENCES acs_objects(object_id)
    "im_costs_cost_center_fk" FOREIGN KEY (cost_center_id) REFERENCES im_cost_centers(cost_center_id)
    "im_costs_cost_fk" FOREIGN KEY (cost_id) REFERENCES acs_objects(object_id)
    "im_costs_currency_fk" FOREIGN KEY (currency) REFERENCES currency_codes(iso)
    "im_costs_customer_fk" FOREIGN KEY (customer_id) REFERENCES acs_objects(object_id)
    "im_costs_inv_fk" FOREIGN KEY (investment_id) REFERENCES acs_objects(object_id)
    "im_costs_last_mod_user" FOREIGN KEY (last_modifying_user) REFERENCES users(user_id)
    "im_costs_paid_currency_fk" FOREIGN KEY (paid_currency) REFERENCES currency_codes(iso)
    "im_costs_parent_fk" FOREIGN KEY (parent_id) REFERENCES im_costs(cost_id)
    "im_costs_planning_type_fk" FOREIGN KEY (planning_type_id) REFERENCES im_categories(category_id)
    "im_costs_provider_fk" FOREIGN KEY (provider_id) REFERENCES acs_objects(object_id)
    "im_costs_status_fk" FOREIGN KEY (cost_status_id) REFERENCES im_categories(category_id)
    "im_costs_type_fk" FOREIGN KEY (cost_type_id) REFERENCES im_categories(category_id)
Triggers:
    im_costs_project_cache_del_tr AFTER DELETE ON im_costs FOR EACH ROW EXECUTE PROCEDURE im_cost_project_cache_del_tr()
    im_costs_project_cache_ins_tr AFTER INSERT ON im_costs FOR EACH ROW EXECUTE PROCEDURE im_cost_project_cache_ins_tr()
    im_costs_project_cache_up_tr AFTER UPDATE ON im_costs FOR EACH ROW EXECUTE PROCEDURE im_cost_project_cache_up_tr()

Views used by ]po[ Time Sheet Management

  • im_timesheet_tasks_view

projop=# \d  im_timesheet_tasks_view
            View "public.im_timesheet_tasks_view"
        Column        |           Type           | Modifiers
----------------------+--------------------------+-----------
 task_id              | integer                  |
 material_id          | integer                  |
 uom_id               | integer                  |
 planned_units        | double precision         |
 billable_units       | double precision         |
 cost_center_id       | integer                  |
 invoice_id           | integer                  |
 priority             | integer                  |
 sort_order           | integer                  |
 project_id           | integer                  |
 task_name            | character varying(1000)  |
 task_nr              | character varying(100)   |
 percent_completed    | double precision         |
 task_type_id         | integer                  |
 task_status_id       | integer                  |
 start_date           | timestamp with time zone |
 end_date             | timestamp with time zone |
 reported_hours_cache | double precision         |
 reported_units_cache | double precision         |
View definition:
 SELECT t.task_id, t.material_id, t.uom_id, t.planned_units, t.billable_units, t.cost_center_id, t.invoice_id, t.priority, t.sort_order, p.parent_id AS project_id, p.project_name AS task_name, p.project_nr AS task_nr, p.percent_completed, p.project_type_id AS task_type_id, p.project_status_id AS task_status_id, p.start_date, p.end_date, p.reported_hours_cache, p.reported_hours_cache AS reported_units_cache
   FROM im_projects p, im_timesheet_tasks t
  WHERE t.task_id = p.project_id;

Time sheet Portlets

Timesheet Tasks

Location: /intranet/projects/view?project_id=...

Facts about the Time sheet Component:

  • In order to speed up page creation, hours are taken from im_projects::reported_hours_cache

Samples

http://www.infoworld.com/article/08/12/02/49TC-jitterbit-talend_1.html

compares TALEND with JITTERBIT

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 }
  • # ------------------------------------------------------
    
    # Show extension fields
    
    # ------------------------------------------------------
    
    set dynamic_fields_p 0
    
    if {[db_table_exists im_dynfield_attributes]} {
    
    set dynamic_fields_p 1
    
        set object_type "im_company"
    
        set form_id "company_view"
    
    template::form create $form_id 
    
            -mode "display" 
    
            -display_buttons {}
    
    im_dynfield::append_attributes_to_form 
    
            -object_type $object_type 
    
            -form_id $form_id 
    
            -object_id $company_id 
    
            -form_display_mode "display"
    
    }

    Table “public.im_menus”

    Description:

    Contains all menu items
    Open questions:

                        Table "public.im_menus"
    
         Column     |          Type           |      Modifiers
    
    ----------------+-------------------------+---------------------
    
     menu_id        | integer                 | not null
    
     package_name   | character varying(200)  | not null
    
     label          | character varying(200)  | not null
    
     name           | character varying(200)  | not null
    
     url            | character varying(200)  | not null
    
     sort_order     | integer                 |
    
     parent_menu_id | integer                 |
    
     tree_sortkey   | character varying(100)  |
    
     visible_tcl    | character varying(1000) |
    
     enabled_p      | character(1)            | default 't'::bpchar
    
    Indexes:
    
        "im_menu_id_pk" PRIMARY KEY, btree (menu_id)
    
        "im_menus_label_un" UNIQUE, btree (label)
    
    Check constraints:
    
        "im_menus_neabled_ck" CHECK (enabled_p = 't'::bpchar OR enabled_p = 'f'::bpchar)
    
        "im_menus_enabled_ck" CHECK (enabled_p = 't'::bpchar OR enabled_p = 'f'::bpchar)
    
    Foreign-key constraints:
    
        "im_parent_menu_id_fk" FOREIGN KEY (parent_menu_id) REFERENCES im_menus(menu_id)
    
        "im_menu_id_fk" FOREIGN KEY (menu_id) REFERENCES acs_objects(object_id)

    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
        }
    }
    

    Groups:

    CREATE TABLE groups
    
    (
    
    group_id int4 NOT NULL,
    
    group_name varchar(1000) NOT NULL,
    
    join_policy varchar(30) NOT NULL DEFAULT 'open'::character varying,
    
    CONSTRAINT groups_pk PRIMARY KEY (group_id),
    
    CONSTRAINT groups_group_id_fk FOREIGN KEY (group_id)
    
    REFERENCES parties (party_id) MATCH SIMPLE
    
    ON UPDATE NO ACTION ON DELETE NO ACTION,
    
    CONSTRAINT groups_join_policy_ck CHECK (join_policy::text = 'open'::text OR join_policy::text = 'needs approval'::text OR join_policy::text = 'closed'::text)
    
    )

    PL/SQL API:

    
    

    create function im_grant_permission(int4,int4,varchar) returns int4 as '         DECLAREp_object_id	alias for $1;p_party_id	alias for $2;
    
    p_privilege	alias for $3;
    
    BEGIN
    
    PERFORM acs_permission__grant_permission(p_object_id, p_party_id, p_privilege);
    
    return 0;
    
    END;' language 'plpgsql';
    
        if {[im_permission $user_id "view_projects_all"]} {
            set children_perm_sql "
            (select t.*
             from   im_projects t
             where  $project_restriction
            )
            "
        }