Sanity checks

Scripts that require sanity checks

  • Add column to table

Scripts not requiring additional sanity checks

  • “im_menu__new” checks if menu item already exists
  • “im_component_plugin__new” checks if plugin already exists

Create privileges

-- New Privilege to allow accounting guys to change hours
select acs_privilege__create_privilege('add_hours_all','Edit Hours All','Edit Hours All');
select acs_privilege__add_child('admin', 'add_hours_all');

select im_priv_create('add_hours_all', 'Accounting');
select im_priv_create('add_hours_all', 'P/O Admins');
select im_priv_create('add_hours_all', 'Senior Managers');

Sample Script ‘Add Column’

create or replace function inline_0 ()
returns integer as '
declare
        v_count         integer;
begin
        select count(*) into v_count from information_schema.columns where
              table_name = ''acs_object_types''
              and column_name = ''icon_path'';

user_tab_columns
        where lower(table_name) = ''acs_object_types'' and lower(column_name) = ''icon_path'';
        IF v_count > 0 THEN return 1; END IF;

        alter table acs_object_types add column icon_path character varying(100);
        RETURN 0;

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

Check existence of column:

        select count(*) into v_count from information_schema.columns where
              table_name = ''acs_object_types''
              and column_name = ''icon_path'';

Check existence of table:

select count(*) into v_count from pg_tables where tablename = ''im_capacity_planning'';

Check for existing constraints

        select count(*) into v_count from pg_constraint
        where lower(conname) = ''im_hours_project_fk'';

Check existence of index:

        select count(*) into v_count
        from pg_indexes
        where tablename = ''im_tickets'' and indexname = ''im_ticket_type_id_idx'';

Create categories

SELECT im_category_new (16006, 'Rejected', 'Intranet Absence Status');

Create menu item

SELECT im_new_menu(
        'intranet-timesheet2',
        'timesheet2_absences_bankholiday',
        'New Bank Holiday',
        '/intranet-timesheet2/absences/new?absence_type_id=5004',
        50,
        'timesheet2_absences',
        null
);

Create menu permissions

SELECT im_new_menu_perms('timesheet2_absences_bankholiday', 'Employees');

Debugging

RAISE NOTICE 'workflow_case__notify_assignee: Subject=%, Body=%', v_subject, v_body;

Using

pg_dump -U projop -E UTF8 -d projop > projop.dump

instead of the usual

pg_dump --no-owner --clean --disable-dollar-quoting --format=p --file projop.dump

I was able to to restore the db.

http://stackoverflow.com/questions/1973/what-is-the-best-way-to-avoid-sql-injection-attacks

Golden Rules:

3: Custom-Written Value Sanitation. Avoid writing your own sanitation routines as much as possible except when it’s absolutely the only option remaining (which is very unlikely in any modern language). Input sanitation is a hard problem, and the costs of getting it wrong are huge. It’s best to leave that job to someone else. When you are forced to rely on this method use white-listing rather than black-listing to sanitize input.

2: Framework / Library Based Value Sanitation. Leave the sanitation routines to the domain experts. Sanitation routines in 1st party frameworks and libraries are typically created by the same folks that wrote the DB or the SQL API. They have a much higher chance of knowing, and properly handling, all of the edge cases than you do. An example of this technique would be using php’s mysql_escape_string() function to sanitize values that will be inserted into strings that serve as dynamic SQL statements.

1: Parameter Binding. Instead of constructing a SQL statement as a raw string which includes user data as in-place literal values, create a SQL statement with tokens where the user data would be. Then bind the user supplied data to the appropriate parameters. The key here is that this binds the provided data to a specific type and a specific use and eliminates any opportunity to change the logic of the SQL statement. This can be used in conjunction with library based input sanitation as well.

http://www.tcl.tk/community/tcl2004/Tcl2003papers/cleverly.htm

Luckily for the Tcl programmer who craves laziness, OpenACS provides a higher-level database API on top of ns_db that hides all the mundane house-keeping chores. Additionaly, bind-variable emulation is available which frees the programmer from worrying about always escaping apostrophes in SQL statements. Bind variables are prefaced with a colon.

 set name "Jim O'Connor"
            set salary 40000

            db_dml new_hire {
                insert into employees (name, salary, dept)
                values (:name, :salary, 'Accounting')
            }

http://openacs.com/forums/message-view?message_id=250625

Posted by Dave Bauer on 01/19/05 04:57 PM

In Postgresql the db_* commands extract the “bind” variables and SQL quote them automatically. In the PG driver itself the [] charactaers are escaped as well for safety.

This means that the effective security of PostgreSQL is the same as Oracle.

This is done at the database api layer.

In addition ad_page_contract has filters and validation features to check input. ad_form and the form builder also have validation features for input.

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%';

Permissions & Privileges

Create a new privilege:

select acs_privilege__create_privilege('add_tickets','Add Tickets','');
select acs_privilege__add_child('admin', 'add_tickets_for_customers');
select im_priv_create('add_tickets', 'P/O Admins');
select im_priv_create('add_tickets', 'Senior Managers');
select im_priv_create('add_tickets', 'Project Managers');
select im_priv_create('add_tickets', 'Employees');
select im_priv_create('add_tickets', 'Customers');

Check if user has privilege

[im_permission $user_id view_costs]

Remove priviliges

delete from acs_permissions where privilege = 'add_view_internal_rates';
delete from acs_privilege_hierarchy where child_privilege = 'add_view_internal_rates';
delete from acs_privileges where privilege = 'add_view_internal_rates';

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_timesheet_task', 'effort_driven_type_id', 'Fixed Task Type', 'gantt_fixed_task_type', 'integer', 'f', 0, 'f', 'im_timesheet_tasks'
);

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 im_dynfield_attribute_new (
                'im_timesheet_task', 'effort_driven_type_id', 'Fixed Task Type', 'gantt_fixed_task_type', 'integer', 'f', 0, 'f', 'im_timesheet_tasks'
        );

        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 {
   ...
} {
    payed_p:array,optional
}

Add hidden vars to form

  1. [export_form_vars company_id return_url]

Create list from form var array:

set payed_p_list [array names payed_p]
 
set hours_list [array names hours]
foreach hour_rec $hours_list {
    ns_log NOTICE "hour_rec $hour_rec /  value: $hours($hour_rec)"
}

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

Moved to:
http://www.project-open.org/documentation/date_and_time_operations

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


Avoid absolute file paths when using ‘ns_returnredirect’

ns_returnredirect "upload-files.tcl?inq_id=$security_token"
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

Define Callback:

Callbacks require a callback 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
} -

Please note the hyphen at the end of the code!

Callback implementation:

Add the function to your [PACKAGE_NAME]-procs.tcl:

ad_proc -public -callback im_dynfield_attribute_after_update -impl intranet-core  {
    {-object_type:required}
    {-attribute_name:required}
} {
    << EXPLAIN >>
} {
    << FUNCTION BODY >>
}

Callback Call

Place a call to the callback anywhere in your code

callback im_dynfield_attribute_after_update -object_type "im_project" -attribute_name "christmas_card"

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'
      )

Notifications:

/intranet-helpdesk/www/new.tcl

    notification::new \
        -type_id [notification::type::get_type_id -short_name ticket_notif] \
        -object_id $ticket_id \
        -response_id “” \
        -notif_subject $ticket_name \
        -notif_text $message