DBA Data[Home] [Help]

PACKAGE BODY: APPS.WF_NOTIFICATION

Source


1 package body WF_NOTIFICATION as
2 /* $Header: wfntfb.pls 120.43.12010000.27 2009/02/26 01:39:33 alepe ship $ */
3 
4 --
5 -- Constants
6 --
7 
8 -- Max_forward - Max number of forwards allowed by routing rules
9 -- before a routing loop is inferred.
10 max_forward number := 10;
11 
12 -- Sequence number for comments for notification actions in the same
13 -- session, caused by Routing Rules
14 g_comments_seq pls_integer := 0;
15 
16   -- logging variable
17   g_plsqlName varchar2(35) := 'wf.plsql.WF_NOTIFICATION.';
18 
19 --
20 -- Private Variables
21 --
22 table_direction varchar2(1) := 'L';
23 table_type varchar2(1) := 'V';
24 table_width  varchar2(8) := '100%';
25 table_border varchar2(2) := '0';
26 table_cellpadding varchar2(2) := '1';
27 table_cellspacing varchar2(2) := '0';
28 table_bgcolor varchar2(7) := 'white';
29 th_bgcolor varchar2(7) := '#cfe0f1';
30 th_fontcolor varchar2(7) := '#336699';
31 th_fontface varchar2(80) := 'Arial, Helvetica, Geneva, sans-serif';
32 th_fontsize varchar2(2) := '2';
33 td_bgcolor varchar2(7) := '#f7f7e7';
34 td_fontcolor varchar2(7) := 'black';
35 td_fontface varchar2(80) := 'Arial, Helvetica, Geneva, sans-serif';
36 td_fontsize varchar2(2) := '2';
37 table_summary  WF_RESOURCES.TEXT%TYPE := wf_core.translate('ACTION_HISTORY');
38 
39 -- Session NLS_DATE_FORMAT
40 -- Use this to fulfill the GSCC requirement
41 s_nls_date_format varchar2(120);  -- session parameter value has length of 120
42 
43 --Data types for custom columns API
44 type text_array_t is varray(10) of VARCHAR2(4000);
45 type numb_array_t is varray(5)  of NUMBER;
46 type date_array_t is varray(5)  of DATE;
47 
48 -- checks if plsqlclob,plsqlblob exists
49 plsql_clob_exists  pls_integer;
50 
51 --
52 -- Private Functions
53 --
54 
55 -- NTF_TABLE
56 --   Generate a "Browser Look and Feel (BLAF)" look a like table.
57 -- ADA compliance is achieved through "scope".
58 --
59 -- IN
60 --   cells - array of table cells
61 --   col   - number of columns
62 --   type  - Two character code. First determines header position.
63 --         - optional second denotes direction for Bi-Di support.
64 --         - V to generate a vertical table
65 --         - H to generate a horizontal table
66 --         - N to generate a mailer notification header table which
67 --             is a form of vertical
68 --         - *L Left to Right (default)
69 --         - *R Right to Left
70 --   rs    - the result html code for the table
71 --
72 -- NOTE
73 --   type - Vertical table is Header always on the first column
74 --        - Horizontal table is Headers always on first row
75 --        - The direction can be omitted to which the default will be
76 --        - Left to Right.
77 --
78 --   cell has the format:
79 --     R40%:content of the cell here
80 --     ^ ^
81 --     | |
82 --     | + -- width specification
83 --     +-- align specification (L-Left, C-Center, R-Right, S-Start E-End)
84 --
85 procedure NTF_Table(cells in tdType,
86                     col   in pls_integer,
87                     type  in varchar2,  -- 'V'ertical or 'H'orizontal
88                     rs    in out nocopy varchar2)
89 is
90   i pls_integer;
91   colon pls_integer;
92   modv pls_integer;
93   alignv   varchar2(1);
94   l_align  varchar2(8);
95   l_width  varchar2(3);
96   l_text   varchar2(4000);
97   l_type   varchar2(1);
98   l_dir    varchar2(1);
99   l_dirAttr varchar2(10);
100 
101 
102   -- Define a local set and initialize with the default
103   l_table_width  varchar2(8);
104   l_table_border varchar2(2);
105   l_table_cellpadding varchar2(2);
106   l_table_cellspacing varchar2(2);
107   l_table_bgcolor varchar2(7);
108   l_th_bgcolor varchar2(7);
109   l_th_fontcolor varchar2(7);
110   l_th_fontface varchar2(80);
111   l_th_fontsize varchar2(2);
112   l_td_bgcolor varchar2(7);
113   l_td_fontcolor varchar2(7);
114   l_td_fontface varchar2(80);
115   l_td_fontsize varchar2(4);
116   l_table_summary  WF_RESOURCES.TEXT%TYPE;
117 
118 begin
119   l_table_width  := table_width;
120   l_table_border := table_border;
121   l_table_cellpadding := table_cellpadding;
122   l_table_cellspacing := table_cellspacing;
123   l_table_bgcolor := table_bgcolor;
124   l_th_bgcolor := th_bgcolor;
125   l_th_fontcolor := th_fontcolor;
126   l_th_fontface := th_fontface;
127   l_th_fontsize := th_fontsize;
128   l_td_bgcolor := td_bgcolor;
129   l_td_fontcolor := td_fontcolor;
130   l_td_fontface := td_fontface;
131   l_td_fontsize := '10pt';
132   l_table_summary := table_summary;
133 
134   if length(type) > 1 then
135      l_type := substrb(type, 1, 1);
136      l_dir := substrb(type,2, 1);
137   else
138      l_type := type;
139      l_dir := 'L';
140   end if;
141 
142   if l_dir = 'L' then
143      l_dirAttr := NULL;
144   else
145      l_dirAttr := 'dir="RTL"';
146   end if;
147 
148   if (l_type = 'N') then
149      -- Notification format. Alter the default colors.
150      l_table_bgcolor := '#FFFFFF';
151      l_th_bgcolor := '#FFFFFF';
152      l_th_fontcolor := '#000000';
153      l_td_bgcolor := '#FFFFFF';
154      l_td_fontcolor := '#000000';
155      l_table_cellpadding := '0';
156      l_table_cellspacing := '0';
157      l_table_width  := '100%';
158   end if;
159 
160   if (cells.COUNT = 0) then
161     rs := null;
162     return;
163   end if;
164 
165    --  << bug 6369346 >> :
166    --  There is no need to increase width of <TD>
167    --  in WF_MAIL.GetHeaderTable from 30% to 50%, just
168    --  increase width of below html table to 100% ,
169    --  irrespective of table Type ('N' -notification, 'H', 'V')
170    rs := '<table width="100%" summary="'||l_table_summary||'" '||l_dirAttr|| '><tr><td>';
171 
172   if (l_type = 'N') then
173      rs := rs||wf_core.newline||'<table width="'||l_table_width||'"'||
174             ' border="'||l_table_border||'"'||
175             ' cellpadding="'||l_table_cellpadding||'"'||
176             ' cellspacing="'||l_table_cellspacing||'"'||
177 	    ' summary="'||l_table_summary||'"'||
178             ' bgcolor="'||l_table_bgcolor||'" '||l_dirAttr||'>';
179 
180 
181 
182   else -- Type ('V' and 'H')
183 
184      rs := rs||wf_core.newline||'<table width="'||l_table_width||'"'||
185                ' class="x1h" cellpadding="'||l_table_cellpadding||'"'||
186                ' cellspacing="'||l_table_cellspacing||'"'||
187                ' summary="'||l_table_summary||'"'||
188                ' border="'||l_table_border||'"'||
189                ' '||l_dirAttr||'>';
190   end if;
191 
192   for i in 1..cells.LAST loop
193     modv := mod(i, col);
194     if (modv = 1) then
195       rs := rs||wf_core.newline||'<tr>';
196     end if;
197 
198     alignv := substrb(cells(i), 1, 1);
199     if (alignv = 'R') then
200       l_align := 'RIGHT';
201     elsif (alignv = 'L') then
202       l_align := 'LEFT';
203     elsif (alignv = 'S') then
204       if (l_dir = 'L') then
205          l_align := 'LEFT';
206       else
207          l_align := 'RIGHT';
208       end if;
209     elsif (alignv = 'E') then
210       if (l_dir = 'L') then
211          l_align := 'RIGHT';
212       else
213          l_align := 'LEFT';
214       end if;
215     else
216       l_align := 'CENTER';
217     end if;
218 
219     colon := instrb(cells(i),':');
220     l_width := substrb(cells(i), 2, colon-2);
221     l_text  := substrb(cells(i), colon+1);   -- what is after the colon
222 
223     if ((l_type = 'V' and modv = 1) or (l_type = 'N' and modv = 1)
224         or  (l_type = 'H' and i <= col)) then
225       -- this is a header
226       if (l_type = 'N') then
227          rs := rs||wf_core.newline||'<td class="x8" ';
228       elsif (l_type = 'H') then
229          rs := rs||wf_core.newline||'<th class="x1r x4j" ';
230          rs := rs||' scope="col"';
231       else -- if (l_type = 'V') then
232          rs := rs||wf_core.newline||'<th class="x1r x4q" ';
233          rs := rs||' scope="row"';
234       end if;
235 
236       if (l_width is not null) then
237         rs := rs||' width="'||l_width||'"';
238       end if;
239       rs := rs||' align="'||l_align||'" valign="baseline" >';
240       if (l_type = 'N') then
241         rs := rs||l_text;
242         rs := rs||'</td><td width="12">&'||'nbsp;</td>';
243       else
244         rs := rs||'<span class="x24">'||l_text||'</span>';
245         rs := rs||'</th>';
246       end if;
247     else
248       -- this is regular data
249       rs := rs||wf_core.newline||'<td';
250       if (l_width is not null) then
251         rs := rs||' width="'||l_width||'"';
252       end if;
253       rs := rs||' align="'||l_align||'" valign="baseline" ';
254 
255       if (l_type = 'N') then
256         rs := rs||' class="x2">'||l_text||'</td>';
257       else
258         rs := rs||' class="x1l x4x">'||l_text||'</td>';
259       end if;
260     end if;
261     if (modv = 0) then
262       rs := rs||wf_core.newline||'</tr>';
263     end if;
264   end loop;
265   rs := rs||wf_core.newline||'</table>'||wf_core.newline||'</td></tr></table>';
266 
267 exception
268   when OTHERS then
269     wf_core.context('Wf_Notification', 'NTF_Table',to_char(col),l_type);
270     raise;
271 end NTF_Table;
272 
273 --
274 -- WF_MSG_ATTR
275 --   Create a table of message attributes
276 -- NOTE
277 --   o Considered using dynamic sql passing in attributes as a comma delimited
278 --     list.  The cost of non-reusable sql may be high.
279 --   o Considered using bind variables with dynamic sql.  Then we must impose
280 --     a hard limit on the number of bind variables.  If a limit exceed we
281 --     need some fall back handling.
282 --   o Parsing the comma delimited list and making individual select is more
283 --     costly.  But the sql will be reusable, it may end up cheaper.
284 --
285 function wf_msg_attr(nid    in number,
286                      attrs  in varchar2,
287                      disptype in varchar2)
288 return varchar2
289 is
290   l_attr      varchar2(30);
291   l_dispname  varchar2(80);
292   l_text      varchar2(4000);
293 
294   l_type      varchar2(8);
295   l_cols      pls_integer;
296   l_table_direction varchar2(1);
297   l_format    varchar2(240);
298   l_textv     varchar2(4000);
299   l_numberv   number;
300   l_datev     date;
301 
302   i           pls_integer;
303   p1          pls_integer;
304   p2          pls_integer;
305   not_empty   boolean := true;
306   role_info_tbl wf_directory.wf_local_roles_tbl_type;
307 
308   l_delim     varchar2(1);
309   cells       tdType;
310   result      varchar2(32000);
311 begin
312 
313   l_delim := ':';
314 
315   l_table_direction := table_direction;
316   if (table_type = 'N') then
317      l_cols := 3;
318   else
319      l_cols := 2;
320   end if;
321 
322   i  := 1;
323   p1 := 1;
324   while not_empty loop
325     p2 := instrb(attrs,',',p1);
326     if (p2 = 0) then
327       p2 := lengthb(attrs)+1;
328       not_empty := false;
329     end if;
330 
331     l_attr := ltrim(substrb(attrs,p1,p2-p1));
332 
333     begin
334       select MA.DISPLAY_NAME,
335              MA.TYPE,
336              MA.FORMAT,
337              NA.TEXT_VALUE,
338              NA.NUMBER_VALUE,
339              NA.DATE_VALUE
340         into l_dispname, l_type, l_format, l_textv, l_numberv, l_datev
341         from WF_MESSAGE_ATTRIBUTES_VL MA,
342              WF_NOTIFICATION_ATTRIBUTES NA,
343              WF_NOTIFICATIONS N
344        where NA.NAME = l_attr
345          and NA.NOTIFICATION_ID = nid
346          and NA.NOTIFICATION_ID = N.NOTIFICATION_ID
347          and N.MESSAGE_TYPE = MA.MESSAGE_TYPE
348          and N.MESSAGE_NAME = MA.MESSAGE_NAME
349          and MA.NAME = NA.NAME;
350     exception
351       when NO_DATA_FOUND then
352         -- skip if this attribute or notification does not exist
353         l_dispname := null;
354         l_type  := 'VARCHAR2';
355         l_format:= null;
356         l_textv := null;
357       when OTHERS then
358         raise;
359     end;
360 
361     if (l_type = 'DATE') then
362       -- <bug 7514495> now as date format we use the first non-null value of:
363       -- l_format, wf_notification_util.G_NLS_DATE_FORMAT (if nid is provided and matches
364       -- wf_notification_util.G_NID), session user's WFDS preference, wf_core.nls_date_format.
365       l_text := wf_notification_util.GetCalendarDate(nid, l_datev, l_format, false);
366     elsif (l_type = 'NUMBER') then
367       if (l_format is null) then
368         l_text := to_char(l_numberv);
369       else
370         l_text := to_char(l_numberv, l_format);
371       end if;
372     elsif (l_type = 'ROLE') then
373       Wf_Directory.GetRoleInfo2(l_textv,role_info_tbl);
374       l_text := role_info_tbl(1).display_name;
375     elsif (l_type = 'LOOKUP') then
376       begin
377         select MEANING
378         into l_text
379         from WF_LOOKUPS
380         where LOOKUP_TYPE = l_format
381         and LOOKUP_CODE = l_textv;
382       exception
383         when no_data_found then
384           -- Use code directly if lookup not found.
385           l_text := l_textv;
386       end;
387     elsif (l_type = 'VARCHAR2') then
388       -- VARCHAR2 is text_value, truncated at format if one provided.
389       if (l_format is null) then
390         l_text := l_textv;
391       else
392         l_text := substrb(l_textv, 1, to_number(l_format));
393       end if;
394     else
395       -- do not do any complicated substitution for URL and FORM
396       -- do nothing for DOCUMENT as it is too costly
397       l_text := l_textv;
398     end if;
399 
400     -- make sure the text does not carry any HTML chars... though NUMBER is safe
401     -- others possibly could carry.
402     if (disptype = wf_notification.doc_html) then
403       l_text := substrb(Wf_Core.SubstituteSpecialChars(l_text), 1, 4000);
404     end if;
405 
406     -- display
407     if (l_dispname is not null) then
408       if (disptype = wf_notification.doc_html) then
409         l_dispname := substrb(Wf_Core.SubstituteSpecialChars(l_dispname), 1, 80);
410         if (table_type = 'N') then
411            cells(i) := 'E:'||l_dispname;
412            i := i+1;
413            cells(i) := 'S12:';
414         else
415            cells(i) := 'E40%:'||l_dispname;
416         end if;
417         i := i+1;
418         cells(i) := 'S:'||l_text;  -- normally align number to the right
419                                    -- but not in vertical table
420         i := i+1;
421       else
422         result := result||wf_core.newline||l_dispname||l_delim||' '||l_text;
423       end if;
424     end if;
425 
426 -- ### implement as generic log in the future
427 --    if (wf_notification.debug) then
428 --      dbms_output.put_line(substrb(l_attr||'/'||l_dispname||': '||l_text,1,250));
429 --    end if;
430 
431     p1 := p2+1;
432   end loop;
433 
434   if (disptype = wf_notification.doc_html) then
435     if (table_type = 'N') then
436        table_width := '100%';
437     else
438        table_width := '70%';
439     end if;
440     NTF_Table(cells=>cells,
441               col=>l_cols,
442               type=>table_type||l_table_direction,
443               rs=>result);
444   end if;
445 
446   return(result);
447 
448 exception
449   when OTHERS then
450     wf_core.context('Wf_Notification','Wf_Msg_Attr',to_char(nid),attrs);
451     raise;
452 end wf_msg_attr;
453 
454 
455 -- Wf_Ntf_History
456 --   Construct Action History table for a given notification from the WF_COMMENTS table
457 --   The table consists of actions like Reassign, More Info Request and Respond and related
458 --   comments. The user can restrict the rows in the table using the following format.
459 --   WF_NOTIFICATION(HISTORY, hide_reassign, hide_requestinfo)
460 --   Example:
461 --     WF_NOTIFICATION(HISTORY, Y, Y) - Hides comments related to Reassign and More Info Reqs
462 --     WF_NOTIFICATION(HISTORY, N, Y) - Hides comments related to More Info Reqs
463 --     WF_NOTIFICATION(HISTORY) - Shows all comments related to the notification
464 --
465 -- InPut
466 --   nid - Notification Id
467 --   disptype - text/plain or text/html
468 --   param - Hide Reassign, Hide Request Info indicators
469 
470 function wf_ntf_history(nid      in number,
471                         disptype in varchar2,
472                         param    in varchar2)
473 return varchar2
474 is
475    l_param            varchar2(100);
476    l_hide_reassign    varchar2(1);
477    l_hide_requestinfo varchar2(1);
478    l_action_history   varchar2(32000);
479    l_pos              pls_integer;
480 begin
481 
482    l_hide_reassign := 'N';
483    l_hide_requestinfo := 'N';
484 
485    begin
486       if (param is not null) then
487          l_pos := instr(param, ',', 1);
488          l_hide_reassign := trim(substr(param, 1, l_pos-1));
489          l_hide_requestinfo := trim(substr(param, l_pos+1, length(param)-l_pos));
490       end if;
491    exception
492       when others then
493          l_hide_reassign := 'N';
494          l_hide_requestinfo := 'N';
495    end;
496 
497    Wf_Notification.GetComments2(p_nid => nid, p_display_type => disptype,
498                                 p_hide_reassign => l_hide_reassign,
499                                 p_hide_requestinfo => l_hide_requestinfo,
500                                 p_action_history => l_action_history);
501    return l_action_history;
502 
503 end wf_ntf_history;
504 
505 /*
506 ** This Procedure is obsolete. From 11.5.10 onwards, Action History table is based on
507 ** WF_COMMENTS table and on the Notification Activities' history. Hence, WF_NTF_HISTORY
508 ** procedure is reimplemented.
509 **
510 --
511 -- Wf_Ntf_History
512 --   Construct a history table for a notification activity.
513 -- NOTE
514 --   Consist of three sections:
515 --   1. Current Notification
516 --   2. Past Notifications in the history table
517 --   3. The owner role as the submitter and begin date for such item
518 --
519 function wf_ntf_history(nid      in number,
520                         disptype in varchar2)
521 return varchar2
522 is
523   -- current notification
524   cursor hist0c(x_item_type varchar2, x_item_key varchar2, x_actid number) is
525   select IAS.NOTIFICATION_ID, IAS.ASSIGNED_USER, A.RESULT_TYPE, IAS.ACTIVITY_RESULT_CODE, nvl(IAS.END_DATE, IAS.BEGIN_DATE) ACT_DATE, IAS.EXECUTION_TIME
526     from WF_ITEM_ACTIVITY_STATUSES IAS,
527          WF_ACTIVITIES A,
528          WF_PROCESS_ACTIVITIES PA,
529          WF_ITEM_TYPES IT,
530          WF_ITEMS I
531    where IAS.ITEM_TYPE = x_item_type
532      and IAS.ITEM_KEY = x_item_key
533      and IAS.PROCESS_ACTIVITY = x_actid
534      and IAS.ITEM_TYPE          = I.ITEM_TYPE
535      and IAS.ITEM_KEY           = I.ITEM_KEY
536      and I.BEGIN_DATE between A.BEGIN_DATE and nvl(A.END_DATE, I.BEGIN_DATE)
537      and I.ITEM_TYPE             = IT.NAME
538      and IAS.PROCESS_ACTIVITY    = PA.INSTANCE_ID
539      and PA.ACTIVITY_NAME        = A.NAME
540      and PA.ACTIVITY_ITEM_TYPE   = A.ITEM_TYPE;
541 
542   -- past notifications
543   cursor histc(x_item_type varchar2, x_item_key varchar2, x_actid number) is
544   select IAS.NOTIFICATION_ID, IAS.ASSIGNED_USER, A.RESULT_TYPE, IAS.ACTIVITY_RESULT_CODE, nvl(IAS.END_DATE, IAS.BEGIN_DATE) ACT_DATE, IAS.EXECUTION_TIME
545     from WF_ITEM_ACTIVITY_STATUSES_H IAS,
546          WF_ACTIVITIES A,
547          WF_PROCESS_ACTIVITIES PA,
548          WF_ITEM_TYPES IT,
549          WF_ITEMS I
550    where IAS.ITEM_TYPE = x_item_type
551      and IAS.ITEM_KEY = x_item_key
552      and IAS.PROCESS_ACTIVITY = x_actid
553      and IAS.ITEM_TYPE          = I.ITEM_TYPE
554      and IAS.ITEM_KEY           = I.ITEM_KEY
555      and I.BEGIN_DATE between A.BEGIN_DATE and nvl(A.END_DATE, I.BEGIN_DATE)
556      and I.ITEM_TYPE             = IT.NAME
557      and IAS.PROCESS_ACTIVITY    = PA.INSTANCE_ID
558      and PA.ACTIVITY_NAME        = A.NAME
559      and PA.ACTIVITY_ITEM_TYPE   = A.ITEM_TYPE
560   order by IAS.BEGIN_DATE desc , IAS.EXECUTION_TIME desc;
561 
562   l_itype varchar2(30);
563   l_ikey  varchar2(240);
564   l_actid number;
565   l_result_type varchar2(30);
566   l_result_code varchar2(30);
567   l_action varchar2(80);
568   l_owner_role  varchar2(320);
569   l_owner       varchar2(320);
570   l_begin_date  date;
571   i pls_integer;
572   j pls_integer;
573   role_info_tbl wf_directory.wf_local_roles_tbl_type;
574 
575   l_table_direction varchar2(1);
576   l_delim     varchar2(1) := ':';
577   cells       tdType;
578   result      varchar2(32000);
579   l_note      varchar2(4000);
580 begin
581 
582   l_table_direction := table_direction;
583 
584   begin
585     select ITEM_TYPE, ITEM_KEY, PROCESS_ACTIVITY
586       into l_itype, l_ikey, l_actid
587       from WF_ITEM_ACTIVITY_STATUSES
588      where notification_id = nid;
589   exception
590     when NO_DATA_FOUND then
591       begin
592         select ITEM_TYPE, ITEM_KEY, PROCESS_ACTIVITY
593           into l_itype, l_ikey, l_actid
594           from WF_ITEM_ACTIVITY_STATUSES_H
595          where notification_id = nid;
596       exception
597         when NO_DATA_FOUND then
598           null;  -- raise a notification not exist message
599       end;
600   end;
601 
602   j := 1;
603   -- title
604   cells(j) := wf_core.translate('NUM');
605   if (disptype = wf_notification.doc_html) then
606     cells(j) := 'S10%:'||cells(j);
607   end if;
608   j := j+1;
609   cells(j) := wf_core.translate('NAME');
610   if (disptype = wf_notification.doc_html) then
611     cells(j) := 'S:'||cells(j);
612   end if;
613   j := j+1;
614   cells(j) := wf_core.translate('ACTION');
615   if (disptype = wf_notification.doc_html) then
616     cells(j) := 'S:'||cells(j);
617   end if;
618   j := j+1;
619   cells(j) := wf_core.translate('ACTION_DATE');
620   if (disptype = wf_notification.doc_html) then
621     cells(j) := 'S:'||cells(j);
622   end if;
623   j := j+1;
624   cells(j) := wf_core.translate('NOTE');
625   if (disptype = wf_notification.doc_html) then
626     cells(j) := 'S:'||cells(j);
627   end if;
628   j := j+1;
629 
630   i := 0;
631   for histr in hist0c(l_itype, l_ikey, l_actid) loop
632     cells(j) := to_char(histr.notification_id);
633     j := j+1;
634     wf_directory.GetRoleInfo2(histr.assigned_user, role_info_tbl);
635     if (disptype = wf_notification.doc_html) then
636       cells(j) := 'S:'||Wf_Notification.SubstituteSpecialChars(role_info_tbl(1).display_name);
637     else
638       cells(j) := role_info_tbl(1).display_name;
639     end if;
640     j := j+1;
641     if (l_result_type is null or l_result_code is null or
642         histr.result_type <> l_result_type or
643         histr.activity_result_code <> l_result_code) then
644       l_result_type := histr.result_type;
645       l_result_code := histr.activity_result_code;
646       l_action := wf_core.activity_result(l_result_type, l_result_code);
647     end if;
648     if (disptype = wf_notification.doc_html) then
649       if (l_action is null) then
650         cells(j) := 'S: ';
651       else
652         cells(j) := 'S:'||l_action;
653       end if;
654     else
655       cells(j) := l_action;
656     end if;
657     j := j+1;
658     if (disptype = wf_notification.doc_html) then
659       cells(j) := 'S:'||to_char(histr.act_date);
660     else
661       cells(j) := to_char(histr.act_date);
662     end if;
663     j := j+1;
664     begin
665       l_note := Wf_Notification.GetAttrText(histr.notification_id,'WF_NOTE',TRUE);
666       if (disptype = wf_notification.doc_html) then
667         l_note := substrb(Wf_Notification.SubstituteSpecialChars(l_note), 1, 4000);
668       end if;
669       cells(j) := l_note;
670     exception
671       when OTHERS then
672         cells(j) := null;
673         wf_core.clear;
674     end;
675     if (disptype = wf_notification.doc_html) then
676       if (cells(j) is null) then
677         cells(j) := 'S: ';
678       else
679         cells(j) := 'S:'||cells(j);
680       end if;
681     end if;
682     j := j+1;
683 
684     i := i+1;
685   end loop;
686 
687   for histr in histc(l_itype, l_ikey, l_actid) loop
688     cells(j) := to_char(histr.notification_id);
689     j := j+1;
690     wf_directory.GetRoleInfo2(histr.assigned_user, role_info_tbl);
691     if (disptype = wf_notification.doc_html) then
692       cells(j) := 'S:'||Wf_Notification.SubstituteSpecialChars(role_info_tbl(1).display_name);
693     else
694       cells(j) := role_info_tbl(1).display_name;
695     end if;
696     j := j+1;
697     if (l_result_type is null or l_result_code is null or
698         histr.result_type <> l_result_type or
699         histr.activity_result_code <> l_result_code) then
700       l_result_type := histr.result_type;
701       l_result_code := histr.activity_result_code;
702       l_action := wf_core.activity_result(l_result_type, l_result_code);
703     end if;
704     if (disptype = wf_notification.doc_html) then
705       if (l_action is null) then
706         cells(j) := 'S: ';
707       else
708         cells(j) := 'S:'||l_action;
709       end if;
710     else
711       cells(j) := l_action;
712     end if;
713     j := j+1;
714     if (disptype = wf_notification.doc_html) then
715       cells(j) := 'S:'||to_char(histr.act_date);
716     else
717       cells(j) := to_char(histr.act_date);
718     end if;
719     j := j+1;
720     begin
721       l_note := Wf_Notification.GetAttrText(histr.notification_id,'WF_NOTE',TRUE);
722       if (disptype = wf_notification.doc_html) then
723         l_note := substrb(Wf_Notification.SubstituteSpecialChars(l_note), 1, 4000);
724       end if;
725       cells(j) := l_note;
726     exception
727       when OTHERS then
728         cells(j) := null;
729         wf_core.clear;
730     end;
731     if (disptype = wf_notification.doc_html) then
732       if (cells(j) is null) then
733         cells(j) := 'S: ';
734       else
735         cells(j) := 'S:'||cells(j);
736       end if;
737     end if;
738     j := j+1;
739 
740     i := i+1;
741   end loop;
742 
743   -- submit row
744   cells(j) := '0';
745   j := j+1;
746   begin
747     select OWNER_ROLE, BEGIN_DATE
748       into l_owner_role, l_begin_date
749       from WF_ITEMS
750      where ITEM_TYPE = l_itype
751        and ITEM_KEY = l_ikey;
752   exception
753     when OTHERS then
754       raise;
755   end;
756   wf_directory.GetRoleInfo2(l_owner_role, role_info_tbl);
757   if (disptype = wf_notification.doc_html) then
758     cells(j) := 'S:'||Wf_Notification.SubstituteSpecialChars(role_info_tbl(1).display_name);
759   else
760     cells(j) := role_info_tbl(1).display_name;
761   end if;
762   j := j+1;
763   if (disptype = wf_notification.doc_html) then
764     cells(j) := 'S:'||wf_core.translate('SUBMIT');
765   else
766     cells(j) := wf_core.translate('SUBMIT');
767   end if;
768   j := j+1;
769   if (disptype = wf_notification.doc_html) then
770     cells(j) := 'S:'||to_char(l_begin_date);
771   else
772     cells(j) := to_char(l_begin_date);
773   end if;
774   j := j+1;
775   if (disptype = wf_notification.doc_html) then
776     cells(j) := 'S: ';
777   else
778     cells(j) := null;
779   end if;
780 
781 -- ### implement as generic log in the future
782 --  if (wf_notification.debug) then
783 --    dbms_output.put_line('j = '||to_char(j));
784 --    dbms_output.put_line(substrb('last cell = '||cells(j),1,254));
785 --  end if;
786 
787   -- calculate the sequence
788   -- Only after we know the number of rows, then we can put the squence
789   -- number on for each row.
790   for k in 0..i loop
791     if (disptype = wf_notification.doc_html) then
792       cells((k+1)*5+1) := 'C:'||to_char(i-k);
793     else
794       cells((k+1)*5+1) := to_char(i-k);
795     end if;
796   end loop;
797 
798   if (disptype = wf_notification.doc_html) then
799     table_width := '100%';
800     NTF_Table(cells=>cells,
801               col=>5,
802               type=>'H'||l_table_direction,
803               rs=>result);
804   else
805     for k in 1..cells.LAST loop
806       if (mod(k, 5) <> 0) then
807         result := result||cells(k)||' '||l_delim||' ';
808       else
809         result := result||cells(k)||wf_core.newline;
810       end if;
811     end loop;
812   end if;
813 
814   return(result);
815 exception
816   when OTHERS then
817     wf_core.context('Wf_Notification', 'Wf_NTF_History', to_char(nid));
818     raise;
819 end wf_ntf_history;
820 **
821 ** End of obsoleted procedure WF_NTF_HISTORY
822 **/
823 
824 --
825 -- runFuncOnBody
826 -- NOTE
827 --   Attempt to find, parse and replace the string
828 --   WF_NOTIFICATION(F,P1,P2,...)
829 --   F = function to run
830 --   P1,P2,... = comma delimited parameter list
831 --
832 function runFuncOnBody(nid      in     number,
833                        body     in     varchar2,
834                        disptype in varchar2)
835 return varchar2
836 is
837   p1 pls_integer;
838   p2 pls_integer;
839   pp pls_integer;
840   l_body varchar2(32000);
841   rs     varchar2(32000);
842   fname  varchar2(32);
843   frun   varchar2(32);
844   func   varchar2(8000);
845   param  varchar2(8000);
846   i  pls_integer;
847   alldone boolean;
848 begin
849 
850   l_body := body;
851 
852   p1:=1;
853   alldone:=false;
854   while (not alldone) loop
855     fname := 'WF_NOTIFICATION(';   -- lengthb(fname) is 16
856 
857     p1 := instrb(l_body, fname, p1);
858     if (p1 <> 0) then
859       p2 := instrb(l_body, ')', p1);
860       if (p2 <> 0) then
861         -- try to separate function to run and parameters
862         func  := substrb(l_body, p1, p2-p1+1);
863         pp    := instrb(func, ',');
864         if (pp = 0) then
865           pp := lengthb(func);  -- only the function to run exist.
866           param := null;
867         else
868           param := substrb(func, pp+1, p2-p1-pp);
869         end if;
870 
871         frun  := substrb(func, 17, pp-17);
872 
873 -- ### implement as generic log in the future
874 --        if (wf_notification.debug) then
875 --          dbms_output.put_line('frun='||frun);
876 --        end if;
877 
878 	if (frun = 'ATTRS') then
879           rs := wf_msg_attr(nid, param, disptype);
880         elsif (frun = 'HISTORY') then
881           rs := wf_ntf_history(nid, disptype, param);
882         else
883           rs := func;
884         end if;
885 
886 	-- do not replace a string with itself.
887         -- if rs is null, then there is nothing to display for Action/Notifications History
888         -- or Attributes table. We would not want WF_NOTIFICATION(ATTRS,...) or
889         -- WF_NOTIFICATION(HISTORY) to appear in the notification as is.
890         if (rs is null or rs <> func) then
891           l_body := replace(l_body, func, rs);
892         end if;
893 
894         -- now move p1 to the end
895         p1 := p2+1;
896       else
897         -- since we cannot find a closing paranthesis
898         alldone := true;
899       end if;
900     else
901       alldone := true;
902     end if;
903   end loop;
904 
905   return(l_body);
906 
907 exception
908   when OTHERS then
909     wf_core.context('Wf_Notification', 'runFuncOnBody', to_char(nid), disptype);
910     raise;
911 end runFuncOnBody;
912 
913 -- More Info mailer support
914 --
915 -- GetUserfromEmail (PRIVATE)
916 --   from_email - from email id
917 --   user_name  - user name
918 --   disp_name  - Display name of the user
919 --   found      - whether user/role has been reconciled
920 -- NOTE:
921 --   Get the user/role name and display name based on the email id. Else return
922 --   the stripped off email with the found flag FALSE
923 
924 procedure GetUserfromEmail (from_email in  varchar2,
925                             user_name  out nocopy varchar2,
926                             disp_name  out nocopy varchar2,
927                             found      out nocopy boolean)
928 is
929    l_email    varchar2(1000);
930    l_role     varchar2(320);
931    l_dname    varchar2(360);
932    l_desc     varchar2(1000);
933    l_npref    varchar2(8);
934    l_terr     varchar2(30);
935    l_lang     varchar2(30);
936    l_fax      varchar2(240);
937    l_expire   date;
938    l_status   varchar2(8);
939    l_orig_sys varchar2(30);
940    l_orig_sysid number;
941    l_start    pls_integer;
942    l_end      pls_integer;
943 
944 begin
945    -- Stripping off unwanted info from email
946    l_start := instr(from_email, '<', 1, 1);
947    if (l_start > 0) then
948       l_end := instr(from_email, '>', l_start);
949       l_email := substr(from_email, l_start+1, l_end-l_start-1);
950    else
951       l_email := from_email;
952    end if;
953    -- user_name := substr(l_email, 1, instr(l_email, '@') - 1);
954    found := false;
955    user_name := l_email;
956    disp_name := l_email;
957 
958    Wf_Directory.GetInfoFromMail(mailid         => l_email,
959                                 role           => l_role,
960                                 display_name   => l_dname,
961                                 description    => l_desc,
962                                 notification_preference => l_npref,
963                                 language       => l_lang,
964                                 territory      => l_terr,
965                                 fax            => l_fax,
966                                 expiration_date => l_expire,
967                                 status         => l_status,
968                                 orig_system    => l_orig_sys,
969                                 orig_system_id => l_orig_sysid);
970 
971    if (l_role is not null) then
972       user_name := l_role;
973       disp_name := l_dname;
974       found := true;
975    end if;
976 end GetUserfromEmail;
977 
978 --
979 -- VALIDATE_CONTEXT
980 -- Introduced because of bug 7914921
981 --  Gets a noification contexts and derives the item type, key and activity ID, if any
982 -- Since the standard format itemtype:itemkey:actid is NOT mandatory this function
983 -- returns null values if the context does not meet the standard. Further validation is
984 -- requried by the calling program
985 -- IN
986 --  context: the notification context to validate
987 --  IN OUT
988 --  itemtype: string before the first colon in the context
989 --  itemkey: string between first and second colons in the contex
990 --  actid: NUMBER after the second colon
991 --
992 procedure validate_context (context IN WF_NOTIFICATIONS.CONTEXT%TYPE,
993                             itemtype OUT NOCOPY varchar2,
994                             itemkey OUT NOCOPY varchar2,
995                             actid OUT NOCOPY number)
996 is
997  col1 number;
998  col2 number;
999 begin
1000   itemtype:=null;
1001   itemkey:=null;
1002   actid:=null;
1003   col1 := instr(context, ':', 1, 1);
1004   col2 := instr(context, ':', -1, 1);
1005   if col1>0 AND col2>col1 then --Context seems to have itemtype and key
1006     itemtype := substr(context, 1, col1-1);
1007 	itemkey := substr(context, col1+1, col2-col1-1);
1008       if LENGTH(itemtype)<=8 then --Standard lenght for a valid item type
1009         BEGIN
1010           actid:=to_number(substr(context, col2+1));
1011         EXCEPTION
1012           WHEN OTHERS THEN --covers for an invalid conversion to number.
1013 		    itemtype:=null;
1014 		    itemkey:=null;
1015             actid:=null;
1016         END;
1017      end if;
1018   end if;
1019 exception
1020   when OTHERS then --no_data_found or invalid_number
1021     itemtype:=null;
1022     itemkey:=null;
1023     actid:=null;
1024     wf_core.context('Wf_Notification', 'validate_context', context);
1025     raise;
1026 end validate_context;
1027 
1028 
1029 -- End Private Functions
1030 --
1031 
1032 --
1033 -- AddAttr
1034 --   Add a new run-time notification attribute.
1035 --   The attribute will be completely unvalidated.  It is up to the
1036 --   user to do any validation and insure consistency.
1037 -- IN:
1038 --   nid - Notification Id
1039 --   aname - Attribute name
1040 --
1041 procedure AddAttr(nid in number,
1042                   aname in varchar2)
1043 is
1044   dummy pls_integer;
1045 begin
1046   if ((nid is null) or (aname is null)) then
1047     wf_core.token('NID', to_char(nid));
1048     wf_core.token('ANAME', aname);
1049     wf_core.raise('WFSQL_ARGS');
1050   end if;
1051 
1052   -- Insure this is a valid notification.
1053   begin
1054     select 1 into dummy from sys.dual where exists
1055       (select null
1056        from   WF_NOTIFICATIONS
1057        where  NOTIFICATION_ID = nid);
1058   exception
1059     when no_data_found then
1060       wf_core.token('NID', to_char(nid));
1061       wf_core.raise('WFNTF_NID');
1062   end;
1063 
1064   -- Insert new attribute
1065   begin
1066     insert into WF_NOTIFICATION_ATTRIBUTES (
1067       NOTIFICATION_ID,
1068       NAME,
1069       TEXT_VALUE,
1070       NUMBER_VALUE,
1071       DATE_VALUE
1072     ) values (
1073       nid,
1074       aname,
1075       '',
1076       '',
1077       ''
1078     );
1079   exception
1080     when dup_val_on_index then
1081       wf_core.token('NID', to_char(nid));
1082       wf_core.token('ATTRIBUTE', aname);
1083       wf_core.raise('WFNTF_ATTR_UNIQUE');
1084   end;
1085 
1086 exception
1087   when others then
1088     wf_core.context('Wf_Notification', 'AddAttr', to_char(nid), aname);
1089     raise;
1090 end AddAttr;
1091 
1092 --
1093 -- SetAttrText
1094 --   Set the value of a notification attribute, given text representation.
1095 --   If the attribute is a NUMBER or DATE type, then translate the
1096 --   text-string value to a number/date using attribute format.
1097 --   For all other types, store the value directly.
1098 -- IN:
1099 --   nid - Notification id
1100 --   aname - Attribute Name
1101 --   avalue - New value for attribute
1102 --
1103 procedure SetAttrText(nid in number,
1104                       aname in varchar2,
1105                       avalue in varchar2)
1106 is
1107   atype varchar2(8);
1108   format varchar2(240);
1109   rname varchar2(320);
1110   role_info_tbl wf_directory.wf_local_roles_tbl_type;
1111   l_parameterlist  wf_parameter_list_t := wf_parameter_list_t();
1112   l_language	   varchar2(30);
1113   l_recipient_role varchar2(320);
1114 
1115 begin
1116   if ((nid is null) or (aname is null)) then
1117     wf_core.token('NID', to_char(nid));
1118     wf_core.token('ANAME', aname);
1119     wf_core.raise('WFSQL_ARGS');
1120   end if;
1121 
1122   -- Get type and format of attr.
1123   -- This is used for translating number/date strings.
1124   begin
1125     select WMA.TYPE, WMA.FORMAT
1126     into atype, format
1127     from WF_NOTIFICATION_ATTRIBUTES WNA, WF_NOTIFICATIONS WN,
1128          WF_MESSAGE_ATTRIBUTES WMA
1129     where WNA.NOTIFICATION_ID = nid
1130     and WNA.NAME = aname
1131     and WNA.NOTIFICATION_ID = WN.NOTIFICATION_ID
1132     and WN.MESSAGE_NAME = WMA.MESSAGE_NAME
1133     and WN.MESSAGE_TYPE = WMA.MESSAGE_TYPE
1134     and WNA.NAME = WMA.NAME;
1135   exception
1136     when no_data_found then
1137       -- This is an unvalidated runtime attr.
1138       -- Treat it as a varchar2.
1139       atype := 'VARCHAR2';
1140       format := '';
1141   end;
1142 
1143   -- Update attribute value in appropriate type column.
1144   if (atype = 'NUMBER') then
1145     update WF_NOTIFICATION_ATTRIBUTES
1146     set NUMBER_VALUE = decode(format,
1147                               '', to_number(avalue),
1148                               to_number(avalue, format))
1149     where NOTIFICATION_ID = nid
1150     and NAME = aname;
1151   elsif (atype = 'DATE') then
1152     -- 4477386 gscc date format requirement change
1153     -- do not use a cached value, this allows nls change within the
1154     -- same session to be seen right away.
1155     update WF_NOTIFICATION_ATTRIBUTES
1156     set DATE_VALUE = decode(format,
1157                 '',to_date(avalue,SYS_CONTEXT('USERENV','NLS_DATE_FORMAT')),
1158                 to_date(avalue, format))
1159     where NOTIFICATION_ID = nid
1160     and NAME = aname;
1161   elsif (atype = 'VARCHAR2') then
1162     -- VARCHAR2
1163     -- Set the text value directly with no translation.
1164     -- bug 1996299 - JWSMITH , changes substr to substrb for korean char
1165     update WF_NOTIFICATION_ATTRIBUTES
1166     set TEXT_VALUE = decode(format,
1167                             '', avalue,
1168                             substrb(avalue, 1, to_number(format)))
1169     where NOTIFICATION_ID = nid
1170     and NAME = aname;
1171   elsif (atype = 'ROLE') then
1172     -- ROLE
1173     -- First check if value is internal name
1174     if (avalue is null) then
1175       -- Null values are ok
1176       rname := '';
1177     else
1178       Wf_Directory.GetRoleInfo2(avalue, role_info_tbl);
1179       rname := role_info_tbl(1).name;
1180 
1181       -- If not internal name, check for display_name
1182       if (rname is null) then
1183         begin
1184           -- look into the wf_role_lov_vl based on display name
1185           SELECT name
1186           INTO   rname
1187           FROM   wf_role_lov_vl
1188           WHERE  upper(display_name) = upper(avalue)
1189           AND    rownum = 1;
1190         exception
1191           when no_data_found then
1192             -- Not displayed or internal role name, error
1193             wf_core.token('ROLE', avalue);
1194             wf_core.raise('WFNTF_ROLE');
1195         end;
1196       end if;
1197     end if;
1198 
1199     -- Set the text value with internal role name
1200     update WF_NOTIFICATION_ATTRIBUTES
1201     set TEXT_VALUE = rname
1202     where NOTIFICATION_ID = nid
1203     and NAME = aname;
1204   else
1205     -- LOOKUP, FORM, URL, DOCUMENT, misc type.
1206     -- Set the text value.
1207     update WF_NOTIFICATION_ATTRIBUTES
1208     set TEXT_VALUE = avalue
1209     where NOTIFICATION_ID = nid
1210     and NAME = aname;
1211   end if;
1212 
1213   if (SQL%NOTFOUND) then
1214     wf_core.token('NID', to_char(nid));
1215     wf_core.token('ATTRIBUTE', aname);
1216     wf_core.raise('WFNTF_ATTR');
1217   end if;
1218 
1219   -- Redenormalize if attribute being updated is #FROM_ROLE
1220   if (aname = '#FROM_ROLE') then
1221     Wf_Notification.Denormalize_Notification(nid);
1222   end if;
1223 
1224   -- Bug 2437347 raising event after DML operation on WF_NOTIFICATION_ATTRIBUTES
1225   if (aname = 'SENDER') then
1226     wf_event.AddParameterToList('NOTIFICATION_ID', nid, l_parameterlist);
1227     wf_event.AddParameterToList(aname, avalue, l_parameterlist);
1228 
1229 
1230 
1231 
1232   select WN.RECIPIENT_ROLE
1233     into l_recipient_role
1234     from WF_NOTIFICATIONS WN
1235     where WN.NOTIFICATION_ID = nid ;
1236 
1237    Wf_Directory.GetRoleInfo2(l_recipient_role, role_info_tbl);
1238    l_language := role_info_tbl(1).language;
1239 
1240    select code into l_language from wf_languages where nls_language = l_language;
1241 
1242     -- AppSearch
1243   wf_event.AddParameterToList('OBJECT_NAME',
1244   'oracle.apps.fnd.wf.worklist.server.AllNotificationsVO', l_parameterlist);
1245   wf_event.AddParameterToList('CHANGE_TYPE', 'INSERT',l_parameterlist);
1246   wf_event.AddParameterToList('ID_TYPE', 'PK', l_parameterlist);
1247   wf_event.addParameterToList('PK_NAME_1', 'NOTIFICATION_ID',l_parameterlist);
1248   wf_event.addParameterToList('PK_VALUE_1', nid, l_parameterlist);
1249   wf_event.addParameterToList('PK_NAME_2', 'LANGUAGE',l_parameterlist);
1250   wf_event.addParameterToList('PK_VALUE_2', l_language, l_parameterlist);
1251     -- Raise the event
1252     wf_event.Raise(p_event_name => 'oracle.apps.wf.notification.setattrtext',
1253                  p_event_key  => to_char(nid),
1254                  p_parameters => l_parameterlist);
1255   end if;
1256 
1257 exception
1258   when others then
1259     wf_core.context('Wf_Notification', 'SetAttrText', to_char(nid),
1260                     aname, avalue);
1261     raise;
1262 end SetAttrText;
1263 
1264 --
1265 -- SetAttrNumber
1266 --   Set the value of a number notification attribute.
1267 --   Attribute must be a NUMBER-type attribute.
1268 -- IN:
1269 --   nid - Notification id
1270 --   aname - Attribute Name
1271 --   avalue - New value for attribute
1272 --
1273 procedure SetAttrNumber (nid in number,
1274                          aname in varchar2,
1275                          avalue in number)
1276 is
1277 begin
1278   if ((nid is null) or (aname is null)) then
1279     wf_core.token('NID', to_char(nid));
1280     wf_core.token('ANAME', aname);
1281     wf_core.raise('WFSQL_ARGS');
1282   end if;
1283 
1284   -- Update attribute value
1285   update WF_NOTIFICATION_ATTRIBUTES
1286   set    NUMBER_VALUE = avalue
1287   where  NOTIFICATION_ID = nid and NAME = aname;
1288 
1289   if (SQL%NOTFOUND) then
1290     wf_core.token('NID', to_char(nid));
1291     wf_core.token('ATTRIBUTE', aname);
1292     wf_core.raise('WFNTF_ATTR');
1293   end if;
1294 
1295 exception
1296   when others then
1297     wf_core.context('Wf_Notification', 'SetAttrNumber', to_char(nid),
1298                     aname, to_char(avalue));
1299     raise;
1300 end SetAttrNumber;
1301 
1302 --
1303 -- SetAttrDate
1304 --   Set the value of a date notification attribute.
1305 --   Attribute must be a DATE-type attribute.
1306 -- IN:
1307 --   nid - Notification id
1308 --   aname - Attribute Name
1309 --   avalue - New value for attribute
1310 --
1311 procedure SetAttrDate (nid in number,
1312                        aname in varchar2,
1313                        avalue in date)
1314 is
1315 begin
1316   if ((nid is null) or (aname is null)) then
1317     wf_core.token('NID', to_char(nid));
1318     wf_core.token('ANAME', aname);
1319     wf_core.raise('WFSQL_ARGS');
1320   end if;
1321 
1322   -- Update attribute value
1323   update WF_NOTIFICATION_ATTRIBUTES
1324   set    DATE_VALUE = avalue
1325   where  NOTIFICATION_ID = nid and NAME = aname;
1326 
1327   if (SQL%NOTFOUND) then
1328     wf_core.token('NID', to_char(nid));
1329     wf_core.token('ATTRIBUTE', aname);
1330     wf_core.raise('WFNTF_ATTR');
1331   end if;
1332 
1333 exception
1334   when others then
1335     wf_core.context('Wf_Notification', 'SetAttrDate', to_char(nid),
1336                     aname, to_char(avalue));
1337     raise;
1338 end SetAttrDate;
1339 
1340 --
1341 -- SubstituteSpecialChars (PRIVATE)
1342 --   Substitutes the occurence of special characters like <, >, \, ', " etc
1343 --   with their html codes in any arbitrary string.
1344 -- IN
1345 --   some_text - text to be substituted
1346 -- RETURN
1347 --   substituted text
1348 
1349 function SubstituteSpecialChars(some_text in varchar2)
1350 return varchar2 is
1351   l_amp     varchar2(1);
1352   buf       varchar2(32000);
1353   l_amp_flag  boolean;
1354   l_lt_flag   boolean;
1355   l_gt_flag   boolean;
1356   l_bsl_flag  boolean;
1357   l_apos_flag boolean;
1358   l_quot_flag boolean;
1359 begin
1360   l_amp := '&';
1361 
1362   buf := some_text;
1363 
1364   -- bug 6025162 - This function should substitute only those chars that
1365   -- really require substitution. Any valid occurences should be retained.
1366   -- No validation should be required for calling this function
1367 
1368   if (instr(buf, l_amp) > 0) then
1369     l_amp_flag  := false;
1370     l_lt_flag   := false;
1371     l_gt_flag   := false;
1372     l_bsl_flag  := false;
1373     l_apos_flag := false;
1374     l_quot_flag := false;
1375 
1376     -- mask all valid ampersand containing patterns in the content
1377     -- issue is when ntf body already contains of these reserved words...
1378     if (instr(buf, l_amp||'amp;') > 0) then
1379       buf := replace(buf, l_amp||'amp;', '#AMP#');
1380       l_amp_flag := true;
1381     end if;
1382     if (instr(buf, l_amp||'lt;') > 0) then
1383       buf := replace(buf, l_amp||'lt;', '#LT#');
1384       l_lt_flag := true;
1385     end if;
1386     if (instr(buf, l_amp||'gt;') > 0) then
1387       buf := replace(buf, l_amp||'gt;', '#GT#');
1388       l_gt_flag := true;
1389     end if;
1390     if (instr(buf, l_amp||'#92;') > 0) then
1391       buf := replace(buf, l_amp||'#92;', '#BSL#');
1392       l_bsl_flag := true;
1393     end if;
1394     if (instr(buf, l_amp||'#39;') > 0) then
1395       buf := replace(buf, l_amp||'#39;', '#APO#');
1396       l_apos_flag := true;
1397     end if;
1398     if (instr(buf, l_amp||'quot;') > 0) then
1399       buf := replace(buf, l_amp||'quot;', '#QUOT#');
1400       l_quot_flag := true;
1401     end if;
1402 
1403     buf := replace(buf, l_amp, l_amp||'amp;');
1404 
1405     -- put the masked valid ampersand containing patterns back
1406     if (l_amp_flag) then
1407       buf := replace(buf, '#AMP#', l_amp||'amp;');
1408     end if;
1409     if (l_lt_flag) then
1410       buf := replace(buf, '#LT#', l_amp||'lt;');
1411     end if;
1412     if (l_gt_flag) then
1413       buf := replace(buf, '#GT#', l_amp||'gt;');
1414     end if;
1415     if (l_bsl_flag) then
1416       buf := replace(buf, '#BSL#', l_amp||'#92;');
1417     end if;
1418     if (l_apos_flag) then
1419       buf := replace(buf, '#APO#', l_amp||'#39;');
1420     end if;
1421     if (l_quot_flag) then
1422       buf := replace(buf, '#QUOT#', l_amp||'quot;');
1423     end if;
1424   end if;
1425 
1426   buf := replace(buf, '<', l_amp||'lt;');
1427   buf := replace(buf, '>', l_amp||'gt;');
1428   buf := replace(buf, '\', l_amp||'#92;');
1429   buf := replace(buf, '''', l_amp||'#39;');
1430   buf := replace(buf, '"', l_amp||'quot;');
1431   return buf;
1432 exception
1433   when others then
1434     wf_core.context('Wf_Notification', 'SubstituteSpecialChars');
1435     raise;
1436 end SubstituteSpecialChars;
1437 
1438 --
1439 -- GetTextInternal (PRIVATE)
1440 --   Substitute tokens in text (pragma-friendly).
1441 --   This is used in forms which only accept 1950 character strings
1442 --   and in views hence document type is not supported
1443 --   DOCUMENT-type attributes not supported.
1444 -- IN:
1445 --   some_text - Text to be substituted
1446 --   nid - Notification id of notification to use for token values
1447 --   target - Frame target
1448 --   urlmode - Look for url tokens with dashes
1449 --   subparams - Recursively substitute FORM/URL parameters
1450 --               (to prevent infinite recursion)
1451 --   disptype - display type
1452 -- ### This only consoliates GetShortText and GetUrlText.
1453 -- ### GetText is a separate procedure and must be double-maintained.
1454 -- ### This is so GetShortText can be pragma'd, and the DOCUMENT
1455 -- ### attribute type uses dbms_sql, which violates pragmas.
1456 --
1457 function GetTextInternal(
1458   some_text in varchar2,
1459   nid in number,
1460   target in out nocopy varchar2,
1461   urlmode in boolean,
1462   subparams in boolean,
1463   disptype in varchar2 default 'text/html')
1464 return varchar2 is
1465   role_name   varchar2(320);
1466   email_address   varchar2(320);
1467   username    varchar2(320);
1468   local_text varchar2(2000);
1469   buf      varchar2(2000);
1470   value varchar2(32000);
1471   colon pls_integer;
1472   params pls_integer;
1473   l_document_attributes   fnd_document_management.fnd_document_attributes;
1474   buf      varchar2(2000);
1475   -- Select attr values, formatting numbers and dates as requested.
1476   -- The order-by is to handle cases where one attr name is a substring
1477   -- of another.
1478   cursor notification_attrs_cursor(nid number) is
1479     select WNA.NAME, WMA.TYPE, WMA.FORMAT, WMA.DISPLAY_NAME,
1480            WNA.TEXT_VALUE, WNA.NUMBER_VALUE, WNA.DATE_VALUE
1481     from WF_NOTIFICATION_ATTRIBUTES WNA, WF_NOTIFICATIONS WN,
1482          WF_MESSAGE_ATTRIBUTES_VL WMA
1483     where WNA.NOTIFICATION_ID = nid
1484     and WN.NOTIFICATION_ID = WNA.NOTIFICATION_ID
1485     and WN.MESSAGE_TYPE = WMA.MESSAGE_TYPE
1486     and WN.MESSAGE_NAME = WMA.MESSAGE_NAME
1487     and WMA.NAME = WNA.NAME
1488     order by decode(wma.type,'URL',length(WNA.NAME),length(WNA.NAME)+1000) desc;
1489 --    order by length(WNA.NAME) desc;
1490 begin
1491   -- make sure text never exceeds 1950 bytes
1492   local_text := substrb(some_text,1,1950);
1493 
1494   for not_attr_row in notification_attrs_cursor(nid) loop
1495     if (urlmode) then
1496       if (instr(local_text, '-&'||not_attr_row.name||'-') = 0) then
1497         goto nextattr;
1498       end if;
1499     else
1500       -- Bug 2843136 Check not only '&' but also '&'
1501       if ((instr(local_text, '&'||not_attr_row.name) = 0) AND
1502               (instr(local_text, '&'||not_attr_row.name) = 0)) then
1503          goto nextattr;
1504       end if;
1505     end if;
1506 
1507       -- Find displayed value of token depending on type
1508     if (not_attr_row.type = 'LOOKUP') then
1509       -- LOOKUP type select meaning from wf_lookups.
1510       begin
1511         select MEANING
1512         into value
1513         from WF_LOOKUPS
1514         where LOOKUP_TYPE = not_attr_row.format
1515         and LOOKUP_CODE = not_attr_row.text_value;
1516       exception
1517         when no_data_found then
1518           -- Use code directly if lookup not found.
1519           value := not_attr_row.text_value;
1520       end;
1521     elsif (not_attr_row.type = 'VARCHAR2') then
1522       -- VARCHAR2 is text_value, truncated at format if one provided.
1523       if (not_attr_row.format is null) then
1524         value := not_attr_row.text_value;
1525       else
1526         value := substrb(not_attr_row.text_value, 1,
1527                          to_number(not_attr_row.format));
1528       end if;
1529 
1530       -- Bug 2843136
1531       -- Replace '&' but also '&' only if it hasn't been already substituted
1532       -- This is to prevent something like '&amp;' from happening
1533       if (disptype = wf_notification.doc_html) then
1534          --  (instr(local_text,'&'||not_attr_row.name) = 0) AND
1535          --  (instr(value,'&') = 0)) then
1536 
1537          -- bug 6025162 - SubstituteSpecialChars function substitutes only those
1538          -- characters that really require substitution. Any valid occurences
1539          -- will be retained. No validation required from calling program.
1540 
1541          value := wf_core.SubstituteSpecialChars(value);
1542       end if;
1543 
1544     elsif (not_attr_row.type = 'NUMBER') then
1545       -- NUMBER is number_value, with format if provided.
1546       if (not_attr_row.format is null) then
1547         value := to_char(not_attr_row.number_value);
1548       else
1549         value := to_char(not_attr_row.number_value, not_attr_row.format);
1550       end if;
1551     elsif (not_attr_row.type = 'DATE') then
1552       -- DATE is date_value, with format if provided.
1553       if (not_attr_row.format is null) then
1554         value := to_char(not_attr_row.date_value);
1555       else
1556         value := to_char(not_attr_row.date_value, not_attr_row.format);
1557       end if;
1558     elsif (not_attr_row.type = 'FORM') then
1559       -- FORM is display_name (function), with parameters of function
1560       -- recursively token-substituted if needed.
1561       value := not_attr_row.text_value;
1562       if (subparams) then
1563         params := instr(value, ':');
1564         if (params <> 0) then
1565           value := not_attr_row.display_name||' ( '||
1566                    substr(value, 1, params)||
1567                    wf_notification.GetTextInternal(substr(value, params+1), nid,
1568                                 target, FALSE, FALSE, 'text/plain')||' )';
1569         end if;
1570       end if;
1571     elsif ((not_attr_row.type = 'URL') and (not urlmode) ) then
1572       -- URL is display_name (url), with parameters of url
1573       -- recursively token-substituted if needed.
1574       value := not_attr_row.text_value;
1575       -- Default value of target is "_top" (all lower case)
1576       target := substr(nvl(not_attr_row.format, '_top'), 1, 16);
1577       if (subparams) then
1578         params := instr(value, '?');
1579         if (params <> 0) then
1580           value := not_attr_row.display_name||' ( '||
1581                    substr(value, 1, params)||
1582                    wf_notification.GetTextInternal(substr(value, params+1), nid,
1583                               target, TRUE, FALSE, 'text/plain')||' )';
1584         end if;
1585       end if;
1586     elsif (not_attr_row.type = 'ROLE') then
1587       -- ROLE type, get display_name of role
1588       begin
1589         -- NOTE: cannot use wf_directory.getroleinfo2 because of the
1590         --   pragma WNPS.
1591         -- Decode into orig_system if necessary for indexes
1592         colon := instr(not_attr_row.text_value, ':');
1593         if (colon = 0) then
1594           select WR.DISPLAY_NAME
1595           into value
1596           from WF_ROLES WR
1597           where WR.NAME = not_attr_row.text_value
1598           and   WR.ORIG_SYSTEM NOT IN ('HZ_PARTY','POS','ENG_LIST','AMV_CHN',
1599               'HZ_GROUP','CUST_CONT');
1600         else
1601           select WR.DISPLAY_NAME
1602           into value
1603           from WF_ROLES WR
1604           where WR.ORIG_SYSTEM = substr(not_attr_row.text_value, 1, colon-1)
1605           and WR.ORIG_SYSTEM_ID = substr(not_attr_row.text_value, colon+1)
1606           and WR.NAME = not_attr_row.text_value;
1607         end if;
1608       exception
1609         when no_data_found then
1610           -- Use code directly if role not found.
1611           value := not_attr_row.text_value;
1612       end;
1613 
1614     elsif ((not_attr_row.type = 'DOCUMENT') and (not urlmode)) then
1615          /*
1616          ** Only execute this function if this attribute is definitely
1617          ** in the subject
1618          */
1619          if (INSTR(local_text, '&'||not_attr_row.name) > 0) then
1620 
1621             if (SUBSTR(not_attr_row.text_value, 1, 2) = 'DM') then
1622                /*
1623                ** get the document name from the attribute.  We used
1624                ** to go fetch the document name from the DM system
1625                ** but that just kills performance because you have to
1626                ** bounce around to a bunch of different nodes using
1627                ** URLS
1628                */
1629                value := not_attr_row.display_name;
1630             else
1631                -- All others default to null since this is a plsql document
1632                value := null;
1633             end if;
1634          end if;
1635     else
1636       -- All others default to text_value
1637       value := not_attr_row.text_value;
1638     end if;
1639 
1640     --
1641     -- Substitute all occurrences of SEND tokens with values.
1642     -- Limit to 1950 chars to avoid value errors if substitution pushes
1643     -- it over the edge.
1644     --
1645     if (urlmode) then
1646       local_text := substrb(replace(local_text, '-&'||
1647                     not_attr_row.name||'-',
1648                     wf_mail.UrlEncode(value)), 1, 1950);
1649 
1650       --Bug 2346237
1651       --The target is set to the attribute format only
1652       --if the attribute is of type URL
1653       if (not_attr_row.type = 'URL') then
1654         target := substr(nvl(not_attr_row.format, '_top'), 1, 16);
1655       end if;
1656 
1657     else
1658       --Bug 2843136
1659       --Replace & or &
1660       local_text := substrb(replace(local_text, '&'||not_attr_row.name,
1661                             value), 1, 1950);
1662       --Now replace any equivalen &sametoken
1663       local_text := substrb(replace(local_text, '&'||not_attr_row.name,
1664                             value), 1, 1950);
1665     end if;
1666 
1667     <<nextattr>>
1668       null;
1669   end loop;
1670 
1671   --
1672   -- Process special '#' internal tokens.  Supported tokens are:
1673   --  &#NID - Notification id
1674   --
1675   if (urlmode) then
1676     local_text := substrb(replace(local_text, '-&'||'#NID-',
1677                           to_char(nid)), 1, 1950);
1678   else
1679     local_text := substrb(replace(local_text, '&'||'#NID',
1680                           to_char(nid)), 1, 1950);
1681   end if;
1682   return(local_text);
1683 exception
1684   when others then
1685     wf_core.context('Wf_Notification','GetTextInternal', to_char(nid), disptype);
1686     raise;
1687 end GetTextInternal;
1688 
1689 
1690 --
1691 -- SetFrameworkAgent
1692 --   Check the URL for a JSP: entry and then substitute
1693 --   it with the value of the APPS_FRAMEWORK_AGENT
1694 --   profile option.
1695 -- IN:
1696 --   URL - URL to be ckecked
1697 -- RETURNS:
1698 --   URL with Frame work agent added
1699 -- NOTE:
1700 --   If errors are detected this routine returns some_text untouched
1701 --   instead of raising exceptions.
1702 --
1703 function SetFrameworkAgent(url in varchar2)
1704 return varchar2
1705 is
1706    value varchar2(32000);
1707    params integer;
1708    apps_fwk_agent varchar2(256);
1709 begin
1710    value := url;
1711    --Bug 2276779
1712    --Check if the URL is a javascript call.
1713    if ((lower(substr(value,1,11))) = 'javascript:') then
1714       --If the URL is a javascript function then
1715       --do not prefix the web agent to the URL.
1716       return value;
1717    end if;
1718    if ((wf_core.Translate('WF_INSTALL')='EMBEDDED') AND
1719        (substr(value, 1, 4) = 'JSP:')) then
1720       -- The URL is a APPS Framework reference and will need
1721       -- the JSP Agent rather than the WEB Agent
1722       value := substr(value, 5);
1723       value := '/' || ltrim(value, '/');
1724       apps_fwk_agent := rtrim(fnd_profile.Value('APPS_FRAMEWORK_AGENT'), '/');
1725       value :=  apps_fwk_agent || value;
1726       params := instr(value,'?');
1727       if (params <> 0) then
1728          value := value||'&'||'dbc='||fnd_web_config.Database_ID;
1729       else
1730          value := value||'?'||'dbc='||fnd_web_config.Database_ID;
1731       end if;
1732    else
1733       if instr(value,'//',1,1)=0 then
1734       -- CTILLEY: Added additional check to make sure a trailing slash
1735       -- is added to the WF_WEB_AGENT if it isn't the first character
1736       -- in the value.  Fix for bug 2207322.
1737          if substr(value,1,1)='/' then
1738              value := wf_core.translate('WF_WEB_AGENT')||value;
1739          else
1740              value := wf_core.translate('WF_WEB_AGENT')||'/'||value;
1741          end if;
1742       end if;
1743    end if;
1744    return value;
1745 
1746 exception
1747   when others then
1748     wf_core.context('Wf_Notification', 'SetFrameworkAgent', url);
1749 end;
1750 
1751 
1752 
1753 --
1754 -- GetText
1755 --   Substitute tokens in an arbitrary text string.
1756 --     This function may return up to 32K chars. It canNOT be used in a view
1757 --   definition or in a Form.  For views and forms, use GetShortText, which
1758 --   truncates values at 2000 chars.
1759 -- IN:
1760 --   some_text - Text to be substituted
1761 --   nid - Notification id of notification to use for token values
1762 --   disptype - Requested display type.  Valid values:
1763 --               wf_notification.doc_text - 'text/plain'
1764 --               wf_notification.doc_html - 'text/html'
1765 -- RETURNS:
1766 --   Some_text with tokens substituted.
1767 -- NOTE:
1768 --   If errors are detected this routine returns some_text untouched
1769 --   instead of raising exceptions.
1770 function GetText(some_text in varchar2,
1771                  nid in number,
1772                  disptype in varchar2)
1773 return varchar2
1774 is
1775   role_name   varchar2(320);
1776   email_address   varchar2(320);
1777   buf      varchar2(2000);
1778   local_text varchar2(32000);
1779   value varchar2(32000);
1780   params pls_integer;
1781   target varchar2(16);
1782 
1783   extPos pls_integer;    -- Image file extension position
1784   extStr varchar2(1000); -- Image file extention
1785 
1786   renderType varchar2(10); -- 4713416 Explicit rendering of URL html.
1787 
1788 
1789   -- Select attr values, formatting numbers and dates as requested.
1790   -- The order-by is to handle cases where one attr name is a substring
1791   -- of another.
1792   cursor notification_attrs_cursor(nid number) is
1793     select WNA.NAME, WMA.TYPE, WMA.FORMAT, WMA.DISPLAY_NAME,
1794            WNA.TEXT_VALUE, WNA.NUMBER_VALUE, WNA.DATE_VALUE
1795     from WF_NOTIFICATION_ATTRIBUTES WNA, WF_NOTIFICATIONS WN,
1796          WF_MESSAGE_ATTRIBUTES_VL WMA
1797     where WNA.NOTIFICATION_ID = nid
1798     and WN.NOTIFICATION_ID = WNA.NOTIFICATION_ID
1799     and WN.MESSAGE_TYPE = WMA.MESSAGE_TYPE
1800     and WN.MESSAGE_NAME = WMA.MESSAGE_NAME
1801     and WMA.NAME = WNA.NAME
1802 --    order by length(WNA.NAME) desc;
1803     order by decode(wma.type,'URL',length(WNA.NAME),length(WNA.NAME)+1000) desc;
1804 
1805   role_info_tbl wf_directory.wf_local_roles_tbl_type;
1806 
1807   error_name      varchar2(30);
1808   error_stack     varchar2(32000);
1809   l_dummy         boolean;
1810 
1811 begin
1812   local_text := some_text;
1813 
1814 
1815   for not_attr_row in notification_attrs_cursor(nid) loop
1816 
1817   -- only bother to find attribute value if it exists in the string
1818   -- dont place in select as each replace can introduce a new token
1819   --
1820   --Bug 2843136
1821   --Check not only '&' but also '&'
1822   if ((instr(local_text, '&'||not_attr_row.name)>0) OR (instr(local_text, '&'||not_attr_row.name)>0)) then
1823 
1824     -- Find displayed value of token depending on type
1825     if (not_attr_row.type = 'LOOKUP') then
1826       -- LOOKUP type select meaning from wf_lookups.
1827       begin
1828         select MEANING
1829         into value
1830         from WF_LOOKUPS
1831         where LOOKUP_TYPE = not_attr_row.format
1832         and LOOKUP_CODE = not_attr_row.text_value;
1833       exception
1834         when no_data_found then
1835           -- Use code directly if lookup not found.
1836           value := not_attr_row.text_value;
1837       end;
1838     elsif (not_attr_row.type = 'VARCHAR2') then
1839       -- VARCHAR2 is text_value, truncated at format if one provided.
1840       if (not_attr_row.format is null) then
1841         value := not_attr_row.text_value;
1842       else
1843         value := substrb(not_attr_row.text_value, 1,
1844                          to_number(not_attr_row.format));
1845       end if;
1846 
1847       -- JWSMITH bug 1725916 - add BR to attribute value
1848       if (disptype=wf_notification.doc_html) then
1849         --Check if we already have and '&' in which
1850         --case don't resubstitute it.
1851         -- if ((instr(local_text,'&'||not_attr_row.name) = 0)
1852         --    AND (instr(value,'&') = 0)) then
1853 
1854         -- bug 6025162 - SubstituteSpecialChars function substitutes only those
1855         -- characters that really require subsstitution. Any valid occurences
1856         -- will be retained. No validation required from calling program.
1857 
1858         value := wf_core.SubstituteSpecialChars(value);
1859 
1860         -- end if;
1861         value := substrb(replace(value, wf_core.newline,
1862                  '<BR>'||wf_core.newline),1, 32000);
1863       end if;
1864 
1865     elsif (not_attr_row.type = 'NUMBER') then
1866       -- NUMBER is number_value, with format if provided.
1867       if (not_attr_row.format is null) then
1868         value := to_char(not_attr_row.number_value);
1869       else
1870         value := to_char(not_attr_row.number_value, not_attr_row.format);
1871       end if;
1872     elsif (not_attr_row.type = 'DATE') then
1873       -- <bug 7514495> now as date format we use the first non-null value of:
1874       -- not_attr_row.format, wf_notification_util.G_NLS_DATE_FORMAT (if nid is provided
1875       -- and matches wf_notification_util.G_NID), session user's WFDS preference,
1876       -- and wf_core.nls_date_format.
1877       value := wf_notification_util.GetCalendarDate(nid, not_attr_row.date_value
1878                                                   , not_attr_row.format, false);
1879 
1880     elsif (not_attr_row.type = 'FORM') then
1881       -- FORM is display_name (function), with parameters of function
1882       -- recursively token-substituted if needed.
1883       value := not_attr_row.text_value;
1884       params := instr(value, ':');
1885       if (params <> 0) then
1886         value := not_attr_row.display_name||' ( '||
1887                  substr(value, 1, params)||
1888                  wf_notification.GetTextInternal(substr(value,params+1), nid,
1889                         target, FALSE, FALSE, 'text/plain')||' )';
1890       end if;
1891 
1892       if (disptype = wf_notification.doc_html) then
1893         -- Bug 4634849
1894         -- Do not display potentially harmful text
1895         begin
1896 	  l_dummy := wf_core.CheckIllegalChars(value,true,';<>()');
1897         exception
1898           when OTHERS then
1899             wf_core.get_error(error_name, value, error_stack);
1900 
1901             value :=wf_core.substitutespecialchars(value);
1902             error_stack:= '';
1903 
1904         end;
1905       end if;
1906 
1907     elsif (not_attr_row.type = 'URL') then
1908       -- URL is display_name (url), with parameters of url
1909       -- recursively token-substituted if needed.
1910       value := not_attr_row.text_value;
1911       target := substr(nvl(not_attr_row.format, '_top'), 1, 16);
1912       value := wf_notification.SetFrameworkAgent(value);
1913       params := instr(value, '?');
1914       if (params <> 0) then
1915         value := substr(value, 1, params)||
1916                  wf_notification.GetTextInternal(substr(value,params+1), nid,
1917                       target, TRUE, FALSE, 'text/plain');
1918       end if;
1919 
1920       if (disptype = wf_notification.doc_html) then
1921         -- Bug 4634849
1922         -- Do not display potentially harmful url
1923         begin
1924 	  if (not wf_core.CheckIllegalChars(value,true, ';<>"')) then
1925 
1926             -- 4713416 Determine the display formatting for the URI
1927             -- First validate the prefix to the known types.
1928             renderType := upper(substr(value, 1, 4));
1929             if renderType in ('IMG:','LNK:') then
1930                -- Remove the prefix
1931                value := substr(value, 5);
1932             else
1933                -- Explicitly reset the type so that the file extention will
1934                -- dictate the render type.
1935                renderType := NULL;
1936             end if;
1937 
1938             -- Check the extention of the URI file but only if
1939             -- there are no URL parameters and either a render prefix has
1940             -- been added or no prefix at all.
1941             extPos := instrb(value, '.', -1, 1) + 1;
1942             extStr := lower(substrb(value, extPos));
1943             if (params = 0 and
1944                 ( renderType is null or renderType = 'IMG:' ) and
1945                 extStr in ('gif','jpg','png','tif','bmp','jpeg'))
1946             then
1947                value := '<IMG SRC="'||value||
1948                         '" alt="'|| not_attr_row.display_name||'"></IMG>';
1949                -- Set the renderType used to inform the next condition
1950                renderType := 'IMG:';
1951             else
1952                -- No IMG was rendered so set the type to a normal link.
1953                renderType := 'LNK:';
1954             end if;
1955 
1956             if renderType = 'LNK:' then
1957                -- If no render prefix was given or an explicit LNK: prefix
1958                -- then render as a normal anchor.
1959 
1960                -- For URL type display as an anchor
1961                value := '<A class="OraLink" HREF="'||value||'" TARGET="'||target||'">'||
1962                         not_attr_row.display_name||'</A>';
1963             end if;
1964           end if;
1965         exception
1966           when OTHERS then
1967             wf_core.get_error(error_name, value, error_stack);
1968 
1969             value :=wf_core.substitutespecialchars(value);
1970             error_stack:= '';
1971 
1972         end;
1973       else
1974         -- Other types get a text representation
1975         value := not_attr_row.display_name||' : '||value;
1976       end if;
1977 
1978     elsif (not_attr_row.type = 'DOCUMENT') then
1979       --skilaru 28-July-03 fix for bug 3042471
1980       if( instr(not_attr_row.text_value, fwk_region_start) = 1 ) then
1981         wf_core.token('ANAME', not_attr_row.name );
1982         wf_core.token('FWK_CONTENT', not_attr_row.text_value );
1983         value := wf_core.translate('WFUNSUP_FWK_CONTENT');
1984       else
1985         -- DOCUMENT type retrieve document contents
1986         -- Bug 2879507 if doc generation fails, let the error propagate
1987         -- to the caller
1988         value := GetAttrDoc(nid, not_attr_row.name, disptype);
1989       end if;
1990     elsif (not_attr_row.type = 'ROLE') then
1991       -- ROLE type, get display_name of role
1992       Wf_Directory.GetRoleInfo2(not_attr_row.text_value,role_info_tbl);
1993       -- Use code directly if role not found.
1994       value := nvl(role_info_tbl(1).display_name,not_attr_row.text_value);
1995 
1996       -- Retrieve role information
1997       if (not_attr_row.text_value is not null) then
1998         -- Default role info to recipient if role cannot be found
1999         role_name     := nvl(role_info_tbl(1).display_name,
2000                              not_attr_row.text_value);
2001         email_address := nvl(role_info_tbl(1).email_address,
2002                              not_attr_row.text_value);
2003       end if;
2004 
2005       if (disptype = wf_notification.doc_html) then
2006 
2007          value := '<A class="OraLink" HREF="mailto:'||email_address||'" TARGET="_top">'||value||'</A>';
2008 
2009       end if;
2010 
2011     else
2012       -- All others default to text_value
2013       value := not_attr_row.text_value;
2014 
2015       if (disptype = wf_notification.doc_html) then
2016         value := wf_core.substitutespecialchars(value);
2017       end if;
2018     end if;
2019 
2020     -- Substitute all occurrences of SEND tokens with values.
2021     -- Limit to 32000 chars to avoid value errors if substitution pushes
2022     -- it over the edge.
2023     --
2024     --Bug 2594012/2843136
2025     -- Bug 2917787 - added code to check if value is null.
2026     if ((value is null) or (lengthb(local_text) + (lengthb(value) - length('&'||not_attr_row.name))
2027               <= 32000)) then
2028       local_text := replace(local_text, '&'||not_attr_row.name, value);
2029       local_text := replace(local_text, '&'||not_attr_row.name, value);
2030     end if;
2031    end if;-- if instr(..
2032   end loop;
2033 
2034   --
2035   -- Process special '#' internal tokens.  Supports tokens are:
2036   --  &#NID - Notification id
2037   --
2038   local_text := substrb(replace(local_text, '&'||'#NID',
2039                         to_char(nid)), 1, 32000);
2040   return(local_text);
2041 exception
2042   when others then
2043     wf_core.context('Wf_Notification','GetText', to_char(nid), disptype);
2044     raise;
2045     -- return(some_text);
2046 end GetText;
2047 
2048 --
2049 -- GetUrlText
2050 --   Substitute url-style tokens (with dashes) in an arbitrary text string.
2051 --     This function may return up to 32K chars. It can NOT be used in a view
2052 --   definition or in a Form.  For views and forms, use GetShortText, which
2053 --   truncates values at 2000 chars.
2054 -- IN:
2055 --   some_text - Text to be substituted
2056 --   nid - Notification id of notification to use for token values
2057 -- RETURNS:
2058 --   Some_text with tokens substituted.
2059 -- NOTE:
2060 --   If errors are detected this routine returns some_text untouched
2061 --   instead of raising exceptions.
2062 --
2063 function GetUrlText(some_text in varchar2,
2064                     nid in number)
2065 return varchar2
2066 is
2067   target  varchar2(16);
2068   l_error varchar2(32000);
2069 begin
2070   return(GetTextInternal(some_text, nid, target, TRUE, TRUE, 'text/plain'));
2071 exception
2072   when others then
2073     -- Return the error message with error stack
2074     l_error := wf_core.translate('ERROR') || wf_core.newline;
2075     if (wf_core.error_name is not null) then
2076       l_error := l_error || wf_core.error_message || wf_core.newline;
2077       l_error := l_error || wf_core.translate('WFENG_ERRNAME') || ': ' ||
2078                  wf_core.error_name || wf_core.newline;
2079     else
2080       l_error := l_error || sqlerrm || wf_core.newline;
2081       l_error := l_error || wf_core.translate('WFENG_ERRNAME') || ': ' ||
2082                  to_char(sqlcode) || wf_core.newline;
2083     end if;
2084     l_error := l_error || wf_core.translate('WFENG_ERRSTACK') || ': ' ||
2085                wf_core.error_stack || wf_core.newline;
2086     return (substrb(l_error, 1, 1950));
2087 end GetUrlText;
2088 
2089 --
2090 -- GetShortText
2091 --   Substitute tokens in an arbitrary text string, limited to 2000 chars.
2092 --   (actually 1950, because of forms overhead).
2093 --     This function is meant to be used in view definitions and Forms, where
2094 --   the field size must be limited to 2000 chars.  Use GetText() to retrieve
2095 --   up to 32K if the text may be longer.
2096 -- IN:
2097 --   some_text - Text to be substituted
2098 --   nid - Notification id of notification to use for token values
2099 -- RETURNS:
2100 --   Some_text with tokens substituted.
2101 -- NOTE:
2102 --   If errors are detected this routine returns some_text untouched
2103 --   instead of raising exceptions.
2104 function GetShortText(some_text in varchar2,
2105                       nid in number)
2106 return varchar2
2107 is
2108   target varchar2(16);
2109   l_error varchar2(32000);
2110 
2111 begin
2112   -- gettextinternal will truncate to 1950 characters.
2113   return(GetTextInternal(some_text, nid, target, FALSE, TRUE));
2114 exception
2115   when others then
2116     -- Return the error message with error stack if GetTextInternal raises
2117     l_error := wf_core.translate('ERROR') || wf_core.newline;
2118     if (wf_core.error_name is not null) then
2119       l_error := l_error || wf_core.error_message || wf_core.newline;
2120       l_error := l_error || wf_core.translate('WFENG_ERRNAME') || ': ' ||
2121                  wf_core.error_name || wf_core.newline;
2122     else
2123       l_error := l_error || sqlerrm || wf_core.newline;
2124       l_error := l_error || wf_core.translate('WFENG_ERRNAME') || ': ' ||
2125                  to_char(sqlcode) || wf_core.newline;
2126     end if;
2127     l_error := l_error || wf_core.translate('WFENG_ERRSTACK') || ': ' ||
2128                wf_core.error_stack||wf_core.newline;
2129     return (substrb(l_error, 1, 1950));
2130 
2131 end GetShortText;
2132 
2133 --
2134 -- GetAttrInfo
2135 --   Get type information about a notification attribute.
2136 -- IN:
2137 --   nid - Notification id
2138 --   aname - Attribute name
2139 -- OUT:
2140 --   atype  - Attribute type
2141 --   subtype - 'SEND' or 'RESPOND',
2142 --   format - Attribute format
2143 --
2144 procedure GetAttrInfo(nid in number,
2145                       aname in varchar2,
2146                       atype out nocopy varchar2,
2147                       subtype out nocopy varchar2,
2148                       format out nocopy varchar2)
2149 is
2150 begin
2151   if ((nid is null) or (aname is null)) then
2152     wf_core.token('NID', to_char(nid));
2153     wf_core.token('ANAME', aname);
2154     wf_core.raise('WFSQL_ARGS');
2155   end if;
2156 
2157   begin
2158     select WMA.TYPE, WMA.SUBTYPE, WMA.FORMAT
2159     into   atype, subtype, format
2160     from   WF_NOTIFICATION_ATTRIBUTES WNA, WF_NOTIFICATIONS WN,
2161            WF_MESSAGE_ATTRIBUTES WMA
2162     where  WNA.NOTIFICATION_ID = nid
2163     and    WNA.NAME = aname
2164     and    WNA.NOTIFICATION_ID = WN.NOTIFICATION_ID
2165     and    WN.MESSAGE_TYPE = WMA.MESSAGE_TYPE
2166     and    WN.MESSAGE_NAME = WMA.MESSAGE_NAME
2167     and    WMA.NAME = WNA.NAME;
2168   exception
2169     when no_data_found then
2170       wf_core.token('NID', to_char(nid));
2171       wf_core.token('ATTRIBUTE', aname);
2172       wf_core.raise('WFNTF_ATTR');
2173   end;
2174 
2175 exception
2176   when others then
2177     wf_core.context('Wf_Notification', 'GetAttrInfo', to_char(nid),
2178                     aname);
2179     raise;
2180 end GetAttrInfo;
2181 
2182 --
2183 -- GetAttrText
2184 --   Get the value of a text notification attribute.
2185 --   If the attribute is a NUMBER or DATE type, then translate the
2186 --   number/date value to a text-string representation using attrbute format.
2187 --   For all other types, get the value directly.
2188 -- IN:
2189 --   nid - Notification id
2190 --   aname - Attribute Name
2191 -- RETURNS:
2192 --   Attribute value
2193 --
2194 function GetAttrText (nid in number,
2195                       aname in varchar2,
2196 		      ignore_notfound in boolean)
2197 return varchar2 is
2198   atype varchar2(8);
2199   format varchar2(240);
2200   lvalue varchar2(4000);
2201   params pls_integer;
2202   l_valDate date;
2203 begin
2204   if ((nid is null) or (aname is null)) then
2205     wf_core.token('NID', to_char(nid));
2206     wf_core.token('ANAME', aname);
2207     wf_core.raise('WFSQL_ARGS');
2208   end if;
2209 
2210   -- Get type and format of attr.
2211   -- This is used for translating number/date strings.
2212   begin
2213     select WMA.TYPE, WMA.FORMAT
2214     into atype, format
2215     from WF_NOTIFICATION_ATTRIBUTES WNA, WF_NOTIFICATIONS WN,
2216          WF_MESSAGE_ATTRIBUTES WMA
2217     where WNA.NOTIFICATION_ID = nid
2218     and WNA.NAME = aname
2219     and WNA.NOTIFICATION_ID = WN.NOTIFICATION_ID
2220     and WN.MESSAGE_NAME = WMA.MESSAGE_NAME
2221     and WN.MESSAGE_TYPE = WMA.MESSAGE_TYPE
2222     and WNA.NAME = WMA.NAME;
2223   exception
2224     when no_data_found then
2225       -- This is an unvalidated runtime attr.
2226       -- Treat it as a varchar2.
2227       atype := 'VARCHAR2';
2228       format := '';
2229   end;
2230 
2231   -- Select value from appropriate type column.
2232   begin
2233     if (atype = 'NUMBER') then
2234       select decode(format,
2235                     '', to_char(WNA.NUMBER_VALUE),
2236                     to_char(WNA.NUMBER_VALUE, format))
2237       into   lvalue
2238       from   WF_NOTIFICATION_ATTRIBUTES WNA
2239       where  WNA.NOTIFICATION_ID = nid and WNA.NAME = aname;
2240     elsif (atype = 'DATE') then
2241       -- <bug 7514495> apply format precedence to get date text
2242       select DATE_VALUE into l_valDate
2243       from   WF_NOTIFICATION_ATTRIBUTES WNA
2244       where  WNA.NOTIFICATION_ID = nid and WNA.NAME = aname;
2245 
2246       lvalue := wf_notification_util.GetCalendarDate(nid, l_valDate, format, false);
2247 
2248     else
2249       -- VARCHAR2, LOOKUP, FORM, or URL type.
2250       select WNA.TEXT_VALUE
2251       into   lvalue
2252       from   WF_NOTIFICATION_ATTRIBUTES WNA
2253       where  WNA.NOTIFICATION_ID = nid and WNA.NAME = aname;
2254 
2255       -- Recursively substitute attributes in parameter portion of
2256       -- FORM and URL type attributes.
2257       -- Note a slight chance of infinite recursion here if
2258       -- parameters are defined perversely.
2259       if (atype = 'FORM') then
2260         -- FORM params are after ':'
2261         params := instr(lvalue, ':');
2262         if (params <> 0) then
2263           lvalue := substr(lvalue, 1, params)||
2264                     wf_notification.GetShortText(substr(lvalue,
2265                                                  params+1), nid);
2266         end if;
2267       elsif (atype = 'URL') then
2268         -- URL params are after '?'
2269         params := instr(lvalue, '?');
2270         if (params <> 0) then
2271           lvalue := substr(lvalue, 1, params)||
2272                     wf_notification.GetUrlText(substr(lvalue,
2273                                                params+1), nid);
2274         end if;
2275        end if;
2276     end if;
2277   exception
2278     when no_data_found then
2279       if (ignore_notfound) then
2280          return(null);
2281       else
2282          wf_core.token('NID', to_char(nid));
2283          wf_core.token('ATTRIBUTE', aname);
2284          wf_core.raise('WFNTF_ATTR');
2285       end if;
2286   end;
2287 
2288   return(lvalue);
2289 exception
2290   when others then
2291     wf_core.context('Wf_Notification', 'GetAttrText', to_char(nid), aname);
2292     raise;
2293 end GetAttrText;
2294 
2295 --
2296 -- GetAttrNumber
2297 --   Get the value of a number notification attribute.
2298 --   Attribute must be a NUMBER-type attribute.
2299 -- IN:
2300 --   nid - Notification id
2301 --   aname - Attribute Name
2302 -- RETURNS:
2303 --   Attribute value
2304 --
2305 function GetAttrNumber (nid in number,
2306                         aname in varchar2)
2307 return number is
2308   lvalue number;
2309 begin
2310   if ((nid is null) or (aname is null)) then
2311     wf_core.token('NID', to_char(nid));
2312     wf_core.token('ANAME', aname);
2313     wf_core.raise('WFSQL_ARGS');
2314   end if;
2315 
2316   begin
2317     select WNA.NUMBER_VALUE
2318     into   lvalue
2319     from   WF_NOTIFICATION_ATTRIBUTES WNA
2320     where  WNA.NOTIFICATION_ID = nid and WNA.NAME = aname;
2321   exception
2322     when no_data_found then
2323       wf_core.token('NID', to_char(nid));
2324       wf_core.token('ATTRIBUTE', aname);
2325       wf_core.raise('WFNTF_ATTR');
2326   end;
2327 
2328   return(lvalue);
2329 exception
2330   when others then
2331     wf_core.context('Wf_Notification', 'GetAttrNumber', to_char(nid), aname);
2332     raise;
2333 end GetAttrNumber;
2334 
2335 --
2336 -- GetAttrDate
2337 --   Get the value of a date notification attribute.
2338 --   Attribute must be a DATE-type attribute.
2339 -- IN:
2340 --   nid - Notification id
2341 --   aname - Attribute Name
2342 -- RETURNS:
2343 --   Attribute value
2344 --
2345 function GetAttrDate (nid in number,
2346                       aname in varchar2)
2347 return date is
2348   lvalue date;
2349 begin
2350   if ((nid is null) or (aname is null)) then
2351     wf_core.token('NID', to_char(nid));
2352     wf_core.token('ANAME', aname);
2353     wf_core.raise('WFSQL_ARGS');
2354   end if;
2355 
2356   begin
2357     select WNA.DATE_VALUE
2358     into   lvalue
2359     from   WF_NOTIFICATION_ATTRIBUTES WNA
2360     where  WNA.NOTIFICATION_ID = nid and WNA.NAME = aname;
2361   exception
2362     when no_data_found then
2363       wf_core.token('NID', to_char(nid));
2364       wf_core.token('ATTRIBUTE', aname);
2365       wf_core.raise('WFNTF_ATTR');
2366   end;
2367 
2368   return(lvalue);
2369 exception
2370   when others then
2371     wf_core.context('Wf_Notification', 'GetAttrDate', to_char(nid), aname);
2372     raise;
2373 end GetAttrDate;
2374 
2375 --
2376 --
2377 -- GetAttrDoc
2378 --   Get the displayed value of a DOCUMENT-type attribute.
2379 --   Returns referenced document in format requested.
2380 --   Use GetAttrText to get retrieve the actual attr value (i.e. the
2381 --   document key string instead of the actual document).
2382 -- NOTE:
2383 --   Only PLSQL document type is implemented.
2384 --   This function will call a revised implementation of procedure GetAttrDoc2
2385 --   which will return the document type also.
2386 -- IN:
2387 --   nid - Notification id
2388 --   aname - Attribute Name
2389 --   disptype - Requested display type.  Valid values:
2390 --               wf_notification.doc_text - 'text/plain'
2391 --               wf_notification.doc_html - 'text/html'
2392 -- RETURNS:
2393 --   Referenced document in format requested.
2394 --
2395 function GetAttrDoc(
2396   nid in number,
2397   aname in varchar2,
2398   disptype in varchar2)
2399 return varchar2
2400 is
2401   document varchar2(32000);
2402   doctype  varchar2(255);
2403 begin
2404 
2405   -- call the procedure to get the Document Content and return to the caller.
2406   wf_notification.GetAttrDoc2(nid, aname, disptype, document, doctype);
2407   return (document);
2408 
2409 exception
2410   when others then
2411     wf_core.context('Wf_Notification', 'GetAttrDoc', to_char(nid), aname,
2412         disptype);
2413     raise;
2414 end GetAttrDoc;
2415 
2416 --
2417 -- GetAttrDoc2 - <explained in wfntfs.pls>
2418 --
2419 procedure GetAttrDoc2(
2420   nid      in     number,
2421   aname    in     varchar2,
2422   disptype in     varchar2,
2423   document out nocopy varchar2,
2424   doctype  out nocopy varchar2)
2425 is
2426   key          varchar2(4000);
2427   colon        pls_integer;
2428   slash        pls_integer;
2429   dmstype      varchar2(30);
2430   display_name varchar2(80);
2431   procname     varchar2(240);
2432   launch_url   varchar2(4000);
2433   procarg      varchar2(32000);
2434   username     varchar2(320);
2435   sqlbuf       varchar2(2000);
2436   target       varchar2(240);
2437 		l_charcheck  boolean;
2438 
2439 begin
2440 
2441   -- Check args
2442   if ((nid is null) or (aname is null) or
2443      (disptype not in (wf_notification.doc_text,
2444                        wf_notification.doc_html))) then
2445     wf_core.token('NID', to_char(nid));
2446     wf_core.token('ANAME', aname);
2447     wf_core.token('DISPTYPE', disptype);
2448     wf_core.raise('WFSQL_ARGS');
2449   end if;
2450 
2451   -- Retrieve key string
2452   key := GetAttrText(nid, aname);
2453 
2454   -- If the key is empty then return a null string
2455   if (key is null) then
2456      document := '';
2457      return;
2458   end if;
2459 
2460   -- Parse doc mgmt system type from key
2461   colon := instr(key, ':');
2462   if ((colon <> 0) and (colon < 30)) then
2463     dmstype := upper(substr(key, 1, colon-1));
2464   end if;
2465 
2466   if (dmstype in ('PLSQLCLOB','PLSQLBLOB')) then
2467     document := '&'||aname;
2468     plsql_clob_exists := 1;
2469     return;
2470   elsif (dmstype = 'PLSQL') then
2471     -- Parse out procedure name and arg
2472     slash := instr(key, '/');
2473     if (slash = 0) then
2474       procname := substr(key, colon+1);
2475       procarg := '';
2476     else
2477       procname := substr(key, colon+1, slash-colon-1);
2478       procarg := substr(key, slash+1);
2479     end if;
2480 
2481     -- Dynamic sql call to procedure
2482     if (procarg is null) then
2483        --force a dummy value since no doc id to pass
2484        procarg := NULL;
2485     else
2486        -- Substitute refs to other attributes in argument
2487        -- NOTE: There is a slight chance of recursive loop here,
2488        -- if the substituted string eventually contains a reference
2489        -- back to this same docattr.
2490        procarg := Wf_Notification.GetTextInternal(procarg, nid, target, FALSE,
2491                                                   FALSE, 'text/plain');
2492     end if;
2493 
2494     -- ### Review Note 4
2495 
2496     l_charcheck := wf_notification_util.CheckIllegalChar(procname);
2497     --Throw the Illegal exception when the check fails
2498 
2499       if (wf_log_pkg.level_statement >= fnd_log.g_current_runtime_level) then
2500         wf_log_pkg.string2(wf_log_pkg.level_statement,
2501                           'wf.plsql.wf_notification.GetAttrDoc2.plsqldoc_callout',
2502                           'Start executing PLSQL Doc procedure - '||procname, true);
2503       end if;
2504 
2505       sqlbuf := 'begin '||procname||'(:p1, :p2, :p3, :p4); end;';
2506       execute immediate sqlbuf using
2507        in procarg,
2508        in disptype,
2509        in out document,
2510        in out doctype;
2511 
2512       if (wf_log_pkg.level_statement >= fnd_log.g_current_runtime_level) then
2513         wf_log_pkg.string2(wf_log_pkg.level_statement,
2514                           'wf.plsql.wf_notification.GetAttrDoc2.plsqldoc_callout',
2515                           'End executing PLSQL Doc procedure - '||procname, false);
2516       end if;
2517 
2518 
2519     -- Translate doc types if needed
2520     if ((disptype = wf_notification.doc_html) and
2521         (doctype = wf_notification.doc_text)) then
2522       -- Change plain text to html by wrapping in preformatted tags
2523       document := '<PRE>'||document||'</PRE>';
2524     end if;
2525     return;
2526 
2527   else
2528      /*
2529      ** Get the attribute display name
2530      */
2531      -- Get type and format of attr.
2532      -- This is used for translating number/date strings.
2533      begin
2534        select WMATL.DISPLAY_NAME, NVL(WMA.FORMAT, '_blank')
2535          into display_name, target
2536          from WF_NOTIFICATION_ATTRIBUTES WNA, WF_NOTIFICATIONS WN,
2537               WF_MESSAGE_ATTRIBUTES_TL WMATL, WF_MESSAGE_ATTRIBUTES WMA
2538         where WNA.NOTIFICATION_ID = nid
2539           and WNA.NAME = aname
2540           and WNA.NOTIFICATION_ID = WN.NOTIFICATION_ID
2541           and WN.MESSAGE_NAME = WMATL.MESSAGE_NAME
2542           and WN.MESSAGE_TYPE = WMATL.MESSAGE_TYPE
2543           and WNA.NAME = WMATL.NAME
2544           and WN.MESSAGE_NAME = WMA.MESSAGE_NAME
2545           and WN.MESSAGE_TYPE = WMA.MESSAGE_TYPE
2546           and WNA.NAME = WMA.NAME
2547           and WMATL.LANGUAGE = userenv('LANG');
2548      exception
2549        when no_data_found then
2550          display_name := null;
2551        when others then
2552          raise;
2553      end;
2554 
2555      /*
2556      ** If this is a plain text request then just return the display
2557      ** name for the attribute.  If it is html then get the attachment
2558      ** url link and return it.
2559      */
2560      if (disptype = wf_notification.doc_html) THEN
2561 
2562         -- Returns session user name if available
2563         username := Wfa_Sec.GetUser;
2564 
2565         fnd_document_management.get_launch_document_url (
2566                           username, key, FALSE, launch_url);
2567 
2568         document :=  '<A class="OraLink" HREF="'||launch_url|| '" TARGET="'||target||'">'||
2569                      display_name||'</A>';
2570 
2571      ELSE
2572          document :=  display_name;
2573      END IF;
2574      return;
2575   end if;
2576   document := null;
2577 exception
2578    when others then
2579      wf_core.context('wf_notification', 'GetAttrDoc2', to_char(nid), aname, disptype);
2580      raise;
2581 end GetAttrDoc2;
2582 
2583 -- bug 2581129
2584 -- GetSubject
2585 --   Get subject of notification message with token values substituted
2586 --   from notification attributes. Takes disptype as input.
2587 -- IN:
2588 --   nid - Notification Id
2589 --   disptype - Display Type
2590 -- RETURNS:
2591 --   Substituted message subject
2592 -- NOTE:
2593 --   If errors are detected this routine returns the subject unsubstituted,
2594 --   or null if all else fails, instead of raising exceptions.
2595 --
2596 function GetSubject(
2597   nid      in number,
2598   disptype in varchar2)
2599 return varchar2 is
2600   local_subject varchar2(240);
2601   target        varchar2(16);
2602   l_error       varchar2(32000);
2603 
2604 begin
2605   -- Get subject
2606   select WM.SUBJECT
2607   into local_subject
2608   from WF_NOTIFICATIONS N, WF_MESSAGES_VL WM
2609   where N.NOTIFICATION_ID = nid
2610   and N.MESSAGE_NAME = WM.NAME
2611   and N.MESSAGE_TYPE = WM.TYPE;
2612 
2613   -- Return substituted subject, limited to 240 chars in case
2614   -- tokens exceed length.
2615   -- return(substrb(GetTextInternal(local_subject, nid, target, FALSE,
2616   --                               TRUE, disptype), 1, 240));
2617   -- Allow PLSQL Document attributes within Subject
2618   return(substrb(GetText(local_subject, nid, disptype), 1, 240));
2619 
2620 exception
2621   when others then
2622     -- Return the error message with error stack
2623     l_error := wf_core.translate('ERROR') || wf_core.newline;
2624     if (wf_core.error_name is not null) then
2625       l_error := l_error || wf_core.error_message || wf_core.newline;
2626       l_error := l_error || wf_core.translate('WFENG_ERRNAME') || ': ' ||
2627                  wf_core.error_name || wf_core.newline;
2628     else
2629       l_error := l_error || sqlerrm || wf_core.newline;
2630       l_error := l_error || wf_core.translate('WFENG_ERRNAME') || ': ' ||
2631                  to_char(sqlcode) || wf_core.newline;
2632     end if;
2633     l_error := l_error || wf_core.translate('WFENG_ERRSTACK') || ': ' ||
2634                wf_core.error_stack || wf_core.newline;
2635     return (substrb(l_error, 1, 240));
2636 end GetSubject;
2637 
2638 -- GetSubject
2639 --   Get subject of notification message with token values substituted
2640 --   from notification attributes.
2641 -- IN:
2642 --   nid - Notification Id
2643 -- RETURNS:
2644 --   Substituted message subject
2645 -- NOTE:
2646 --   If errors are detected this routine returns the subject unsubstituted,
2647 --   or null if all else fails, instead of raising exceptions.
2648 --
2649 function GetSubject(nid in number)
2650 return varchar2 is
2651   local_subject varchar2(240);
2652 begin
2653   return (Wf_Notification.GetSubject(nid, 'text/html'));
2654 end GetSubject;
2655 
2656 --
2657 -- GetBody
2658 --   Get body of notification message with token values substituted
2659 --   from notification attributes.
2660 --     This function may return up to 32K chars. It can NOT be used in a view
2661 --   definition or in a Form.  For views and forms, use GetShortBody, which
2662 --   truncates values at 1950 chars.
2663 -- IN:
2664 --   nid - Notification Id
2665 --   disptype - Requested display type.  Valid values:
2666 --               wf_notification.doc_text - 'text/plain'
2667 --               wf_notification.doc_html - 'text/html'
2668 --               wf_notification.doc_attach - ''
2669 -- RETURNS:
2670 --   Substituted message body
2671 -- NOTE:
2672 --   If errors are detected this routine returns the body unsubstituted,
2673 --   or null if all else fails, instead of raising exceptions.
2674 --
2675 function GetBody(
2676   nid in number,
2677   disptype in varchar2)
2678 return varchar2 is
2679   local_body varchar2(32000);
2680   local_html_body varchar2(32000);
2681 
2682   -- To check if Reassign or Request Info is performed on a FYI Notification
2683   CURSOR c_comm IS
2684   SELECT count(1)
2685   FROM   wf_comments
2686   WHERE  action_type in ('REASSIGN', 'QA')
2687   AND    notification_id = nid
2688   AND    rownum = 1;
2689 
2690   l_resp_cnt    number;
2691   l_fyi         boolean;
2692   l_comm_cnt    pls_integer;
2693   l_html_hist   boolean;
2694   l_cust_hist   varchar2(4000);
2695   l_action_hist varchar2(240);
2696 begin
2697   -- Get body
2698   select WM.BODY, WM.HTML_BODY
2699   into local_body, local_html_body
2700   from WF_NOTIFICATIONS N, WF_MESSAGES_VL WM
2701   where N.NOTIFICATION_ID = nid
2702   and N.MESSAGE_NAME = WM.NAME
2703   and N.MESSAGE_TYPE = WM.TYPE;
2704 
2705   -- If user has not used WF_NOTIFICATION(HISTORY) or #HISTORY, append Action History in the
2706   -- notification body by default if this is a...
2707   -- 1. Response required notification
2708   -- 2. FYI notification with at least one Reassign action
2709   -- Query to check if the ntf is FYI or not
2710   SELECT count(1)
2711   INTO   l_resp_cnt
2712   FROM   wf_message_attributes wma,
2713          wf_notifications wn
2714   WHERE  wn.notification_id = nid
2715   AND    wma.message_type = wn.message_type
2716   AND    wma.message_name = wn.message_name
2717   AND    wma.subtype = 'RESPOND'
2718   AND    rownum = 1;
2719 
2720   if (l_resp_cnt = 0) then
2721     l_fyi := true;
2722   else
2723     l_fyi := false;
2724   end if;
2725 
2726   -- If this is FYI, get the count of Reassign and Request Info actions
2727   if (l_fyi) then
2728     l_comm_cnt := 0;
2729     open c_comm;
2730     fetch c_comm into l_comm_cnt;
2731     if (c_comm%notfound) then
2732       l_comm_cnt := 0;
2733     end if;
2734     close c_comm;
2735   end if;
2736 
2737   l_html_hist := false;
2738   if ((l_fyi and l_comm_cnt > 0) or not l_fyi) then
2739     -- According to bug 3612609, if the user just defines #HISTORY, but does not place it in the
2740     -- message body, even then it should be used instead of default WF Action History
2741     begin
2742       l_cust_hist := Wf_Notification.GetAttrText(nid, '#HISTORY');
2743     exception
2744       when others then
2745         l_cust_hist := '';
2746         Wf_Core.Clear;
2747     end;
2748     -- Validate if l_cust_hist has a valid PLSQL doc api attached to it. If it ever has JSP: we
2749     -- would not be here.
2750     if (l_cust_hist is not null and upper(trim(substr(l_cust_hist, 1, 5))) = 'PLSQL') then
2751       l_action_hist := '&#HISTORY';
2752     else
2753       l_action_hist := 'WF_NOTIFICATION(HISTORY)';
2754     end if;
2755 
2756     -- Either a FYI with at least one reassign/Request Info or a Response notification.
2757     -- So, append Action History
2758     if (local_body is not null and instrb(local_body, 'WF_NOTIFICATION(HISTORY)') = 0 and
2759                                    instrb(local_body, '&#HISTORY') = 0) then
2760 
2761       local_body := local_body || Wf_Core.newline || l_action_hist;
2762     end if;
2763 
2764     if (local_html_body is not null and instrb(local_html_body, 'WF_NOTIFICATION(HISTORY)') = 0 and
2765                                         instrb(local_html_body, '&#HISTORY') = 0) then
2766       -- Defer adding history macro until after stripping off BODY tags
2767       l_html_hist := true;
2768     end if;
2769   end if;
2770 
2771   -- Return substituted body.
2772   if (disptype = wf_notification.doc_text) then
2773     local_body := GetText(local_body, nid, disptype);
2774 
2775     -- replace the functions here
2776     local_body := runFuncOnBody(nid, local_body, disptype);
2777 
2778     return(local_body);
2779   else
2780     if (local_html_body is null) then
2781       --use the plain text body but fake it as html by adding <BR>
2782       local_body := substrb(replace(local_body, wf_core.newline,
2783                                     '<BR>'||wf_core.newline),1, 32000);
2784 
2785       -- get the attribute values
2786       local_body := GetText(local_body, nid, disptype);
2787 
2788       -- replace the functions here
2789       local_body := runFuncOnBody(nid, local_body, disptype);
2790 
2791       return(local_body);
2792     else
2793       if instr(upper(local_html_body),'<BODY')>0 then
2794       --strip out the Body tag
2795 
2796       local_html_body:=  substr(local_html_body,
2797                          instr(local_html_body,'>',
2798                                instr(upper(local_html_body),'<BODY'))+1);
2799       end if;
2800 
2801       if instr(upper(local_html_body),'</BODY')>0 then
2802       local_html_body:=  substr(local_html_body,1,
2803                          instr(upper(local_html_body),'</BODY')-1);
2804       end if;
2805 
2806       if (l_html_hist) then
2807         local_html_body := local_html_body || Wf_Core.newline || l_action_hist;
2808       end if;
2809 
2810       local_html_body := GetText(local_html_body, nid, disptype);
2811 
2812       -- replace the functions here
2813       local_html_body := runFuncOnBody(nid, local_html_body, disptype);
2814 
2815       return(local_html_body);
2816     end if;
2817   end if;
2818 
2819 exception
2820   when others then
2821     wf_core.context('Wf_Notification', 'GetBody', to_char(nid), disptype);
2822     raise;
2823 end GetBody;
2824 
2825 --
2826 -- GetShortBody
2827 --   Get body of notification message with token values substituted
2828 --   from notification attributes.
2829 --     This function is meant to be used in view definitions and Forms, where
2830 --   the field size must be limited to 2000 chars.  Use GetBody() to retrieve
2831 --   up to 32K if the text may be longer.
2832 -- IN:
2833 --   nid - Notification Id
2834 -- RETURNS:
2835 --   Substituted message body
2836 -- NOTE:
2837 --   If errors are detected this routine returns the body unsubstituted,
2838 --   or null if all else fails, instead of raising exceptions.  It must do
2839 --   this so the routine can be pragma'd and used in the
2840 --   wf_notifications_view view.
2841 --
2842 function GetShortBody(nid in number)
2843 return varchar2 is
2844   local_body varchar2(4000);
2845   l_error    varchar2(32000);
2846 begin
2847   -- Get body
2848   select WM.BODY
2849   into local_body
2850   from WF_NOTIFICATIONS N, WF_MESSAGES_VL WM
2851   where N.NOTIFICATION_ID = nid
2852   and N.MESSAGE_NAME = WM.NAME
2853   and N.MESSAGE_TYPE = WM.TYPE;
2854 
2855   -- Return substituted body.
2856   -- GetShortText already limits to 1950 chars, so further limit is not needed.
2857   return(GetShortText(local_body, nid));
2858 exception
2859   when others then
2860     -- If there is a failure in GetShortText, the error message is returned
2861         -- Return the error message with error stack
2862     l_error := wf_core.translate('ERROR') || wf_core.newline;
2863     if (wf_core.error_name is not null) then
2864       l_error := l_error || wf_core.error_message || wf_core.newline;
2865       l_error := l_error || wf_core.translate('WFENG_ERRNAME') || ': ' ||
2866                  wf_core.error_name || wf_core.newline;
2867     else
2868       l_error := l_error || sqlerrm || wf_core.newline;
2869       l_error := l_error || wf_core.translate('WFENG_ERRNAME') || ': ' ||
2870                  to_char(sqlcode) || wf_core.newline;
2871     end if;
2872     l_error := l_error || wf_core.translate('WFENG_ERRSTACK') || ': ' ||
2873                wf_core.error_stack || wf_core.newline;
2874     return (substrb(l_error, 1, 1950));
2875 end GetShortBody;
2876 
2877 --
2878 -- GetInfo
2879 --   Return info about notification
2880 -- IN:
2881 --   nid - Notification Id
2882 -- OUT:
2883 --   role - Role notification is sent to
2884 --   message_type - Type flag of message
2885 --   message_name - Message name
2886 --   priority - Notification priority
2887 --   due_date - Due date
2888 --   status - Notification status (OPEN, CLOSED, CANCELED)
2889 --
2890 procedure GetInfo(nid in number,
2891                   role out nocopy varchar2,
2892                   message_type out nocopy varchar2,
2893                   message_name out nocopy varchar2,
2894                   priority out nocopy varchar2,
2895                   due_date out nocopy varchar2,
2896                   status out nocopy varchar2)
2897 is
2898 begin
2899 
2900   begin
2901     select
2902       N.RECIPIENT_ROLE,
2903       N.MESSAGE_TYPE,
2904       N.MESSAGE_NAME,
2905       N.PRIORITY,
2906       N.DUE_DATE,
2907       N.STATUS
2908     into
2909       GetInfo.role,
2910       GetInfo.message_type,
2911       GetInfo.message_name,
2912       GetInfo.priority,
2913       GetInfo.due_date,
2914       GetInfo.status
2915     from WF_NOTIFICATIONS N
2916     where N.NOTIFICATION_ID = nid;
2917   exception
2918     when no_data_found then
2919       wf_core.token('NID', to_char(nid));
2920       wf_core.raise('WFNTF_NID');
2921   end;
2922 
2923 exception
2924   when others then
2925     wf_core.context('Wf_Notification', 'GetInfo', to_char(nid));
2926     raise;
2927 end GetInfo;
2928 
2929 --
2930 -- Responder
2931 --   Return responder of closed notification.
2932 -- IN
2933 --   nid - Notification Id
2934 -- RETURNS
2935 --   Responder to notification.  If no responder was set or notification
2936 --   not yet closed, return null.
2937 --
2938 function Responder(
2939   nid in number)
2940 return varchar2
2941 is
2942   respbuf varchar2(240);
2943 begin
2944   if (nid is null) then
2945     wf_core.token('NID', to_char(nid));
2946     wf_core.raise('WFSQL_ARGS');
2947   end if;
2948 
2949   -- Get responder
2950   begin
2951     select WN.RESPONDER
2952     into respbuf
2953     from WF_NOTIFICATIONS WN
2954     where WN.NOTIFICATION_ID = nid;
2955   exception
2956     when no_data_found then
2957       wf_core.token('NID', to_char(nid));
2958       wf_core.raise('WFNTF_NID');
2959   end;
2960 
2961   return(respbuf);
2962 exception
2963   when others then
2964     Wf_Core.Context('Wf_Notification', 'Responder', to_char(nid));
2965     raise;
2966 end Responder;
2967 
2968 -- AccessCheck
2969 --   Check that the access key is valid for this notification.
2970 -- IN
2971 --   Access string <nid>/<nkey>
2972 -- RETURNS
2973 --   user name (or NULL)
2974 function AccessCheck(access_str in varchar2) return varchar2
2975 is
2976     pos   pls_integer;
2977     nid   pls_integer;
2978     nkey  varchar2(80);
2979     uname varchar2(320);
2980 begin
2981     pos  := instr(access_str, '/');
2982     nid  := to_number(substr(access_str, 1, pos-1));
2983     nkey := substr(access_str, pos+1);
2984 
2985     select recipient_role
2986     into   uname
2987     from   WF_NOTIFICATIONS
2988     where  NOTIFICATION_ID = nid
2989     and    ACCESS_KEY = nkey;
2990 
2991     return uname;
2992 exception
2993     when others then
2994         return NULL;
2995 end AccessCheck;
2996 
2997 --
2998 -- GetMailPreference (PRIVATE)
2999 --   Get the mail preference of a role
3000 -- IN
3001 --   role - role notification being sent to
3002 --   callback - engine callback
3003 --   context -  engine callback context
3004 -- RETURNS
3005 --   mail preference of role
3006 --
3007 function GetMailPreference(
3008   role in varchar2,
3009   callback in varchar2,
3010   context in varchar2)
3011 return varchar2
3012 is
3013   colon pls_integer;
3014   mailpref varchar2(8);
3015 
3016   sqlbuf varchar2(2000);
3017   tvalue varchar2(4000);
3018   nvalue number;
3019   dvalue date;
3020   l_language    varchar2(80);
3021   l_territory   varchar2(80);
3022   l_email       varchar2(320);
3023   l_dname       varchar2(360);
3024 		l_charcheck   boolean;
3025 
3026 begin
3027 
3028   -- ROLE type, get display_name of role
3029   wf_directory.getroleinfo (GetMailPreference.role, l_dname,
3030                             l_email, mailpref,
3031                             l_language, l_territory);
3032 
3033   --
3034   -- Check for the "special" mail suppression item attribute.
3035   -- This attribute is set to the process originator in the Process
3036   -- Navigator, so that the originator doesn't receive mail generated
3037   -- by that process.
3038   --
3039   if (callback is not null) then
3040     -- ### Review Note 3 - private function
3041     l_charcheck := WF_NOTIFICATION_UTIL.CheckIllegalChar(callback);
3042 
3043 
3044        -- BINDVAR_SCAN_IGNORE
3045        sqlbuf := 'begin '||callback||
3046               '(:p1, :p2, :p3, :p4, :p5, :p6, :p7); end;';
3047        begin
3048          execute immediate sqlbuf using
3049           in 'GET',
3050           in context,
3051           in '.MAIL_QUERY',
3052           in 'VARCHAR2',
3053           in out tvalue,
3054           in out nvalue,
3055           in out dvalue;
3056        exception
3057           when others then
3058           -- Ignore cases where no attribute is defined
3059            if (wf_core.error_name = 'WFENG_ITEM_ATTR') then
3060              wf_core.clear;
3061            else
3062              raise;
3063            end if;
3064        end;
3065 
3066        -- We have a match, this is the originator.  No mail for you.
3067        if (tvalue = role) then
3068         mailpref := 'QUERY';
3069        end if;
3070   end if;
3071 
3072   return mailpref;
3073 
3074 exception
3075   when others then
3076     Wf_Core.Context('Wf_Notification', 'GetMailPreference', role);
3077     raise;
3078 end GetMailPreference;
3079 
3080 --
3081 -- Route (PRIVATE)
3082 --   Auto-forward or respond to notification according to routing rules
3083 --   when notification is sent or forwarded.
3084 --   Called from SendSingle and Forward.
3085 -- IN
3086 --   nid - Notification id
3087 --
3088 procedure Route(
3089   nid in number,
3090   cnt in number)
3091 is
3092   recip   varchar2(320);
3093   o_recip varchar2(320);
3094   msgtype varchar2(8);
3095   msgname varchar2(30);
3096 
3097   newcomment varchar2(4000);
3098 
3099   badfwd   exception;          -- bad Forward/Transfer happened
3100   inactive_role exception;     -- Notification not routed if role is inactive
3101   errmsg   varchar2(4000);
3102   dummy    varchar2(4000);
3103   l_hide_reassign varchar(1);
3104 
3105   cursor rulecurs is
3106     select WRR.RULE_ID, WRR.ACTION, WRR.ACTION_ARGUMENT, WRR.RULE_COMMENT
3107     from WF_ROUTING_RULES WRR
3108     where WRR.ROLE = recip
3109     and sysdate between nvl(WRR.BEGIN_DATE, sysdate-1) and
3110                         nvl(WRR.END_DATE, sysdate+1)
3111     and nvl(WRR.MESSAGE_TYPE, msgtype) = msgtype
3112     and nvl(WRR.MESSAGE_NAME, msgname) = msgname
3113     order by WRR.MESSAGE_TYPE, WRR.MESSAGE_NAME;
3114 
3115   rulerec rulecurs%rowtype;
3116 
3117   cursor attrcurs(ruleid in number) is
3118     select WRRA.NAME, WRRA.TEXT_VALUE, WRRA.NUMBER_VALUE, WRRA.DATE_VALUE,
3119            WMA.TYPE
3120     from WF_ROUTING_RULE_ATTRIBUTES WRRA, WF_ROUTING_RULES WRR,
3121          WF_MESSAGE_ATTRIBUTES WMA
3122     where WRRA.RULE_ID = ruleid
3123     and WRRA.RULE_ID = WRR.RULE_ID
3124     and WRR.MESSAGE_TYPE = WMA.MESSAGE_TYPE
3125     and WRR.MESSAGE_NAME = WMA.MESSAGE_NAME
3126     and WRRA.NAME = WMA.NAME;
3127 
3128 begin
3129   -- Get ntf current recipient and message
3130   begin
3131     select WN.RECIPIENT_ROLE, WN.MESSAGE_TYPE, WN.MESSAGE_NAME
3132     into recip, msgtype, msgname
3133     from WF_NOTIFICATIONS WN
3134     where WN.NOTIFICATION_ID = nid;
3135 
3136     o_recip := recip;  -- set original recipient
3137   exception
3138     when no_data_found then
3139       wf_core.token('NID', to_char(nid));
3140       wf_core.raise('WFNTF_NID');
3141   end;
3142 
3143   /* implement the above loop recursively */
3144   if (cnt > wf_notification.max_forward) then
3145     -- it means max_forward must have been exceeded.  Treat as a loop error.
3146     wf_core.token('NID', to_char(nid));
3147     wf_core.raise('WFNTF_ROUTE_LOOP');
3148   end if;
3149 
3150   -- Select one routing rule to execute
3151   open rulecurs;
3152   fetch rulecurs into rulerec;
3153   if (rulecurs%notfound) then
3154     -- No routing rules found - treat like a NOOP
3155     rulerec.action := 'NOOP';
3156     rulerec.rule_comment := '';
3157   end if;
3158   close rulecurs;
3159 
3160   -- If rule has a comment append it to the buffer
3161   -- if (rulerec.rule_comment is not null) then
3162   --  if (newcomment is not null) then
3163   --    newcomment := substrb(newcomment||wf_core.newline, 1, 4000);
3164   --  end if;
3165   --  newcomment := substrb(newcomment||recip||': '||rulerec.rule_comment,
3166   --                        1, 4000);
3167   -- end if;
3168 
3169   -- Check for value in #HIDE_REASSIGN attribute if defined
3170   -- Y: Donot allow Reassign
3171   -- N: Allow Reassign
3172   -- B: Allow Reassign only through Routing Rule
3173   l_hide_reassign := 'N';
3174   begin
3175     l_hide_reassign := Wf_Notification.GetAttrText(nid, '#HIDE_REASSIGN');
3176   exception
3177     when others then
3178       -- Clear the error stack since we ignore the error
3179       Wf_Core.Clear;
3180   end;
3181    -- Bug 7358225: If the recipient role of the routing rule is inactive then update the user_comment
3182    -- for the notification and return without executing the routing rule
3183   if LENGTH(rulerec.action_argument) > 0 then
3184     if Not Wf_Directory.UserActive(rulerec.action_argument) then
3185       raise inactive_role;
3186     end if;
3187   end if;
3188   newcomment := rulerec.rule_comment;
3189 
3190   if (rulerec.action = 'FORWARD' and l_hide_reassign in ('N', 'B')) then
3191     -- FORWARD
3192     -- Set savepoint before doing anything.
3193     -- savepoint fwd_ntf;
3194 
3195     -- Reset recipient and cycle through the loop again to check
3196     -- for another forward.
3197     recip := rulerec.action_argument;
3198 
3199     begin
3200 -- ### implement this in next release
3201 --    Wf_Notification.Forward(nid, recip, newcomment, o_recip, cnt+1);
3202       Wf_Notification.Forward(nid, recip, newcomment, o_recip, cnt+1, 'RULE');
3203     exception
3204       when others then
3205         raise badfwd;
3206     end;
3207   elsif (rulerec.action = 'TRANSFER' and l_hide_reassign in ('N', 'B')) then
3208     -- TRANSFER
3209     -- Set savepoint before doing anything.
3210     -- savepoint fwd_ntf;
3211 
3212     -- Reset recipient and cycle through the loop again to check
3213     -- for another transfer.
3214     recip := rulerec.action_argument;
3215 
3216     begin
3217 -- ### implement this in next release
3218 --    Wf_Notification.Transfer(nid, recip, newcomment, o_recip, cnt+1);
3219       Wf_Notification.Transfer(nid, recip, newcomment, o_recip, cnt+1, 'RULE');
3220     exception
3221       when others then
3222         raise badfwd;
3223     end;
3224   elsif (rulerec.action = 'RESPOND') then
3225     -- RESPOND
3226     -- Query response values for this rule and set attrs accordingly
3227     for respattr in attrcurs(rulerec.rule_id) loop
3228       if (respattr.type = 'NUMBER') then
3229         Wf_Notification.SetAttrNumber(nid, respattr.name,
3230                                       respattr.number_value);
3231       elsif (respattr.type = 'DATE') then
3232         Wf_Notification.SetAttrDate(nid, respattr.name,
3233                                     respattr.date_value);
3234       else -- All other types use text
3235         Wf_Notification.SetAttrText(nid, respattr.name,
3236                                     respattr.text_value);
3237       end if;
3238     end loop;
3239 
3240     -- Complete response
3241     Wf_Notification.Respond(nid, newcomment, recip, 'RULE');
3242   else
3243     -- This must be one of:
3244     --   a. NOOP rule
3245     --   b. No routing rule found
3246     --   c. Unimplemented rule type
3247     -- In any case, just return
3248     return;
3249   end if;
3250   return;
3251 exception
3252   when inactive_role then
3253     begin
3254       update WF_NOTIFICATIONS set
3255         USER_COMMENT = substr(USER_COMMENT||decode(nvl(USER_COMMENT,'T'),
3256                        'T', null, wf_core.newline)||wf_core.translate('INACTIVE_ROLE'), 1, 4000)
3257        where NOTIFICATION_ID = nid;
3258     exception
3259       when others then
3260         wf_core.context('Wf_Notification', 'Route (update comment)',to_char(nid));
3261         raise;
3262     end;
3263   when badfwd then
3264     Wf_Core.Get_Error(dummy, errmsg, dummy);
3265     Wf_Core.Clear;
3266     if (newcomment is not null) then
3267       newcomment := newcomment||wf_core.newline;
3268     end if;
3269     Wf_Core.Token('TO_ROLE', WF_Directory.GetRoleDisplayName(recip));
3270     newcomment := substrb(newcomment||
3271                     Wf_Core.Translate('AUTOROUTE_FAIL')||
3272                     wf_core.newline||errmsg,
3273                     1, 4000);
3274     begin
3275         -- append newcomment to the existing comment.
3276         -- need to add a newline character if user_comment is not null.
3277         update WF_NOTIFICATIONS set
3278           USER_COMMENT = substr(USER_COMMENT||
3279                            decode(nvl(USER_COMMENT,'T'),
3280                                 'T', null, wf_core.newline)||
3281                            Route.newcomment, 1, 4000)
3282          where NOTIFICATION_ID = nid;
3283     exception
3284       when OTHERS then
3285         wf_core.context('Wf_Notification', 'Route (update comment)',
3286                         to_char(nid));
3287         raise;
3288     end;
3289 
3290   when others then
3291     if (rulecurs%isopen) then
3292       close rulecurs;
3293     end if;
3294     wf_core.context('Wf_Notification', 'Route', to_char(nid));
3295     raise;
3296 end Route;
3297 
3298 --
3299 -- First_Execution (Private)
3300 --   Checks if the given item activity is executed for the first time
3301 --   or was executed already
3302 -- IN
3303 --   context - Activity Context
3304 -- RETURN
3305 --   boolean status, TRUE or FALSE
3306 --
3307 function First_Execution(p_context in varchar2)
3308 return boolean
3309 is
3310   l_count     pls_integer;
3311   l_item_type varchar2(8);
3312   l_item_key  varchar2(240);
3313   l_actid     number;
3314 begin
3315 
3316   -- Derive item type, item key and activity id from the context
3317   -- when no ':' or just one ':', context does not conform to WF standard
3318   -- it could be sent by calling wf_notification.send directly
3319   -- in this case, we just return false to preserve the old behavior
3320   validate_context (p_context, l_item_type, l_item_key, l_actid);
3321   if (l_item_type is null or l_item_key is null or l_actid is null) then
3322     return false;
3323   end if;
3324 
3325   -- If a record exists in history table for this item activity, it has already
3326   -- been executed
3327   SELECT count(1)
3328   INTO   l_count
3329   FROM   wf_item_activity_statuses_h
3330   WHERE  item_type = l_item_type
3331   AND    item_key = l_item_key
3332   AND    process_activity = l_actid
3333   AND    rownum = 1;
3334 
3335   if (l_count > 0) then
3336     return false;
3337   end if;
3338   return true;
3339 
3340 exception
3341   when others then
3342     Wf_Core.Context('Wf_Notification', 'First_Execution', p_context);
3343     raise;
3344 end First_Execution;
3345 
3346 -- Denormalize_Columns_Internal(PRIVATE)
3347 -- Custom Columns
3348 procedure denormalize_columns_internal(p_item_key in varchar2,
3349 				       p_user_key in varchar2,
3350 				       p_nid  in number)
3351 is
3352   cursor c_custom_cols is
3353 	  select to_number(
3354                  substr(wnrm.column_name,instr(wnrm.column_name,'UTE')+3)) idx,
3355                  wnrm.column_name,
3356 	         wna.text_value,
3357 		 wna.number_value,
3358 		 wna.date_value
3359 	  from  wf_ntf_rules wnr,
3360 	        wf_ntf_rule_maps wnrm,
3361 		wf_ntf_rule_criteria wnrc,
3362 		wf_notification_attributes wna,
3363 		wf_notifications wn
3364 	  where wnr.rule_name = wnrc.rule_name
3365 	  and   wnrc.message_type = wn.message_type
3366           and   wnr.status = 'ENABLED'
3367 	  and   wnrc.rule_name = wnrm.rule_name
3368 	  and   wnrm.attribute_name = wna.name
3369 	  and   wna.notification_id = wn.notification_id
3370 	  and   wn.notification_id = p_nid
3371 	  order by wnr.phase;
3372 
3373   pta     text_array_t;
3374   pfa     text_array_t;
3375   pua     text_array_t;
3376   pda     date_array_t;
3377   pna     numb_array_t;
3378   ta      text_array_t;
3379   fa      text_array_t;
3380   ua      text_array_t;
3381   da      date_array_t;
3382   na      numb_array_t;
3383 
3384   nd      date;   /* null date */
3385   nn      number; /* null number */
3386 begin
3387 
3388   -- initialize the varrays
3389   pta := text_array_t('','','','','','','','','','');
3390   pfa := text_array_t('','','','','');
3391   pua := text_array_t('','','','','');
3392   pda := date_array_t(nd,nd,nd,nd,nd);
3393   pna := numb_array_t(nn,nn,nn,nn,nn);
3394   ta := text_array_t('','','','','','','','','','');
3395   fa := text_array_t('','','','','');
3396   ua := text_array_t('','','','','');
3397   da := date_array_t(nd,nd,nd,nd,nd);
3398   na := numb_array_t(nn,nn,nn,nn,nn);
3399 
3400   for c in c_custom_cols
3401   loop
3402     if (c.column_name like 'PROTECTED_TEXT%') then
3403       pta(c.idx) := c.text_value;
3404     elsif (c.column_name like 'PROTECTED_FORM%') then
3405       pfa(c.idx) := c.text_value;
3406     elsif (c.column_name like 'PROTECTED_URL%') then
3407       pua(c.idx) := c.text_value;
3408     elsif (c.column_name like 'PROTECTED_DATE%') then
3409       pda(c.idx) := c.date_value;
3410     elsif (c.column_name like 'PROTECTED_NUMBER%') then
3411       pna(c.idx) := c.number_value;
3412     elsif (c.column_name like 'TEXT%') then
3413       ta(c.idx) := c.text_value;
3414     elsif (c.column_name like 'FORM%') then
3415       fa(c.idx) := c.text_value;
3416     elsif (c.column_name like 'URL%') then
3417       ua(c.idx) := c.text_value;
3418     elsif (c.column_name like 'DATE%') then
3419       da(c.idx) := c.date_value;
3420     elsif (c.column_name like 'NUMBER%') then
3421       na(c.idx) := c.number_value;
3422     end if;
3423   end loop;
3424 
3425   update WF_NOTIFICATIONS
3426     set
3427        PROTECTED_TEXT_ATTRIBUTE1  = pta(1)
3428       ,PROTECTED_TEXT_ATTRIBUTE2  = pta(2)
3429       ,PROTECTED_TEXT_ATTRIBUTE3  = pta(3)
3430       ,PROTECTED_TEXT_ATTRIBUTE4  = pta(4)
3431       ,PROTECTED_TEXT_ATTRIBUTE5  = pta(5)
3432       ,PROTECTED_TEXT_ATTRIBUTE6  = pta(6)
3433       ,PROTECTED_TEXT_ATTRIBUTE7  = pta(7)
3434       ,PROTECTED_TEXT_ATTRIBUTE8  = pta(8)
3435       ,PROTECTED_TEXT_ATTRIBUTE9  = pta(9)
3436       ,PROTECTED_TEXT_ATTRIBUTE10 = pta(10)
3437       ,PROTECTED_FORM_ATTRIBUTE1  = pfa(1)
3438       ,PROTECTED_FORM_ATTRIBUTE2  = pfa(2)
3439       ,PROTECTED_FORM_ATTRIBUTE3  = pfa(3)
3440       ,PROTECTED_FORM_ATTRIBUTE4  = pfa(4)
3441       ,PROTECTED_FORM_ATTRIBUTE5  = pfa(5)
3442       ,PROTECTED_URL_ATTRIBUTE1   = pua(1)
3443       ,PROTECTED_URL_ATTRIBUTE2   = pua(2)
3444       ,PROTECTED_URL_ATTRIBUTE3   = pua(3)
3445       ,PROTECTED_URL_ATTRIBUTE4   = pua(4)
3446       ,PROTECTED_URL_ATTRIBUTE5   = pua(5)
3447       ,PROTECTED_DATE_ATTRIBUTE1  = pda(1)
3448       ,PROTECTED_DATE_ATTRIBUTE2  = pda(2)
3449       ,PROTECTED_DATE_ATTRIBUTE3  = pda(3)
3450       ,PROTECTED_DATE_ATTRIBUTE4  = pda(4)
3451       ,PROTECTED_DATE_ATTRIBUTE5  = pda(5)
3452       ,PROTECTED_NUMBER_ATTRIBUTE1= pna(1)
3453       ,PROTECTED_NUMBER_ATTRIBUTE2= pna(2)
3454       ,PROTECTED_NUMBER_ATTRIBUTE3= pna(3)
3455       ,PROTECTED_NUMBER_ATTRIBUTE4= pna(4)
3456       ,PROTECTED_NUMBER_ATTRIBUTE5= pna(5)
3457       ,TEXT_ATTRIBUTE1  = ta(1)
3458       ,TEXT_ATTRIBUTE2  = ta(2)
3459       ,TEXT_ATTRIBUTE3  = ta(3)
3460       ,TEXT_ATTRIBUTE4  = ta(4)
3461       ,TEXT_ATTRIBUTE5  = ta(5)
3462       ,TEXT_ATTRIBUTE6  = ta(6)
3463       ,TEXT_ATTRIBUTE7  = ta(7)
3464       ,TEXT_ATTRIBUTE8  = ta(8)
3465       ,TEXT_ATTRIBUTE9  = ta(9)
3466       ,TEXT_ATTRIBUTE10 = ta(10)
3467       ,FORM_ATTRIBUTE1  = fa(1)
3468       ,FORM_ATTRIBUTE2  = fa(2)
3469       ,FORM_ATTRIBUTE3  = fa(3)
3470       ,FORM_ATTRIBUTE4  = fa(4)
3471       ,FORM_ATTRIBUTE5  = fa(5)
3472       ,URL_ATTRIBUTE1   = ua(1)
3473       ,URL_ATTRIBUTE2   = ua(2)
3474       ,URL_ATTRIBUTE3   = ua(3)
3475       ,URL_ATTRIBUTE4   = ua(4)
3476       ,URL_ATTRIBUTE5   = ua(5)
3477       ,DATE_ATTRIBUTE1  = da(1)
3478       ,DATE_ATTRIBUTE2  = da(2)
3479       ,DATE_ATTRIBUTE3  = da(3)
3480       ,DATE_ATTRIBUTE4  = da(4)
3481       ,DATE_ATTRIBUTE5  = da(5)
3482       ,NUMBER_ATTRIBUTE1= na(1)
3483       ,NUMBER_ATTRIBUTE2= na(2)
3484       ,NUMBER_ATTRIBUTE3= na(3)
3485       ,NUMBER_ATTRIBUTE4= na(4)
3486       ,NUMBER_ATTRIBUTE5= na(5)
3487       ,ITEM_KEY = p_item_key
3488       ,USER_KEY = p_user_key
3489       where notification_id = p_nid;
3490 
3491  exception
3492   when OTHERS then
3493     wf_core.context('Wf_Notification', 'denormalize_columns_internal',
3494 		     p_item_key, p_user_key, to_char(p_nid));
3495     raise;
3496 end Denormalize_columns_internal;
3497 
3498 --
3499 -- Denormalize Custom columns(PUBLIC)
3500 -- Called by FNDWFDCC concurrent program
3501 --
3502 procedure denormalizeColsConcurrent(retcode      out nocopy varchar2,
3503   			            errbuf       out nocopy varchar2,
3504 			            p_item_type  in varchar2,
3505 			            p_status     in varchar2,
3506 				    p_recipient  in varchar2)
3507 is
3508    cursor c_notifications is
3509      select wi.item_key, wi.user_key, wn.notification_id
3510      from  wf_items wi,
3511            wf_item_activity_statuses wias,
3512            wf_notifications wn
3513      where wi.item_key = wias.item_key
3514      and   wi.item_type = wias.item_type
3515      and   wias.notification_id = wn.group_id
3516      and   (wn.message_type = p_item_type or p_item_type is null)
3517      and   wn.status =  nvl(p_status, 'OPEN')
3518      and   (wn.recipient_role = p_recipient or p_recipient is null)
3519      order by wi.item_type;
3520 
3521   errname varchar2(30);
3522   errmsg varchar2(2000);
3523   errstack varchar2(4000);
3524 begin
3525 
3526   for v_ntf in c_notifications
3527   loop
3528        denormalize_columns_internal(v_ntf.item_key, v_ntf.user_key, v_ntf.notification_id);
3529   end loop;
3530 
3531   -- Return 0 for successful completion.
3532   errbuf := '';
3533   retcode := '0';
3534 
3535 exception
3536   when others then
3537     -- Retrieve error message into errbuf
3538     wf_core.get_error(errname, errmsg, errstack);
3539     if (errmsg is not null) then
3540       errbuf := errmsg;
3541     else
3542       errbuf := sqlerrm;
3543     end if;
3544 
3545     -- Return 2 for error.
3546     retcode := '2';
3547 end denormalizeColsConcurrent;
3548 
3549 --
3550 -- SendSingle (PRIVATE)
3551 --   Send a single notification.
3552 --   Called by Send and SendGroup public functions.
3553 --   Argument error checking should be done by Send and SendGroup before
3554 --   calling this function.
3555 -- IN:
3556 --   role - Role to send notification to
3557 --   msg_type - Message type
3558 --   msg_name - Message name
3559 --   due_date - Date due
3560 --   callback - Callback function
3561 --   context - Data for callback
3562 --   send_comment - Comment to add to notification
3563 --   priority - Notification priority
3564 --   group_id - Id of notification group
3565 --              (If null use not_id of notification sent)
3566 -- RETURNS:
3567 --   Notification id
3568 --
3569 function SendSingle(role in varchar2,
3570                     msg_type in varchar2,
3571                     msg_name in varchar2,
3572                     due_date in date,
3573                     callback in varchar2,
3574                     context in varchar2,
3575                     send_comment in varchar2,
3576                     priority in number,
3577                     group_id in number)
3578 return number is
3579   mailpref     varchar2(8);
3580   nid          pls_integer;
3581   attr_name    varchar2(30);
3582   attr_type    varchar2(8);
3583   attr_tvalue  varchar2(4000);
3584   attr_nvalue  number;
3585   attr_dvalue  date;
3586   --  Bug 2376033
3587   attr_evalue  wf_event_t;
3588   -- Bug 2283697
3589   l_parameterlist wf_parameter_list_t := wf_parameter_list_t();
3590   -- the following variables for Dynamic SQL
3591   sqlbuf         varchar2(2000);
3592   l_from_role    varchar2(320);
3593   l_send_comment varchar2(4000);
3594   l_send_source  varchar2(30);
3595   l_charcheck    boolean;
3596   -- Custom columns fix
3597   l_itemkey      varchar2(240);
3598   l_userkey      varchar2(240);
3599   l_itemtype     varchar2(8);
3600   col1           pls_integer;
3601   col2           pls_integer;
3602   l_language     varchar2(30);
3603   role_info_tbl  wf_directory.wf_local_roles_tbl_type;
3604 
3605   cursor message_attrs_cursor(msg_type varchar2, msg_name varchar2) is
3606     select NAME, TYPE, SUBTYPE, VALUE_TYPE,
3607            TEXT_DEFAULT, NUMBER_DEFAULT, DATE_DEFAULT
3608     from WF_MESSAGE_ATTRIBUTES
3609     where MESSAGE_TYPE = msg_type
3610     and MESSAGE_NAME = msg_name;
3611 begin
3612   -- Check role is valid and get mail preference
3613   mailpref := Wf_Notification.GetMailPreference(role, callback, context);
3614 
3615   -- Create new nid and insert notification
3616   select WF_NOTIFICATIONS_S.NEXTVAL
3617   into nid
3618   from SYS.DUAL;
3619 
3620   insert into WF_NOTIFICATIONS (
3621     NOTIFICATION_ID,
3622     GROUP_ID,
3623     MESSAGE_TYPE,
3624     MESSAGE_NAME,
3625     RECIPIENT_ROLE,
3626     ORIGINAL_RECIPIENT,
3627     STATUS,
3628     ACCESS_KEY,
3629     MAIL_STATUS,
3630     PRIORITY,
3631     BEGIN_DATE,
3632     END_DATE,
3633     DUE_DATE,
3634     -- USER_COMMENT,
3635     CALLBACK,
3636     CONTEXT
3637   ) select
3638     sendsingle.nid,
3639     nvl(sendsingle.group_id, sendsingle.nid),
3640     sendsingle.msg_type,
3641     sendsingle.msg_name,
3642     sendsingle.role,
3643     sendsingle.role,
3644     'OPEN',
3645     wf_core.random,
3646     decode(sendsingle.mailpref, 'QUERY', '',
3647                                 'SUMMARY', '',
3648                                 'SUMHTML', '',
3649                                 'DISABLED', 'FAILED',
3650                                 null, '', 'MAIL'),
3651     nvl(SendSingle.priority, WM.DEFAULT_PRIORITY),
3652     sysdate,
3653     null,
3654     sendsingle.due_date,
3655     -- sendsingle.send_comment,
3656     sendsingle.callback,
3657     sendsingle.context
3658   from WF_MESSAGES WM
3659   where WM.TYPE = sendsingle.msg_type
3660   and WM.NAME = sendsingle.msg_name;
3661 
3662   -- Open and parse cursor for dynamic sql for getting attr values
3663   -- Bug 2376033 added event value in call to CB
3664   if (callback is not null) then
3665      -- ### Review Note 2
3666      l_charcheck := wf_notification_util.CheckIllegalChar(callback);
3667      --Throw the Illegal exception when the check fails
3668 
3669 
3670      -- BINDVAR_SCAN_IGNORE
3671       sqlbuf := 'begin '||callback||
3672                '(:p1, :p2, :p3, :p4, :p5, :p6, :p7, :p8); end;';
3673 
3674   end if;
3675 
3676   --
3677   -- Get and insert notification attributes
3678   --
3679   for message_attr_row in message_attrs_cursor(msg_type, msg_name) loop
3680 
3681      attr_name := message_attr_row.name;
3682      attr_type := message_attr_row.type;
3683 
3684      -- Set up default values for attributes
3685      if (message_attr_row.value_type = 'CONSTANT') then
3686        -- Constant default values used directly
3687        attr_tvalue := message_attr_row.text_default;
3688        attr_nvalue := message_attr_row.number_default;
3689        attr_dvalue := message_attr_row.date_default;
3690      else
3691        -- Defaults to be fetched from cb - default to null
3692        attr_tvalue := '';
3693        attr_nvalue := '';
3694        attr_dvalue := '';
3695      end if;
3696 
3697      -- Bug 2376033 initialize event to fetch value using cb
3698      if (attr_type = 'EVENT') then
3699        wf_event_t.initialize(attr_evalue);
3700      end if;
3701 
3702      -- If there is a cb defined and the default vtype is ITEMATTR
3703      -- then call the cb to fetch possible item attribute value.
3704      -- Bug 2376033 execute call to CB with event value
3705      if ((callback is not null) and
3706          (message_attr_row.value_type = 'ITEMATTR')) then
3707        begin
3708          execute immediate sqlbuf using
3709            in 'GET',
3710            in context,
3711            in message_attr_row.text_default,
3712            in attr_type,
3713            in out attr_tvalue,
3714            in out attr_nvalue,
3715            in out attr_dvalue,
3716            in out attr_evalue;
3717 
3718        exception
3719          when others then
3720            -- Ignore cases where no attribute is defined
3721           if (wf_core.error_name = 'WFENG_ITEM_ATTR') then
3722              wf_core.clear;
3723           else
3724              -- Bug 2580807 call with original signature for backward
3725              -- compatibility
3726              -- ### Review Note 2 - callback is from table
3727              l_charcheck := wf_notification_util.CheckIllegalChar(callback);
3728              --Throw the Illegal exception when the check fails
3729 
3730 
3731                -- BINDVAR_SCAN_IGNORE
3732                sqlbuf := 'begin '||callback||
3733                        '(:p1, :p2, :p3, :p4, :p5, :p6, :p7); end;';
3734                begin
3735                 execute immediate sqlbuf using
3736                   in 'GET',
3737                   in context,
3738                   in message_attr_row.text_default,
3739                   in attr_type,
3740                   in out attr_tvalue,
3741                   in out attr_nvalue,
3742                   in out attr_dvalue;
3743                exception
3744                 when others then
3745                   if (wf_core.error_name = 'WFENG_ITEM_ATTR') then
3746                     wf_core.clear;
3747                   else
3748                     raise;
3749                   end if;
3750                end;
3751           end if;
3752        end;
3753      end if;
3754 
3755      --
3756      -- Insert notification attribute
3757      -- Bug 2376033 insert the event value
3758      --
3759      insert into WF_NOTIFICATION_ATTRIBUTES  (
3760        NOTIFICATION_ID,
3761        NAME,
3762        TEXT_VALUE,
3763        NUMBER_VALUE,
3764        DATE_VALUE,
3765        EVENT_VALUE
3766      ) values (
3767        nid,
3768        attr_name,
3769        attr_tvalue,
3770        attr_nvalue,
3771        attr_dvalue,
3772        attr_evalue
3773      );
3774   end loop;
3775 
3776   l_send_source := '';
3777   -- Notification sender comment
3778   if (send_comment is not null) then
3779     l_send_comment := send_comment;
3780   else
3781     -- Look for the sender comment in #SUBMIT_COMMENTS and store it
3782     -- only for the first time
3783     if (First_Execution(context)) then
3784       l_send_source := 'FIRST';
3785       begin
3786         l_send_comment := Wf_Notification.GetAttrText(nid, '#SUBMIT_COMMENTS');
3787       exception
3788         when others then
3789           if(Wf_Core.Error_Name = 'WFNTF_ATTR') then
3790              Wf_Core.Clear();
3791              l_send_comment := '';
3792           else
3793              raise;
3794           end if;
3795        end;
3796     end if;
3797   end if;
3798 
3799   -- If #FROM_ROLE is defined, we will get the value in this attribute
3800   begin
3801     l_from_role := Wf_Notification.GetAttrText(nid, '#FROM_ROLE');
3802   exception
3803     when OTHERS then
3804       wf_core.clear;
3805       -- Check if the notification is sent under a valid Fwk Session
3806       l_from_role := Wfa_Sec.GetUser();
3807   end;
3808 
3809   -- Use dummy user WF_SYSTEM as last resort
3810   if (l_from_role is null) then
3811      l_from_role := 'WF_SYSTEM';
3812   end if;
3813   Wf_Notification.SetComments(nid, l_from_role, role, 'SEND', l_send_source, l_send_comment);
3814 
3815   -- Check for auto-routing of notification just sent
3816   Wf_Notification.Route(nid, 0);
3817 
3818   -- Denormalize the Notification after auto-routing is done
3819   Wf_Notification.Denormalize_Notification(nid);
3820 
3821   -- Denormalize custom columns
3822   -- Derive item type, item key and activity id from the context
3823   if context is not null then
3824       col1 := instr(context, ':', 1, 1);
3825       col2 := instr(context, ':', -1, 1);
3826 
3827       l_itemtype := substr(substr(context, 1, col1-1),1,8);
3828       l_itemkey  := substr(substr(context, col1+1, col2-col1-1),1,240);
3829       begin
3830          l_userkey  := wf_engine.GetItemUserKey(l_itemtype,l_itemkey);
3831       exception
3832          when others then
3833             l_userkey := null;
3834       end;
3835   else
3836       l_itemkey := null;
3837       l_userkey := null;
3838   end if;
3839   wf_notification.denormalize_columns_internal(l_itemkey, l_userkey, nid);
3840 
3841 
3842   -- DL: Move this to be the last step before returning the nid
3843   --     The recipient_role could be updated during auto-routing
3844   --     EnqueueNotification maybe able to take advantage of
3845   --     denormalization in the future.
3846   -- Push the notification to the outbound queue
3847   -- Enqueuing has been moved to a subscription for forward
3848   -- compatability. The subscription need only be enabled to use
3849   -- the older mailer. The subscription will call the
3850   -- wf_xml.enqueueNotification API.
3851   -- wf_xml.EnqueueNotification(nid);
3852 
3853   --Bug 2283697
3854   --To raise an EVENT whenever DML operation is performed on
3855   --WF_NOTIFICATIONS and WF_NOTIFICATION_ATTRIBUTES table.
3856   wf_event.AddParameterToList('NOTIFICATION_ID',nid,l_parameterlist);
3857   wf_event.AddParameterToList('ROLE',role,l_parameterlist);
3858   wf_event.AddParameterToList('GROUP_ID',nvl(group_id,nid),l_parameterlist);
3859 
3860   wf_event.addParameterToList('Q_CORRELATION_ID', sendsingle.msg_type||':'||
3861                               sendsingle.msg_name, l_parameterlist);
3862 
3863   Wf_Directory.GetRoleInfo2(sendsingle.role, role_info_tbl);
3864   l_language := role_info_tbl(1).language;
3865 
3866   select code into l_language from wf_languages where nls_language = l_language;
3867 
3868   -- AppSearch
3869   wf_event.AddParameterToList('OBJECT_NAME',
3870   'oracle.apps.fnd.wf.worklist.server.AllNotificationsVO', l_parameterlist);
3871   wf_event.AddParameterToList('CHANGE_TYPE', 'INSERT',l_parameterlist);
3872   wf_event.AddParameterToList('ID_TYPE', 'PK', l_parameterlist);
3873   wf_event.addParameterToList('PK_NAME_1', 'NOTIFICATION_ID',l_parameterlist);
3874   wf_event.addParameterToList('PK_VALUE_1', nid, l_parameterlist);
3875   wf_event.addParameterToList('PK_NAME_2', 'LANGUAGE',l_parameterlist);
3876   wf_event.addParameterToList('PK_VALUE_2', l_language, l_parameterlist);
3877 
3878 
3879 
3880   --Raise the event
3881   if wf_event.phase_maxthreshold is null then
3882     -- means deferred mode , avoid synchronous processing of Send event
3883     wf_event.SetDispatchMode('ASYNC');
3884 
3885     wf_event.Raise(p_event_name => 'oracle.apps.wf.notification.send',
3886                  p_event_key  => to_char(nid),
3887                  p_parameters => l_parameterlist);
3888 
3889     wf_event.phase_maxthreshold := null;
3890   else
3891     wf_event.Raise(p_event_name => 'oracle.apps.wf.notification.send',
3892                  p_event_key  => to_char(nid),
3893                  p_parameters => l_parameterlist);
3894 
3895   end if;
3896 
3897   return (nid);
3898 
3899 exception
3900   when others then
3901     wf_core.context('Wf_Notification', 'SendSingle', role, msg_type,
3902                     msg_name, due_date, callback);
3903     raise;
3904 end SendSingle;
3905 
3906 --
3907 -- Send
3908 --   Send the role the specified message.
3909 --   Insert a single notification in the notifications table, and set
3910 --   the default send and respond attributes for the notification.
3911 -- IN:
3912 --   role - Role to send notification to
3913 --   msg_type - Message type
3914 --   msg_name - Message name
3915 --   due_date - Date due
3916 --   callback - Callback function
3917 --   context - Data for callback
3918 --   send_comment - Comment to add to notification
3919 --   priority - Notification priority
3920 -- RETURNS:
3921 --   Notification ID
3922 --
3923 function Send(role in varchar2,
3924               msg_type in varchar2,
3925               msg_name in varchar2,
3926               due_date in date,
3927               callback in varchar2,
3928               context in varchar2,
3929               send_comment in varchar2,
3930               priority in number)
3931 return number is
3932   dummy pls_integer;
3933   nid pls_integer;
3934   rorig_system varchar2(30);
3935   rorig_system_id pls_integer;
3936   --Bug 4050078
3937   itemtype        varchar2(8);
3938   itemkey         varchar2(240);
3939   actid           number;
3940   col1            pls_integer;
3941   col2            pls_integer;
3942   prev_nid        pls_integer;
3943 begin
3944   if ((role is null) or (msg_type is null) or (msg_name is null)) then
3945     wf_core.token('ROLE', role);
3946     wf_core.token('TYPE', msg_type);
3947     wf_core.token('NAME', msg_name);
3948     wf_core.raise('WFSQL_ARGS');
3949   end if;
3950 
3951   -- Check message is valid
3952   begin
3953     select 1 into dummy from sys.dual where exists
3954     (select null
3955     from WF_MESSAGES M
3956     where M.TYPE = msg_type
3957     and M.NAME = msg_name);
3958   exception
3959     when no_data_found then
3960       wf_core.token('TYPE', msg_type);
3961       wf_core.token('NAME', msg_name);
3962       wf_core.raise('WFNTF_MESSAGE');
3963   end;
3964 
3965   Wf_Directory.GetRoleOrigSysInfo(role,rorig_system,rorig_system_id);
3966 
3967   -- if ORIG_SYSTEM is null, there is no data found for this role
3968   if (rorig_system is null) then
3969     wf_core.token('ROLE', role);
3970     wf_core.raise('WFNTF_ROLE');
3971   end if;
3972 
3973   -- Call SendSingle to complete notification,
3974   -- using group_id = null to create group of one.
3975   nid := SendSingle(role, msg_type, msg_name, due_date, callback,
3976                     context, send_comment, priority, null);
3977 
3978   -- Update action history of notifications in approval chain
3979   -- Bug 4050078
3980   begin
3981      -- Derive item type, item key and activity id from the context
3982      validate_context(context, itemtype, itemkey, actid);
3983      -- Bug 7914921. The context not always comes in format itemtype:itemkey:actid
3984       if (itemtype is not null AND itemkey is not null AND actid is not null) then
3985         SELECT wn.notification_id
3986         INTO   prev_nid
3987         FROM  wf_notifications wn,
3988             wf_comments wc
3989         WHERE
3990            EXISTS ( SELECT /*+ NO_UNNEST */ 'x'
3991                  FROM wf_item_activity_statuses_h wiash
3992                  WHERE  wiash.notification_id= wn.notification_id
3993                  AND    wiash.item_type = wn.message_type
3994                  AND    wiash.item_type = itemtype
3995                  AND    wiash.item_key = itemkey
3996                  AND    wiash.process_activity = actid)
3997         AND  wn.status = 'CLOSED'
3998         AND  wn.notification_id = wc.notification_id
3999         AND  wc.to_role = 'WF_SYSTEM'
4000         AND  wc.action_type = 'RESPOND';
4001 
4002         UPDATE wf_comments
4003         SET to_role = role,
4004             to_user = nvl(Wf_Directory.GetRoleDisplayname(role), role)
4005         WHERE notification_id = prev_nid
4006         AND  to_role = 'WF_SYSTEM'
4007         AND  action_type = 'RESPOND';
4008       end if;
4009   exception
4010      when no_data_found then
4011         null;
4012   end;
4013 
4014   return (nid);
4015 exception
4016   when others then
4017     wf_core.context('Wf_Notification', 'Send', role, msg_type,
4018                     msg_name, due_date, callback);
4019     raise;
4020 end Send;
4021 
4022 --
4023 -- SendGroup
4024 --   Send the role users the specified message.
4025 --   Send a separate notification to every user assigned to the role.
4026 -- IN:
4027 --   role - Role of users to send notification to
4028 --   msg_type - Message type
4029 --   msg_name - Message name
4030 --   due_date - Date due
4031 --   callback - Callback function
4032 --   context - Data for callback
4033 --   send_comment - Comment to add to notification
4034 --   priority - Notification priority
4035 -- RETURNS:
4036 --   Group ID - Id of notification group
4037 --
4038 function SendGroup(role in varchar2,
4039                    msg_type in varchar2,
4040                    msg_name in varchar2,
4041                    due_date in date,
4042                    callback in varchar2,
4043                    context in varchar2,
4044                    send_comment in varchar2,
4045                    priority in number)
4046 return number is
4047   dummy pls_integer;
4048   nid pls_integer;
4049   gid pls_integer;
4050 
4051   rorig_system varchar2(30);
4052   rorig_system_id pls_integer;
4053 
4054   cursor role_users_curs is
4055     select WUR.USER_NAME
4056     from WF_USER_ROLES WUR
4057     where WUR.ROLE_ORIG_SYSTEM = rorig_system
4058     and WUR.ROLE_ORIG_SYSTEM_ID = rorig_system_id
4059     and WUR.ROLE_NAME = role;
4060 
4061 begin
4062   if ((role is null) or (msg_type is null) or (msg_name is null)) then
4063     wf_core.token('ROLE', role);
4064     wf_core.token('TYPE', msg_type);
4065     wf_core.token('NAME', msg_name);
4066     wf_core.raise('WFSQL_ARGS');
4067   end if;
4068 
4069   -- Check message is valid
4070   begin
4071     select 1 into dummy from sys.dual where exists
4072     (select null
4073     from WF_MESSAGES M
4074     where M.TYPE = msg_type
4075     and M.NAME = msg_name);
4076   exception
4077     when no_data_found then
4078       wf_core.token('TYPE', msg_type);
4079       wf_core.token('NAME', msg_name);
4080       wf_core.raise('WFNTF_MESSAGE');
4081   end;
4082 
4083   -- Get the orig system ids for the role.
4084   -- Do this instead of using role_name directly so that indexes on
4085   -- the original tables are used when selecting through the view.
4086   Wf_Directory.GetRoleOrigSysInfo(role,rorig_system,rorig_system_id);
4087 
4088   -- if ORIG_SYSTEM is null, there is no data found for this role
4089   if (rorig_system is null) then
4090     wf_core.token('ROLE', role);
4091     wf_core.raise('WFNTF_ROLE');
4092   end if;
4093 
4094   -- Loop through users of role, sending notification to each one.
4095   gid := '';
4096   for user in role_users_curs loop
4097     -- Send Notification to only active users - Bug 7413050
4098     if Wf_Directory.UserActive(user.user_name) then
4099       -- Call SendSingle to complete notification,
4100       nid := SendSingle(user.user_name, msg_type, msg_name, due_date, callback,
4101                       context, send_comment, priority, gid);
4102 
4103       -- Use nid of the first notification as group id for the rest.
4104       if (gid is null) then
4105         gid := nid;
4106       end if;
4107     end if;
4108   end loop;
4109 
4110   -- Raise error if no users found for role.
4111   -- Most probable cause is role argument is invalid.
4112   if (gid is null) then
4113     wf_core.token('ROLE', role);
4114     wf_core.raise('WFNTF_ROLE');
4115   end if;
4116 
4117   return (gid);
4118 exception
4119   when others then
4120     wf_core.context('Wf_Notification', 'SendGroup', role, msg_type,
4121                     msg_name, due_date, callback);
4122     raise;
4123 end SendGroup;
4124 
4125 --
4126 -- ForwardInternal (PRIVATE)
4127 --   Forward a notification, identified by NID to another user. Validate
4128 --   the user and Return error messages ...
4129 --   Depend on which mode 'FORWARD' or 'TRANSFER', it calls the call-back
4130 --   function differently.
4131 -- IN:
4132 --   nid - Notification Id
4133 --   new_role - Role to forward notification to
4134 --   fmode - Callback mode: 'FORWARD', 'TRANSFER'
4135 --   forward_comment - comment to append to notification
4136 --   user - role who perform this action if provided
4137 --   cnt - count for recursive purpose
4138 --   action_source - Source from where the action is performed
4139 --
4140 procedure ForwardInternal(
4141   nid in number,
4142   new_role in varchar2,
4143   fmode in varchar2,
4144   forward_comment in varchar2,
4145   user in varchar2,
4146   cnt in number,
4147   action_source in varchar2)
4148 is
4149   mailpref varchar2(8);
4150   newcomment varchar2(4000);
4151   old_role varchar2(320);
4152   old_origrole varchar2(320);
4153   status varchar2(8);
4154   cb varchar2(240);
4155   context varchar2(2000);
4156   sqlbuf varchar2(2000);
4157   tvalue varchar2(4000);
4158   nvalue number;
4159   dvalue date;
4160 
4161 
4162   -- Bug 2331070
4163   l_from_role varchar2(320);
4164 
4165   --Bug 2283697
4166   l_parameterlist        wf_parameter_list_t := wf_parameter_list_t();
4167 
4168   --Bug 2474770
4169   l_more_info_role       VARCHAR2(320);
4170   l_dispname     VARCHAR2(360);
4171   l_username     varchar2(320);
4172   l_found        boolean;
4173   l_dummy        varchar2(1);
4174 
4175   l_language	varchar2(30);
4176   l_recipient_role varchar2(320);
4177   role_info_tbl wf_directory.wf_local_roles_tbl_type;
4178 
4179   -- Bug 3827935
4180   l_charcheck   boolean;
4181 
4182   -- <bug 7641725>
4183   l_msgType varchar2(8);
4184   l_msgName varchar2(30);
4185 
4186 begin
4187   if ((nid is null) or (new_role is null)) then
4188     wf_core.token('NID', to_char(nid));
4189     wf_core.token('NEW_ROLE', new_role);
4190     wf_core.raise('WFSQL_ARGS');
4191   end if;
4192 
4193   -- Check the notification exists and is open
4194   begin
4195     --Bug 2474770
4196     --Obtain the more_info_role in addition
4197     select WN.STATUS, WN.CALLBACK, WN.CONTEXT, -- , WN.USER_COMMENT
4198            WN.RECIPIENT_ROLE, WN.ORIGINAL_RECIPIENT,WN.MORE_INFO_ROLE,
4199            WN.FROM_ROLE
4200            , wn.message_type, wn.message_name -- <7641725>
4201     into  status, cb, context, old_role,   --  newcomment,
4202           old_origrole,l_more_info_role,l_from_role
4203           , l_msgType, l_msgName
4204     from  WF_NOTIFICATIONS WN
4205     where WN.NOTIFICATION_ID = nid
4206     for update nowait;
4207   exception
4208     when no_data_found then
4209       wf_core.token('NID', to_char(nid));
4210       wf_core.raise('WFNTF_NID');
4211   end;
4212   if (status <> 'OPEN') then
4213     wf_core.token('NID', to_char(nid));
4214     wf_core.raise('WFNTF_NID_OPEN');
4215   end if;
4216 
4217   -- If we are in a different Fwk session, need to clear Workflow PLSQL state
4218   if (not Wfa_Sec.CheckSession) then
4219     Wf_Global.Init;
4220   end if;
4221 
4222   -- Check role is valid and get mail preference
4223   begin
4224     mailpref := Wf_Notification.GetMailPreference(new_role, cb, context);
4225   exception
4226     when others then
4227       wf_core.token('ROLE', new_role);
4228       if (fmode = 'FORWARD') then
4229         wf_core.raise('WFNTF_DELEGATE_FAIL');
4230       elsif (fmode = 'TRANSFER') then
4231         wf_core.raise('WFNTF_TRANSFER_FAIL');
4232       end if;
4233   end;
4234 
4235   -- Bug 3065814
4236   -- Set the global context variables to appropriate values for this mode
4237   if (forward_comment is not null and
4238         substr(forward_comment, 1, 6) = 'email:') then
4239      -- If responded through mail then get the username from the email
4240      GetUserFromEmail(forward_comment, l_username, l_dispname, l_found);
4241      if (l_found) then
4242         g_context_user := l_username;
4243      else
4244         g_context_user := 'email:'||l_username;
4245      end if;
4246   else
4247      if (action_source = 'WA') then
4248         -- notification is reassigned by a proxy who is logged in and has a Fwk session
4249         g_context_proxy := Wfa_Sec.GetUser();
4250         g_context_user := ForwardInternal.user;
4251      elsif (action_source = 'RULE') then
4252         -- notification is reassigned by Routing Rule, the context user should be the user
4253         -- to whom the rule belongs and to whom the notification is reassigned
4254         g_context_proxy := null;
4255         g_context_user :=  ForwardInternal.user;
4256      else
4257         -- notification is reassigned by the recipient
4258         g_context_proxy := null;
4259         g_context_user := Wfa_Sec.GetUser();
4260      end if;
4261   end if;
4262 
4263   g_context_user_comment := forwardinternal.forward_comment;
4264   g_context_recipient_role := old_role;
4265   g_context_original_recipient:= old_origrole;
4266   g_context_from_role := l_from_role;
4267   g_context_new_role  := new_role;
4268   g_context_more_info_role  := l_more_info_role;
4269 
4270   -- Call the callback in whatever mode specified if callback is provided
4271   if (cb is not null) then
4272     tvalue := new_role;
4273     nvalue := nid;
4274     -- ### Review Note 2 - cb is from table
4275     -- BINDVAR_SCAN_IGNORE
4276     l_charcheck := wf_notification_util.CheckIllegalChar(cb);
4277     --Throw the Illegal exception when the check fails
4278 
4279 
4280       sqlbuf := 'begin '||cb||
4281               '(:p1, :p2, :p3, :p4, :p5, :p6, :p7); end;';
4282       execute immediate sqlbuf using
4283        in fmode,
4284        in context,
4285        in l_dummy,
4286        in l_dummy,
4287        in out tvalue,
4288        in out nvalue,
4289        in out dvalue;
4290 
4291   end if;
4292 
4293   -- Old style comment appending to existing user comment is obsolete
4294   -- Call SetComments with the required values. Also, the audit message
4295   -- is no longer available
4296 
4297   -- CTILLEY: Bug 2331070.
4298   if (fmode = 'FORWARD') then
4299      l_from_role := nvl(user,old_role);
4300   else
4301      l_from_role := nvl(user,old_origrole);
4302   end if;
4303 
4304   --Bug 2474770
4305   --If the transfer/delegate role is the same as the more_info_role
4306   --the more_info_role is set to null
4307   --Bug 2609352 - if the notification is reassigned the more_info_role
4308   --will be set to null.
4309   if (l_more_info_role is not NULL) then
4310     l_more_info_role := null;
4311   end if;
4312 
4313   -- Finally, do the update.
4314   -- Reset the mail flag so mailer will look for it again.
4315   -- BUG 1772490 JWSMITH - added new access_key when fmode is transfer
4316 
4317   -- BUG 2331070 CTILLEY - added update to FROM_ROLE
4318   -- Bug 2474770
4319   -- Update the more_info_role aswell
4320   if (fmode = 'TRANSFER') then
4321       update WF_NOTIFICATIONS set
4322          RECIPIENT_ROLE = ForwardInternal.new_role,
4323          ORIGINAL_RECIPIENT = decode(ForwardInternal.fmode,
4324                                 'TRANSFER', ForwardInternal.new_role,
4325                                 ORIGINAL_RECIPIENT),
4326          -- USER_COMMENT = ForwardInternal.newcomment,
4327          MAIL_STATUS = decode(ForwardInternal.mailpref,
4328                          'QUERY', '',
4329                          'SUMMARY', '',
4330                          'SUMHTML', '',
4331                          'DISABLED', 'FAILED',
4332                          null, '', 'MAIL'),
4333          ACCESS_KEY = wf_core.random,
4334          FROM_ROLE = l_from_role,
4335          MORE_INFO_ROLE = l_more_info_role
4336       where NOTIFICATION_ID = nid;
4337 
4338       Wf_Notification.SetComments(nid, l_from_role, new_role, 'TRANSFER',
4339                                   action_source, forward_comment);
4340 
4341   else
4342       update WF_NOTIFICATIONS set
4343         RECIPIENT_ROLE = ForwardInternal.new_role,
4344         ORIGINAL_RECIPIENT = decode(ForwardInternal.fmode,
4345                                 'TRANSFER', ForwardInternal.new_role,
4346                                 ORIGINAL_RECIPIENT),
4347         -- USER_COMMENT = ForwardInternal.newcomment,
4348         MAIL_STATUS = decode(ForwardInternal.mailpref,
4349                          'QUERY', '',
4350                          'SUMMARY', '',
4351                          'SUMHTML', '',
4352                          'DISABLED', 'FAILED',
4353                          null, '', 'MAIL'),
4354         FROM_ROLE = l_from_role,
4355         MORE_INFO_ROLE = l_more_info_role
4356       where NOTIFICATION_ID = nid;
4357 
4358       Wf_Notification.SetComments(nid, l_from_role, new_role, 'DELEGATE',
4359                                   action_source, forward_comment);
4360   end if;
4361 
4362   -- Pop any messages from then outbound queue
4363 
4364   -- GK: 1636402: wf_xml.RemoveNotification is not necessary
4365   -- since the message is likely to be sent by the time the
4366   -- user goes in and does an action from the worklist.
4367   -- wf_xml.RemoveNotification(nid);
4368 
4369   -- Check for auto-routing of notification just forwarded
4370   Wf_Notification.Route(nid, cnt);
4371 
4372   -- Denormalize after all the routing is done
4373   if (cnt = 0) then
4374     Wf_Notification.Denormalize_Notification(nid);
4375 
4376     -- Push the new notification to the queue
4377     -- The call to wf_xml.EnqueueNotification has been moved
4378     -- to an event subscription.
4379     -- wf_xml.EnqueueNotification(nid);
4380   end if;
4381 
4382   --Bug 2283697
4383   --To raise an EVENT whenever DML operation is performed on
4384   --WF_NOTIFICATIONS and WF_NOTIFICATION_ATTRIBUTES table.
4385   wf_event.AddParameterToList('NOTIFICATION_ID',nid,l_parameterlist);
4386   wf_event.AddParameterToList('NEW_ROLE',new_role,l_parameterlist);
4387   wf_event.AddParameterToList('MODE',fmode,l_parameterlist);
4388   if (user is not null) then
4389     wf_event.AddParameterToList('USER',user,l_parameterlist);
4390   elsif (fmode = 'FORWARD') then
4391     wf_event.AddParameterToList('USER',old_role,l_parameterlist);
4392   else
4393     wf_event.AddParameterToList('USER',old_origrole,l_parameterlist);
4394   end if;
4395 
4396   -- <7641725> we need to include Q_CORRELATION_ID parameter
4397   wf_event.addParameterToList('Q_CORRELATION_ID'
4398                                   , l_msgType||':'||l_msgName, l_parameterlist);
4399 
4400     select WN.RECIPIENT_ROLE
4401     into  l_recipient_role
4402     from  WF_NOTIFICATIONS WN
4403     where WN.NOTIFICATION_ID = nid;
4404 
4405   Wf_Directory.GetRoleInfo2(l_recipient_role, role_info_tbl);
4406   l_language := role_info_tbl(1).language;
4407 
4408   select code into l_language from wf_languages where nls_language = l_language;
4409 
4410   -- AppSearch
4411   wf_event.AddParameterToList('OBJECT_NAME',
4412   'oracle.apps.fnd.wf.worklist.server.AllNotificationsVO', l_parameterlist);
4413   wf_event.AddParameterToList('CHANGE_TYPE', 'INSERT',l_parameterlist);
4414   wf_event.AddParameterToList('ID_TYPE', 'PK', l_parameterlist);
4415   wf_event.addParameterToList('PK_NAME_1', 'NOTIFICATION_ID',l_parameterlist);
4416   wf_event.addParameterToList('PK_VALUE_1', nid, l_parameterlist);
4417   wf_event.addParameterToList('PK_NAME_2', 'LANGUAGE',l_parameterlist);
4418   wf_event.addParameterToList('PK_VALUE_2', l_language, l_parameterlist);
4419 
4420 
4421   --Raise the event
4422   wf_event.Raise(p_event_name => 'oracle.apps.wf.notification.reassign',
4423                  p_event_key  => to_char(nid),
4424                  p_parameters => l_parameterlist);
4425 
4426 exception
4427   when others then
4428     wf_core.context('Wf_Notification', 'ForwardInternal', to_char(nid),
4429         new_role, fmode, forward_comment);
4430     raise;
4431 end ForwardInternal;
4432 
4433 --
4434 -- Forward
4435 --   Forward a notification, identified by NID to another user. Validate
4436 --   the user and Return error messages ...
4437 -- IN:
4438 --   nid - Notification Id
4439 --   new_role - Role to forward notification to
4440 --   forward_comment - comment to append to notification
4441 --   user - role who perform this action if provided
4442 --   cnt - count for recursive purpose
4443 --   action_source - Source from where the action is performed
4444 --
4445 procedure Forward(nid in number,
4446                   new_role in varchar2,
4447                   forward_comment in varchar2,
4448                   user in varchar2,
4449                   cnt in number,
4450                   action_source in varchar2)
4451 is
4452 begin
4453   ForwardInternal(nid, new_role, 'FORWARD', forward_comment, user, cnt, action_source);
4454 exception
4455   when others then
4456     wf_core.context('Wf_Notification', 'Forward', to_char(nid),
4457         new_role, forward_comment);
4458     -- This call is for enhanced error handling with respect to OAFwk
4459     wf_notification.SetUIErrorMessage;
4460     raise;
4461 end Forward;
4462 
4463 --
4464 -- Transfer
4465 --   Transfer a notification, identified by NID to another user. Validate
4466 --   the user and Return error messages ...
4467 -- IN:
4468 --   nid - Notification Id
4469 --   new_role - Role to transfer notification to
4470 --   forward_comment - comment to append to notification
4471 --   user - role who perform this action if provided
4472 --   cnt - count for recursive purpose
4473 --   action_source - Source from where the action is performed
4474 --
4475 procedure Transfer(nid in number,
4476                   new_role in varchar2,
4477                   forward_comment in varchar2,
4478                   user in varchar2,
4479                   cnt in number,
4480                   action_source in varchar2)
4481 is
4482 begin
4483   ForwardInternal(nid, new_role, 'TRANSFER', forward_comment, user, cnt, action_source);
4484 exception
4485   when others then
4486     wf_core.context('Wf_Notification', 'Transfer', to_char(nid),
4487         new_role, forward_comment);
4488     -- This call is for enhanced error handling with respect to OAFwk
4489     wf_notification.SetUIErrorMessage;
4490     raise;
4491 end Transfer;
4492 
4493 --
4494 -- CancelSingle (PRIVATE)
4495 --   Cancel a single notification.
4496 --   Called by Cancel and CancelGroup public functions.
4497 --   Argument error checking should be done by Cancel and CancelGroup before
4498 --   calling this function.
4499 -- IN:
4500 --   nid - Notification Id
4501 --   role - Role notification is sent to
4502 --   cancel_comment - Comment to append to notification
4503 --
4504 procedure CancelSingle(nid in number,
4505                        role in varchar2,
4506                        cancel_comment in varchar2,
4507 		       timeout in boolean)
4508 is
4509   mailpref varchar2(8);
4510   newcomment varchar2(4000);
4511   status varchar2(8);
4512   cb varchar2(240);
4513   context varchar2(2000);
4514   dummy pls_integer;
4515 
4516  --Bug 2283697
4517  l_parameterlist        wf_parameter_list_t := wf_parameter_list_t();
4518 
4519  --Bug 2373925
4520  l_mail varchar2(4);
4521  l_from_role varchar2(320);
4522  l_action  varchar2(30);
4523  l_send_cancel varchar2(10);
4524  l_msg_type    varchar2(8);
4525  l_msg_name    varchar2(30);
4526  l_language    varchar2(30);
4527 begin
4528 
4529   -- Check the notification exists and is open
4530   begin
4531     select WN.STATUS, WN.CALLBACK, WN.CONTEXT, WN.MESSAGE_TYPE, WN.MESSAGE_NAME, WN.LANGUAGE
4532     into status, cb, context, l_msg_type, l_msg_name, l_language
4533     from WF_NOTIFICATIONS WN
4534     where WN.NOTIFICATION_ID = nid
4535     for update nowait;
4536   exception
4537     when no_data_found then
4538       wf_core.token('NID', to_char(nid));
4539       wf_core.raise('WFNTF_NID');
4540   end;
4541   if (status <> 'OPEN') then
4542     wf_core.token('NID', to_char(nid));
4543     wf_core.raise('WFNTF_NID_OPEN');
4544   end if;
4545 
4546   -- Check role is valid and get mail preference
4547   mailpref := Wf_Notification.GetMailPreference(role, cb, context);
4548 
4549   -- If no responses expected, then do not mail cancel notice
4550   -- regardless of role notification_preference setting.
4551   begin
4552     select 1 into dummy from sys.dual where exists
4553     (select NULL
4554     from WF_NOTIFICATIONS WN, WF_MESSAGE_ATTRIBUTES WMA
4555     where WN.NOTIFICATION_ID = nid
4556     and WN.MESSAGE_NAME = WMA.MESSAGE_NAME
4557     and WN.MESSAGE_TYPE = WMA.MESSAGE_TYPE
4558     and WMA.SUBTYPE = 'RESPOND');
4559 
4560   exception
4561     when no_data_found then
4562       -- No responses, set mailpref to not mail cancel notice regardless.
4563       mailpref := 'QUERY';
4564   end;
4565 
4566   -- if mailer config parameter SEND_CANCELED_EMAIL is set to N, no e-mails
4567   -- are sent for canceled notifications
4568   l_send_cancel := wf_mailer_parameter.GetValueForCorr(pCorrId => l_msg_type || ':'|| l_msg_name,
4569                                                        pName   => 'SEND_CANCELED_EMAIL');
4570   if (l_send_cancel = 'Y') then
4571     l_mail := 'MAIL';
4572   else
4573     l_mail := '';
4574   end if;
4575 
4576   update WF_NOTIFICATIONS set
4577     STATUS = 'CANCELED',
4578     END_DATE = sysdate,
4579     -- USER_COMMENT = CancelSingle.newcomment,
4580     MAIL_STATUS = decode(MAIL_STATUS,
4581                          'ERROR', 'ERROR',
4582                -- if this was never sent, dont bother sending cancelation
4583                          'MAIL',  '',
4584                          decode(CancelSingle.mailpref,
4585                                'QUERY', '',
4586                                'SUMMARY', '',
4587                                'SUMHTML', '',
4588                                'DISABLED', 'FAILED',
4589                                null, '', l_mail))
4590   where NOTIFICATION_ID = nid;
4591 
4592   if (timeout) then
4593     l_action := 'TIMEOUT';
4594     l_from_role := role;
4595   else
4596     l_action := 'CANCEL';
4597     l_from_role := Wfa_Sec.GetUser();
4598   end if;
4599 
4600   if (l_from_role is null) then
4601      l_from_role := 'WF_SYSTEM';
4602   end if;
4603   Wf_Notification.SetComments(nid, l_from_role, 'WF_SYSTEM', l_action, null, newcomment);
4604 
4605   -- GK: 1636402: wf_xml.RemoveNotification is not necessary
4606   -- since the message is likely to be sent by the time the
4607   -- user goes in and does an action from the worklist.
4608   -- wf_xml.RemoveNotification(nid);
4609   -- SJM: 2122556 - Cancelled notifications are not being sent out
4610   -- becuase they are not being enqueued.
4611   -- The call to wf_xml.EnqueueNotification has been moved to an
4612   -- event subscription
4613   -- wf_xml.EnqueueNotification(nid);
4614 
4615   --Bug 2283697
4616   --To raise an EVENT whenever DML operation is performed on
4617   --WF_NOTIFICATIONS and WF_NOTIFICATION_ATTRIBUTES table.
4618   wf_event.AddParameterToList('NOTIFICATION_ID',nid,l_parameterlist);
4619   wf_event.AddParameterToList('ROLE',role,l_parameterlist);
4620   wf_event.addParameterToList('Q_CORRELATION_ID', l_msg_type || ':'||
4621                               l_msg_name, l_parameterlist);
4622 
4623 
4624    -- AppSearch
4625   wf_event.AddParameterToList('OBJECT_NAME',
4626   'oracle.apps.fnd.wf.worklist.server.AllNotificationsVO', l_parameterlist);
4627   wf_event.AddParameterToList('CHANGE_TYPE', 'INSERT',l_parameterlist);
4628   wf_event.AddParameterToList('ID_TYPE', 'PK', l_parameterlist);
4629   wf_event.addParameterToList('PK_NAME_1', 'NOTIFICATION_ID',l_parameterlist);
4630   wf_event.addParameterToList('PK_VALUE_1', nid, l_parameterlist);
4631   wf_event.addParameterToList('PK_NAME_2', 'LANGUAGE',l_parameterlist);
4632   wf_event.addParameterToList('PK_VALUE_2', l_language, l_parameterlist);
4633 
4634   --Raise the event
4635   wf_event.Raise(p_event_name => 'oracle.apps.wf.notification.cancel',
4636                  p_event_key  => to_char(nid),
4637                  p_parameters => l_parameterlist);
4638 
4639 
4640 exception
4641   when others then
4642     wf_core.context('Wf_Notification', 'CancelSingle', to_char(nid),
4643                     role, cancel_comment);
4644     raise;
4645 end CancelSingle;
4646 
4647 --
4648 -- Cancel
4649 --   Cancel a single notification.
4650 -- IN:
4651 --   nid - Notification Id
4652 --   cancel_comment - Comment to append to notification
4653 --
4654 procedure Cancel(nid in number,
4655                  cancel_comment in varchar2)
4656 is
4657   status varchar2(8);
4658   role varchar2(320);
4659 begin
4660   if (nid is null) then
4661     wf_core.token('NID', to_char(nid));
4662     wf_core.raise('WFSQL_ARGS');
4663   end if;
4664 
4665   -- Check the notification exists and is open
4666   begin
4667     select STATUS, RECIPIENT_ROLE
4668     into status, role
4669     from WF_NOTIFICATIONS
4670     where NOTIFICATION_ID = nid
4671     for update nowait;
4672   exception
4673     when no_data_found then
4674       wf_core.token('NID', to_char(nid));
4675       wf_core.raise('WFNTF_NID');
4676   end;
4677 
4678   if (status <> 'OPEN') then
4679     wf_core.token('NID', to_char(nid));
4680     wf_core.raise('WFNTF_NID_OPEN');
4681   end if;
4682 
4683   -- Call CancelSingle to complete cancellation of single notification
4684   CancelSingle(nid, role, cancel_comment, FALSE);
4685 
4686 exception
4687   when others then
4688     wf_core.context('Wf_Notification', 'Cancel', to_char(nid), cancel_comment);
4689     raise;
4690 end Cancel;
4691 
4692 --
4693 -- CancelGroup
4694 --   Cancel all notifications belonging to a notification group
4695 -- IN:
4696 --   gid - Notification group id
4697 --   cancel_comment - Comment to append to all notifications
4698 --
4699 procedure CancelGroup(gid in number,
4700                       cancel_comment in varchar2,
4701 		      timeout in boolean)
4702 is
4703   -- Get all still open notifications in the group
4704   cursor group_curs is
4705     select NOTIFICATION_ID, RECIPIENT_ROLE
4706     from WF_NOTIFICATIONS
4707     where GROUP_ID = gid
4708     and status = 'OPEN'
4709     for update nowait;
4710 
4711 begin
4712   if (gid is null) then
4713     wf_core.token('NID', to_char(gid));
4714     wf_core.raise('WFSQL_ARGS');
4715   end if;
4716 
4717   -- Cancel all open notifications in this group
4718   for notice in group_curs loop
4719     -- Call CancelSingle to complete cancellation of single notification
4720     CancelSingle(notice.notification_id, notice.recipient_role,
4721                  cancel_comment, timeout);
4722   end loop;
4723 exception
4724   when others then
4725     wf_core.context('Wf_Notification', 'CancelGroup', to_char(gid),
4726                     cancel_comment);
4727     raise;
4728 end CancelGroup;
4729 
4730 --
4731 -- Respond
4732 --   Respond to a notification.
4733 -- IN:
4734 --   nid - Notification Id
4735 --   respond_comment - Comment to append to notification
4736 --   responder - User or role responding to notification
4737 --   action_source - Source from where the action is performed
4738 --
4739 procedure Respond(nid in number,
4740                   respond_comment in varchar2,
4741                   responder in varchar2,
4742                   action_source in varchar2)
4743 is
4744   callback varchar2(240);
4745   context varchar2(2000);
4746   status varchar2(8);
4747   newcomment varchar2(4000);
4748 
4749   -- Dynamic sql stuff
4750   sqlbuf varchar2(2000);
4751 
4752   --Bug 2283697
4753   l_parameterlist        wf_parameter_list_t := wf_parameter_list_t();
4754 
4755 
4756   cursor notification_attrs_cursor(nid number) is
4757     select WNA.NAME, WMA.TYPE, WNA.TEXT_VALUE, WNA.NUMBER_VALUE,
4758            WNA.DATE_VALUE
4759     from WF_NOTIFICATION_ATTRIBUTES WNA,
4760       WF_MESSAGE_ATTRIBUTES WMA,
4761       WF_NOTIFICATIONS WN
4762     where WNA.NOTIFICATION_ID = nid
4763     and WN.NOTIFICATION_ID = WNA.NOTIFICATION_ID
4764     and WN.MESSAGE_TYPE = WMA.MESSAGE_TYPE
4765     and WN.MESSAGE_NAME = WMA.MESSAGE_NAME
4766     and WNA.NAME = WMA.NAME
4767     and WMA.SUBTYPE = 'RESPOND';
4768 
4769   aname varchar2(30);
4770   atype varchar2(8);
4771   tvalue varchar2(4000);
4772   nvalue number;
4773   dvalue date;
4774 
4775   response_found boolean;
4776 
4777   -- kma bug2376058 digital signature support
4778   proxyuser varchar2(4000);
4779 
4780   --Bug 3065814
4781   l_recip_role varchar2(320);
4782   l_orig_recip_role varchar2(320);
4783   l_more_info_role varchar2(320);
4784   l_from_role   varchar2(320);
4785   l_dispname   VARCHAR2(360);
4786   l_responder  varchar2(320);
4787   l_found      boolean;
4788   l_dummy      varchar2(1);
4789 
4790   l_language		varchar2(30);
4791 
4792   --Bug 3827935
4793   l_charcheck boolean;
4794 
4795 begin
4796   if (nid is null) then
4797     wf_core.token('NID', to_char(nid));
4798     wf_core.raise('WFSQL_ARGS');
4799   end if;
4800 
4801   -- kma bug2376058 digital signature support
4802   begin
4803     proxyuser := Wf_Notification.GetAttrText(nid, '#WF_PROXIED_VIA');
4804   exception
4805     when others then
4806       if (wf_core.error_name = 'WFNTF_ATTR') then
4807         -- Pass null result if no result attribute.
4808         wf_core.clear;
4809         proxyuser := '';
4810       else
4811         raise;
4812       end if;
4813   end;
4814   if ((proxyuser is not null) and (proxyuser <> '') and
4815       ((responder is null) or
4816        ((responder is not null) and (proxyuser <> responder)))) then
4817     wf_core.token('NID', to_char(nid));
4818     wf_core.raise('WFNTF_DIGSIG_USER_MISMATCH');
4819   end if;
4820 
4821   -- bug 2698999 Checking if ntf's signature requirements are met
4822   if (NOT Wf_Notification.NtfSignRequirementsMet(nid)) then
4823      wf_core.token('NID', to_char(nid));
4824      wf_core.raise('WFNTF_NOT_SIGNED');
4825   end if;
4826 
4827   -- Get callback, check for valid notification id.
4828   begin
4829     select N.CALLBACK, N.CONTEXT, N.STATUS, N.USER_COMMENT,
4830            N.RECIPIENT_ROLE, N.ORIGINAL_RECIPIENT,N.MORE_INFO_ROLE, N.FROM_ROLE, N.LANGUAGE
4831     into   respond.callback, respond.context, respond.status, newcomment,
4832            l_recip_role,l_orig_recip_role,l_more_info_role, l_from_role, l_language
4833     from   WF_NOTIFICATIONS N
4834     where  N.NOTIFICATION_ID = nid
4835     for update nowait;
4836   exception
4837     when no_data_found then
4838       wf_core.token('NID', to_char(nid));
4839       wf_core.raise('WFNTF_NID');
4840   end;
4841 
4842   -- Check notification is open
4843   if (status <> 'OPEN') then
4844     wf_core.token('NID', to_char(nid));
4845     wf_core.raise('WFNTF_NID_OPEN');
4846   end if;
4847 
4848   -- If we are in a different Fwk session, need to clear Workflow PLSQL state
4849   if (not Wfa_Sec.CheckSession) then
4850     Wf_Global.Init;
4851   end if;
4852 
4853   -- Bug 3065814
4854   -- Set the global context variables to appropriate values for this mode
4855   if (respond.responder is not null and
4856         substr(respond.responder, 1, 6) = 'email:') then
4857      -- If responded through mail then get the username from email
4858      GetUserfromEmail(respond.responder, l_responder, l_dispname, l_found);
4859      if (not l_found) then
4860         l_responder := 'email:' || l_responder;
4861      end if;
4862   else
4863      if (action_source = 'WA') then
4864         -- notification is responded by a proxy who is logged in and has a Fwk session
4865         g_context_proxy := Wfa_Sec.GetUser();
4866         l_responder := respond.responder;
4867      elsif (action_source = 'RULE') then
4868         -- notification is responded by Routing Rule, the context user should be the user
4869         -- to whom the rule belongs who is actually responding to the notification
4870         g_context_proxy := null;
4871         l_responder := respond.responder;
4872      else
4873         -- notification is responded by the recipient.
4874 	-- responder should be respond.responder
4875         g_context_proxy := null;
4876         l_responder := respond.responder;
4877      end if;
4878   end if;
4879 
4880   -- Set the approrpiate responder to context user
4881   g_context_user := l_responder;
4882 
4883   if respond.respond_comment is null then
4884      g_context_user_comment := Wf_Notification.GetAttrText(nid,'WF_NOTE',TRUE);
4885   else
4886      g_context_user_comment := respond.respond_comment;
4887   end if;
4888   g_context_recipient_role := l_recip_role;
4889   g_context_original_recipient:= l_orig_recip_role;
4890   g_context_from_role := l_from_role;
4891   g_context_new_role  := '';
4892   g_context_more_info_role  := l_more_info_role;
4893 
4894   -- Call the callback in VALIDATE mode to execute the post notification
4895   -- function to perform some custom validation and reject the response by
4896   -- raising exception. If validation is already done in RESPOND mode, it
4897   -- can stay there... VALIDATE mode can be called back from outside of
4898   -- notification code also before calling the Wf_Notification.Respond API
4899   if (callback is not null) then
4900     tvalue := respond.responder;
4901     nvalue := nid;
4902     -- ### Review Note 2 - callback is from table
4903 				-- Check for bug#3827935
4904     l_charcheck := wf_notification_util.CheckIllegalChar(callback);
4905     --Throw the Illegal exception when the check fails
4906 
4907 
4908      -- BINDVAR_SCAN_IGNORE
4909      sqlbuf := 'begin '||callback||
4910               '(:p1, :p2, :p3, :p4, :p5, :p6, :p7); end;';
4911      execute immediate sqlbuf using
4912       in 'VALIDATE',
4913       in context,
4914       in l_dummy,
4915       in l_dummy,
4916       in out tvalue,
4917       in out nvalue,
4918       in out dvalue;
4919 
4920      -- Call the callback in RESPOND mode to perform the post-notification
4921      -- callback if there is one. Note this should be before the response is
4922      -- actually processed to give the callback a chance to reject the response
4923      -- by raising an exception.
4924 
4925      -- ### Review Note 2 - callback is from table
4926      -- BINDVAR_SCAN_IGNORE
4927      -- sqlbuf := 'begin '||callback||
4928      --          '(:p1, :p2, :p3, :p4, :p5, :p6, :p7); end;';
4929      execute immediate sqlbuf using
4930       in 'RESPOND',
4931       in context,
4932       in l_dummy,
4933       in l_dummy,
4934       in out tvalue,
4935       in out nvalue,
4936       in out dvalue;
4937 
4938       tvalue := '';
4939       nvalue := '';
4940   end if;
4941 
4942   -- Append the respond_comment (if any) to the user_comment
4943   -- if (respond_comment is not null) then
4944   --   if (newcomment is not null) then
4945   --     newcomment := substrb(newcomment||wf_core.newline||
4946   --                   respond_comment, 1, 4000);
4947   --   else
4948   --     newcomment := respond_comment;
4949   --   end if;
4950   -- end if;
4951 
4952   -- Mark notification closed
4953   update WF_NOTIFICATIONS
4954   set STATUS = 'CLOSED',
4955       MAIL_STATUS = NULL,
4956       END_DATE = sysdate,
4957       -- RESPONDER = respond.responder
4958       -- For responses through e-mail, this helps strip off unwanted parts from e-mail like
4959       -- "John Doe" [email protected]> and have only email:[email protected]
4960       RESPONDER = l_responder
4961       -- USER_COMMENT = respond.newcomment
4962   where NOTIFICATION_ID = respond.nid;
4963 
4964   -- responder should be the From role that appears in the action history
4965   Wf_Notification.SetComments(nid, l_responder, 'WF_SYSTEM', 'RESPOND',
4966                               action_source, respond_comment);
4967 
4968   --Bug 2283697
4969   --To raise an EVENT whenever DML operation is performed on
4970   --WF_NOTIFICATIONS and WF_NOTIFICATION_ATTRIBUTES table.
4971   wf_event.AddParameterToList('NOTIFICATION_ID',nid,l_parameterlist);
4972   wf_event.AddParameterToList('RESPONDER',respond.responder,l_parameterlist);
4973 
4974   -- AppSearch
4975   wf_event.AddParameterToList('OBJECT_NAME',
4976   'oracle.apps.fnd.wf.worklist.server.AllNotificationsVO', l_parameterlist);
4977   wf_event.AddParameterToList('CHANGE_TYPE', 'INSERT',l_parameterlist);
4978   wf_event.AddParameterToList('ID_TYPE', 'PK', l_parameterlist);
4979   wf_event.addParameterToList('PK_NAME_1', 'NOTIFICATION_ID',l_parameterlist);
4980   wf_event.addParameterToList('PK_VALUE_1', nid, l_parameterlist);
4981   wf_event.addParameterToList('PK_NAME_2', 'LANGUAGE',l_parameterlist);
4982   wf_event.addParameterToList('PK_VALUE_2', l_language, l_parameterlist);
4983 
4984   --Raise the event
4985   wf_event.Raise(p_event_name => 'oracle.apps.wf.notification.respond',
4986                  p_event_key  => to_char(nid),
4987                  p_parameters => l_parameterlist);
4988 
4989   -- If no callback, there is nothing else to do.
4990   if (callback is null) then
4991     return;
4992   end if;
4993 
4994   --
4995   -- Open dynamic sql cursor for SET callback calls.
4996   --
4997   -- ### Review Note 2 - callback is from table
4998   l_charcheck := wf_notification_util.CheckIllegalChar(callback);
4999   --Throw the Illegal exception when the check fails
5000 
5001 
5002    -- BINDVAR_SCAN_IGNORE
5003    sqlbuf := 'begin '||callback||
5004             '(:p1, :p2, :p3, :p4, :p5, :p6, :p7); end;';
5005    --
5006    -- Call callback to SET all RESPOND attributes for notification.
5007    --
5008    response_found := FALSE;
5009    for response_row in notification_attrs_cursor(nid) loop
5010     response_found := TRUE;
5011 
5012     aname := response_row.name;
5013     atype := response_row.type;
5014     tvalue := response_row.text_value;
5015     nvalue := response_row.number_value;
5016     dvalue := response_row.date_value;
5017 
5018     execute immediate sqlbuf using
5019       in 'SET',
5020       in context,
5021       in aname,
5022       in atype,
5023       in out tvalue,
5024       in out nvalue,
5025       in out dvalue;
5026   end loop;
5027   --
5028   -- If no response attributes are found, then no response was expected
5029   -- for this notification.  Do not call the callback to complete the
5030   -- activity.  The activity 'completed' as soon as the notification
5031   -- was sent.
5032   --
5033    if (not response_found) then
5034     return;
5035    end if;
5036 
5037   --
5038   -- Call callback one more time to mark activity COMPLETE.
5039   -- Send the result and notification id as the value.
5040   --
5041    begin
5042     tvalue := Wf_Notification.GetAttrText(nid, 'RESULT');
5043 
5044   exception
5045     when others then
5046       if (wf_core.error_name = 'WFNTF_ATTR') then
5047         -- Pass null result if no result attribute.
5048         wf_core.clear;
5049         tvalue := '';
5050       else
5051         raise;
5052       end if;
5053   end;
5054   nvalue := nid;
5055   dvalue := '';
5056 
5057   -- Reparse and bind dynamic sql for the COMPLETE callback call.
5058   -- ### Review Note 2 - callback is from table
5059   -- Check for bug#3827935
5060   l_charcheck := wf_notification_util.CheckIllegalChar(callback);
5061   --Throw the Illegal exception when the check fails
5062 
5063 
5064    -- BINDVAR_SCAN_IGNORE[3]
5065 	 	sqlbuf := 'begin '||callback||
5066                    '(:p1, :p2, :p3, :p4, :p5, :p6, :p7); end;';
5067    execute immediate sqlbuf using
5068     in 'COMPLETE',
5069     in context,
5070     in 'NID',
5071     in 'NUMBER',
5072     in out tvalue,
5073     in out nvalue,
5074     in out dvalue;
5075 
5076    -- Remove any messages from the outbound queue
5077    -- GK: 1636402: wf_xml.RemoveNotification is not necessary
5078    -- since the message is likely to be sent by the time the
5079    -- user goes in and does an action from the worklist.
5080    -- wf_xml.RemoveNotification(nid);
5081 exception
5082   when others then
5083     wf_core.context('Wf_Notification', 'Respond', to_char(nid),
5084                     respond_comment, responder);
5085     -- This call is for enhanced error handling with respect to OAFwk
5086     wf_notification.SetUIErrorMessage;
5087     raise;
5088 end Respond;
5089 
5090 --
5091 -- TestContext
5092 --   Test if current context is correct
5093 -- IN
5094 --   nid - Notification id
5095 -- RETURNS
5096 --   TRUE if context ok, or context check not implemented
5097 --   FALSE if context check fails
5098 --
5099 function TestContext(
5100   nid in number)
5101 return boolean
5102 is
5103   callback varchar2(240);
5104   context varchar2(2000);
5105 
5106   -- Dynamic sql stuff
5107   sqlbuf varchar2(2000);
5108   tvalue varchar2(4000);
5109   nvalue number;
5110   dvalue date;
5111   l_dummy  varchar2(1);
5112 
5113 		l_charcheck  boolean;
5114 begin
5115   if (nid is null) then
5116     wf_core.token('NID', to_char(nid));
5117     wf_core.raise('WFSQL_ARGS');
5118   end if;
5119 
5120   -- Get callback, check for valid notification id.
5121   begin
5122     select N.CALLBACK, N.CONTEXT
5123     into   TestContext.callback, TestContext.context
5124     from   WF_NOTIFICATIONS N
5125     where  N.NOTIFICATION_ID = nid;
5126   exception
5127     when no_data_found then
5128       wf_core.token('NID', to_char(nid));
5129       wf_core.raise('WFNTF_NID');
5130   end;
5131 
5132   -- If no callback, then nothing to check
5133   if (callback is null) then
5134     return(TRUE);
5135   end if;
5136 
5137   -- Open dynamic sql cursor for callback call
5138   -- ### Review Note 2 - callback is from table
5139 		-- Check for bug#3827935
5140   l_charcheck := wf_notification_util.CheckIllegalChar(callback);
5141   --Throw the Illegal exception when the check fails
5142 
5143 
5144     -- BINDVAR_SCAN_IGNORE
5145     sqlbuf := 'begin '||callback||
5146                    '(:p1, :p2, :p3, :p4, :p5, :p6, :p7); end;';
5147     execute immediate sqlbuf using
5148      in 'TESTCTX',
5149      in context,
5150      in l_dummy,
5151      in l_dummy,
5152      in out tvalue,
5153      in out nvalue,
5154      in out dvalue;
5155 
5156     if (tvalue in ('FALSE', 'NOTSET')) then
5157      return(FALSE);
5158     else
5159      -- Any other returned value means TEST_CTX mode is not implemented
5160      return(TRUE);
5161     end if;
5162 
5163 exception
5164   when others then
5165     wf_core.context('Wf_Notification', 'TestContext', to_char(nid));
5166     raise;
5167 end TestContext;
5168 
5169 --
5170 -- VoteCout
5171 --      Count the number of responses for a result_code
5172 -- IN:
5173 --      Gid -  Notification group id
5174 --      ResultCode - Result code to be tallied
5175 -- OUT:
5176 --      ResultCount - Number of responses for ResultCode
5177 --      PercentOfTotalPop - % ResultCode ( As a % of total population )
5178 --      PercentOfVotes - % ResultCode ( As a % of votes cast )
5179 --
5180 procedure VoteCount (   Gid                     in  number,
5181                         ResultCode              in  varchar2,
5182                         ResultCount             out nocopy number,
5183                         PercentOfTotalPop       out nocopy number,
5184                         PercentOfVotes          out nocopy number ) is
5185 --
5186 --
5187         l_code_count    pls_integer;
5188         l_total_pop     pls_integer;
5189         l_total_voted   pls_integer;
5190 begin
5191         --
5192         --
5193         select  count(*)
5194         into    l_total_pop
5195         from    wf_notifications
5196         where   group_id        = Gid;
5197         --
5198         select  count(*)
5199         into    l_total_voted
5200         from    wf_notifications
5201         where   group_id        = Gid
5202         and     status          = 'CLOSED';
5203         --
5204         select  count(*)
5205         into    l_code_count
5206         from    wf_notifications wfn,
5207                 wf_notification_attributes wfna
5208         where   wfn.group_id            = Gid
5209         and     wfn.notification_id     = wfna.notification_id
5210         and     wfn.status              = 'CLOSED'
5211         and     wfna.name               = 'RESULT'
5212         and     wfna.text_value         = ResultCode;
5213         --
5214 
5215         ResultCount := l_code_count;
5216         --
5217         -- Prevent division by zero if group has no notifications
5218         --
5219         if ( l_total_pop = 0 ) then
5220                 --
5221                 PercentOfTotalPop := 0;
5222                 --
5223         else
5224                 --
5225                 PercentOfTotalPop := l_code_count/l_total_pop*100;
5226                 --
5227         end if;
5228         --
5229         -- Prevent division by zero if nobody votes
5230         --
5231         if ( l_total_voted = 0 ) then
5232                 --
5233                 PercentOfVotes := 0;
5234                 --
5235         else
5236                 --
5237                 PercentOfVotes := l_code_count/l_total_voted*100;
5238                 --
5239         end if;
5240         --
5241 exception
5242         when others then
5243                 --
5244                 wf_core.context('Wf_Notification', 'VoteCount', to_char(gid), ResultCode );
5245                 raise;
5246                 --
5247 end VoteCount;
5248 --
5249 -- OpenNotifications
5250 --      Determine if any Notifications in the Group are OPEN
5251 --
5252 --IN:
5253 --      Gid -  Notification group id
5254 --
5255 --Returns:
5256 --      TRUE  - if the Group contains open notifications
5257 --      FALSE - if the group does NOT contain open notifications
5258 --
5259 function OpenNotificationsExist( Gid    in Number ) return Boolean is
5260 --
5261 dummy pls_integer;
5262 --
5263 begin
5264         --
5265         select  1
5266         into    dummy
5267         from    sys.dual
5268         where   exists  ( select null
5269                           from   wf_notifications
5270                           where  group_id = Gid
5271                           and    status   = 'OPEN'
5272                         );
5273         --
5274         return(TRUE);
5275         --
5276 exception
5277         when NO_DATA_FOUND then
5278                 --
5279                 return(FALSE);
5280                 --
5281         when others then
5282                 --
5283                 wf_core.context('Wf_Notification', 'OpenNotifications', to_char(gid) );
5284                 raise;
5285                 --
5286 end OpenNotificationsExist;
5287 
5288 --
5289 -- WorkCount
5290 --   Count number of open notifications for user
5291 -- IN:
5292 --   username - user to check
5293 -- RETURNS:
5294 --   Number of open notifications for that user
5295 --
5296 function WorkCount(
5297   username in varchar2)
5298 return number
5299 is
5300   colon pls_integer;
5301   ncount pls_integer;
5302 begin
5303   colon := instr(username, ':');
5304   if (colon = 0) then
5305     select count(1)
5306     into ncount
5307     from WF_NOTIFICATIONS WN
5308     where WN.RECIPIENT_ROLE in
5309       (select WUR.ROLE_NAME
5310       from WF_USER_ROLES WUR
5311       where WUR.USER_NAME = WorkCount.username)
5312     and WN.STATUS = 'OPEN';
5313   else
5314     select count(1)
5315     into ncount
5316     from WF_NOTIFICATIONS WN
5317     where WN.RECIPIENT_ROLE in
5318       (select WUR.ROLE_NAME
5319       from WF_USER_ROLES WUR
5320       where WUR.USER_ORIG_SYSTEM = substr(WorkCount.username, 1, colon-1)
5321       and WUR.USER_ORIG_SYSTEM_ID = substr(WorkCount.username, colon+1)
5322       and WUR.USER_NAME = WorkCount.username)
5323     and WN.STATUS = 'OPEN';
5324   end if;
5325 
5326   return(ncount);
5327 exception
5328   when others then
5329     wf_core.context('Wf_Notification', 'WorkCount', username);
5330     raise;
5331 end WorkCount;
5332 
5333 --
5334 -- Close
5335 --   Close a notification.
5336 -- IN:
5337 --   nid - Notification Id
5338 --   resp - Respond Required?  0 - No, 1 - Yes
5339 --   responder - User or role close this notification
5340 --
5341 procedure Close(nid in number,
5342                 responder in varchar2)
5343 is
5344   status varchar2(8);
5345 
5346   -- Any existence of response attribute constitutes a response required.
5347   cursor attrs(mnid in number) is
5348     select MA.NAME
5349     from WF_NOTIFICATION_ATTRIBUTES NA,
5350          WF_MESSAGE_ATTRIBUTES_VL MA,
5351          WF_NOTIFICATIONS N
5352     where N.NOTIFICATION_ID = mnid
5353     and NA.NOTIFICATION_ID = N.NOTIFICATION_ID
5354     and MA.MESSAGE_NAME = N.MESSAGE_NAME
5355     and MA.MESSAGE_TYPE = N.MESSAGE_TYPE
5356     and MA.NAME = NA.NAME
5357     and MA.SUBTYPE = 'RESPOND';
5358 
5359   result attrs%rowtype;
5360 
5361   --Bug 2283697
5362   l_parameterlist        wf_parameter_list_t := wf_parameter_list_t();
5363 
5364   l_language		varchar2(30);
5365 
5366 begin
5367 
5368   if (nid is null) then
5369     wf_core.token('NID', to_char(nid));
5370     wf_core.raise('WFSQL_ARGS');
5371   end if;
5372 
5373   -- Get Status
5374   begin
5375 
5376     select N.STATUS, N.LANGUAGE
5377     into   close.status, l_language
5378     from   WF_NOTIFICATIONS N
5379     where  N.NOTIFICATION_ID = nid
5380     for update nowait;
5381   exception
5382     when no_data_found then
5383       wf_core.token('NID', Wf_Notification.GetSubject(nid));
5384       wf_core.raise('WFNTF_NID');
5385   end;
5386 
5387   -- Check notification is open
5388   if (status <> 'OPEN') then
5389     wf_core.token('NID', Wf_Notification.GetSubject(nid) );
5390     wf_core.raise('WFNTF_NID_OPEN');
5391   end if;
5392 
5393 
5394   open attrs(nid);
5395   fetch attrs into result;
5396   if (attrs%found) then
5397   -- Check response required?
5398     wf_core.token('NID', Wf_Notification.GetSubject(nid));
5399     wf_core.raise('WFNTF_NID_REQUIRE');
5400   end if;
5401 
5402   -- Mark notification closed
5403   update WF_NOTIFICATIONS
5404   set STATUS = 'CLOSED',
5405       END_DATE = sysdate,
5406       RESPONDER = close.responder
5407   where NOTIFICATION_ID = nid;
5408 
5409 
5410   -- Remove any messages from the outbound queue
5411   -- GK: 1636402: wf_xml.RemoveNotification is not necessary
5412   -- since the message is likely to be sent by the time the
5413   -- user goes in and does an action from the worklist.
5414   -- wf_xml.RemoveNotification(nid);
5415 
5416   --Bug 2283697
5417   --To raise an EVENT whenever DML operation is performed on
5418   --WF_NOTIFICATIONS and WF_NOTIFICATION_ATTRIBUTES table.
5419   wf_event.AddParameterToList('NOTIFICATION_ID',nid,l_parameterlist);
5420   wf_event.AddParameterToList('RESPONDER',close.responder,l_parameterlist);
5421 
5422   -- AppSearch
5423   wf_event.AddParameterToList('OBJECT_NAME',
5424   'oracle.apps.fnd.wf.worklist.server.AllNotificationsVO', l_parameterlist);
5425   wf_event.AddParameterToList('CHANGE_TYPE', 'INSERT',l_parameterlist);
5426   wf_event.AddParameterToList('ID_TYPE', 'PK', l_parameterlist);
5427   wf_event.addParameterToList('PK_NAME_1', 'NOTIFICATION_ID',l_parameterlist);
5428   wf_event.addParameterToList('PK_VALUE_1', nid, l_parameterlist);
5429   wf_event.addParameterToList('PK_NAME_2', 'LANGUAGE',l_parameterlist);
5430   wf_event.addParameterToList('PK_VALUE_2', l_language, l_parameterlist);
5431 
5432   --Raise the event
5433   wf_event.Raise(p_event_name => 'oracle.apps.wf.notification.close',
5434                  p_event_key  => to_char(nid),
5435                  p_parameters => l_parameterlist);
5436 
5437 exception
5438   when others then
5439     wf_core.context('Wf_Notification', 'Close', to_char(nid), responder);
5440     raise;
5441 end Close;
5442 
5443 --
5444 -- GetSubSubjectDisplay
5445 --   Get the design subject of a notification and Substitute tokens in text
5446 --   with the display name of the attributes in the subject.
5447 --   This is used in routing rule poplists
5448 -- IN:
5449 --   message_type - Item type of the message
5450 --   message_name - Name of the message to substitute
5451 --
5452 function GetSubSubjectDisplay(message_type IN VARCHAR2, message_name IN VARCHAR2)
5453 return varchar2 is
5454 
5455   local_text varchar2(2000);
5456 
5457   -- Select attr values, formatting numbers and dates as requested.
5458   -- The order-by is to handle cases where one attr name is a substring
5459   -- of another.
5460   cursor message_attrs_cursor(c_message_type VARCHAR2,
5461                               c_message_name VARCHAR2) is
5462     select WMA.NAME, WMA.DISPLAY_NAME, WMA.TYPE
5463     from WF_MESSAGE_ATTRIBUTES_VL WMA
5464     where WMA.MESSAGE_TYPE = c_message_type
5465     and WMA.MESSAGE_NAME = c_message_name
5466     order by length(WMA.NAME) desc;
5467 
5468 begin
5469 
5470   -- Get the message subject
5471   SELECT SUBJECT
5472   INTO   local_text
5473   FROM   wf_messages_vl
5474   WHERE  type = message_type
5475   AND    name = message_name;
5476 
5477   for msg_attr_row in message_attrs_cursor (message_type, message_name) loop
5478 
5479     --
5480     -- Substitute all occurrences of SEND tokens with values.
5481     -- Limit to 1950 chars to avoid value errors if substitution pushes
5482     -- it over the edge.
5483     -- Wanted to use '<' and '>' to denote substituted attribute but these
5484     -- characters are the tag markers in html and cause problems in the
5485     -- poplist
5486     --
5487     if (msg_attr_row.type = 'URL') then
5488 
5489       local_text := substrb(replace(local_text, '-&'||msg_attr_row.name||'-',
5490                             '['||msg_attr_row.display_name||']'), 1, 1950);
5491     else
5492 
5493       local_text := substrb(replace(local_text, '&'||msg_attr_row.name,
5494                             '[<I><B>'||msg_attr_row.display_name||'</B></I>]'), 1, 1950);
5495 
5496     end if;
5497 
5498   end loop;
5499 
5500   --
5501   -- Process special '#' internal tokens.  Supported tokens are:
5502   --  &#NID - Notification id
5503   --
5504   local_text := substrb(replace(local_text, '&'||'#NID',
5505                           '[<I><B>'||wf_core.translate('WF_NOTIFICATION_ID')||'</B></I>]'), 1, 1950);
5506 
5507 
5508   return(local_text);
5509 
5510 exception
5511   when others then
5512     return(local_text);
5513 end GetSubSubjectDisplay;
5514 
5515 --
5516 -- GetSubSubjectDisplayShort
5517 --   Get the design subject of a notification and Substitute tokens in text
5518 --   with ellipsis '...' in the subject.
5519 --   This is used in routing rule poplists for the new web screens
5520 -- IN:
5521 --   message_type - Item type of the message
5522 --   message_name - Name of the message to substitute
5523 --
5524 function GetSubSubjectDisplayShort(message_type IN VARCHAR2, message_name IN VARCHAR2)
5525 return varchar2 is
5526 
5527   local_text varchar2(2000);
5528 
5529   -- Select attr values, formatting numbers and dates as requested.
5530   -- The order-by is to handle cases where one attr name is a substring
5531   -- of another.
5532   cursor message_attrs_cursor(c_message_type VARCHAR2,
5533                               c_message_name VARCHAR2) is
5534     select WMA.NAME, WMA.DISPLAY_NAME, WMA.TYPE
5535     from WF_MESSAGE_ATTRIBUTES_VL WMA
5536     where WMA.MESSAGE_TYPE = c_message_type
5537     and WMA.MESSAGE_NAME = c_message_name
5538     order by length(WMA.NAME) desc;
5539 
5540 begin
5541 
5542   -- Get the message subject
5543   SELECT SUBJECT
5544   INTO   local_text
5545   FROM   wf_messages_vl
5546   WHERE  type = message_type
5547   AND    name = message_name;
5548 
5549   for msg_attr_row in message_attrs_cursor (message_type, message_name) loop
5550 
5551     --
5552     -- Substitute all occurrences of SEND tokens with values.
5553     -- Limit to 1950 chars to avoid value errors if substitution pushes
5554     -- it over the edge.
5555     -- Wanted to use '<' and '>' to denote substituted attribute but these
5556     -- characters are the tag markers in html and cause problems in the
5557     -- poplist
5558     --
5559     if (msg_attr_row.type = 'URL') then
5560 
5561       local_text := substrb(replace(local_text, '-&'||msg_attr_row.name||'-',
5562                             '['||msg_attr_row.display_name||']'), 1, 1950);
5563     else
5564 
5565       local_text := substrb(replace(local_text, '&'||msg_attr_row.name,
5566                             '...'), 1, 1950);
5567 
5568     end if;
5569 
5570   end loop;
5571 
5572   --
5573   -- Process special '#' internal tokens.  Supported tokens are:
5574   --  &#NID - Notification id
5575   --
5576   local_text := substrb(replace(local_text, '&'||'#NID',
5577                           '...'), 1, 1950);
5578 
5579 
5580   return(local_text);
5581 
5582 exception
5583   when others then
5584     return(local_text);
5585 end GetSubSubjectDisplayShort;
5586 
5587 -- PLSQL-Clob Procssing
5588 -----------------------------------------------------------
5589 --Name : WriteToClob (PUBLIC)
5590 --Desc : appends a string to the end of a clob.
5591 --note : the efficiency of clob manipulation makes is dubious.
5592 --       It is probably best to call this as infrequently as possible
5593 --       by concatenating the string as long as possible before hand.
5594 
5595 procedure WriteToClob  ( clob_loc      in out nocopy clob,
5596                          msg_string    in  varchar2) is
5597  pos integer;
5598  amt number;
5599 begin
5600 
5601    pos :=   dbms_lob.getlength(clob_loc) +1;
5602    amt := length(msg_string);
5603    dbms_lob.write(clob_loc,amt,pos,msg_string);
5604 
5605 exception
5606 when others then
5607     wf_core.context('WF_NOTIFICATION','WriteToClob');
5608     raise;
5609 end WriteToClob;
5610 
5611 --Name : GetFullBody (PUBLIC)
5612 --Desc : Gets full body of message with all PLSQLCLOB variables transalted.
5613 --       and returns the message in 32K chunks in the msgbody out variable.
5614 --       Call this repeatedly until end_of_body is true.
5615 --       Call syntax is
5616 --while not (end_of_msgbody) loop
5617 --   wf_notification.getfullbody(nid,msgbody,end_of_msgbody);
5618 --end loop;
5619 procedure GetFullBody (nid in number,
5620                        msgbody  out nocopy varchar2,
5621                        end_of_body in out nocopy boolean,
5622                        disptype in varchar2) is
5623 
5624  buffer varchar2(200);
5625  buff_length number;
5626 
5627 
5628  msg varchar2(30);
5629  pos number;
5630  amt number;
5631  msg_body varchar2(3000);
5632 
5633  strt number;
5634  ampersand number;
5635  attr_name varchar2(30);
5636 begin
5637 
5638 -- if this is the same nid as was just used in this session,
5639 -- and the message is stored as a clob (so clob_exists is not null) then
5640 -- retrieve message from the temp clob.
5641 
5642   if  nid = wf_notification.last_nid
5643   and disptype = wf_notification.last_disptype
5644   and wf_notification.clob_exists is not null then
5645      wf_notification.read_Clob(msgbody, end_of_body);
5646      return;
5647   end if;
5648 
5649   wf_notification.clob_exists :=null;
5650   wf_notification.last_nid:=nid;
5651   wf_notification.last_disptype:=disptype;
5652   plsql_clob_exists:=null;
5653 
5654   msgbody := wf_notification.getbody(nid,disptype);
5655 
5656   if msgbody is null
5657   or instr(msgbody,'&') = 0
5658   or plsql_clob_exists is null then
5659 
5660    end_of_body := TRUE;
5661 
5662   else
5663 
5664    strt:=1;
5665 
5666    wf_notification.newclob(wf_notification.temp_clob,null);
5667    wf_notification.clob_exists :=1;
5668 
5669    loop
5670 
5671       attr_name := null;
5672       ampersand := instr(msgbody,'&',strt);
5673 
5674 
5675       if ampersand = 0 then
5676          if strt <= length(msgbody) then
5677 
5678             wf_notification.WriteToClob(wf_notification.temp_clob,
5679                                        substr(msgbody,strt,length(msgbody)));
5680          end if;
5681          exit;
5682       end if;
5683 
5684       -- If the token starts at the first character of the message body, we
5685       -- would encounter an error in our logic.  1 - 1 is 0 so the call to
5686       -- substr would fail.
5687 
5688       if ((ampersand - strt) > 0) then
5689          wf_notification.writeToClob(wf_notification.temp_clob,
5690                                          substr(msgbody,strt,ampersand-strt));
5691 
5692       end if;
5693 
5694       -- 2691290 if the '&' is at the end of the body the notification
5695       -- will error when calling GetAttrClob API for "Invalid values for
5696       -- Arguments".
5697       if (substr(msgbody,ampersand+1,30) is not null) then
5698         wf_notification.getattrclob(nid,substr(msgbody,ampersand+1,30),disptype,
5699                     wf_notification.temp_clob , attr_name);
5700       end if;
5701 
5702 
5703       if attr_name is not null then
5704          --it was already written to clob.
5705          strt := ampersand + 1 + length(attr_name);
5706       else
5707          --the string was not a plsqlclob
5708          wf_notification.writeToClob(wf_notification.temp_clob,'&');
5709          strt := ampersand + 1;
5710       end if;
5711 
5712    end loop;
5713 
5714 
5715    --set the clob chunk to zero. then request the next chunk in the msgbody
5716    wf_notification.clob_chunk := 0;
5717    wf_notification.read_Clob(msgbody, end_of_body);
5718 
5719   end if;
5720 
5721 exception
5722    when others then
5723       wf_core.context('WF_NOTIFICATION','GetFullBody', 'nid => '||to_char(nid),
5724                       'disptype => '||disptype);
5725       raise;
5726 end GetFullBody;
5727 
5728 
5729 --Name: GetFullBodyWrapper (PUBLIC)
5730 --Desc : Gets full body of message with all PLSQLCLOB variables transalted.
5731 --       and returns the message in 32K chunks in the msgbody out variable.
5732 --       Call this repeatedly until end_of_body is "Y". Uses string arg
5733 --       instead of boolean like GetFullBody for end_of_msg_body
5734 --       Call syntax is
5735 --while (end_of_msgbody <> 'Y') loop
5736 --   wf_notification.getfullbody(nid,msgbody,end_of_msgbody);
5737 --end loop;
5738 procedure GetFullBodyWrapper (nid in number,
5739                               msgbody  out nocopy varchar2,
5740                               end_of_body out nocopy varchar2,
5741                               disptype in varchar2)
5742 is
5743   end_of_body_b boolean;
5744 begin
5745   end_of_body_b := FALSE;
5746   WF_Notification.GetFullBody(nid, msgbody, end_of_body_b, disptype);
5747   if (end_of_body_b = TRUE) then
5748     end_of_body := 'Y';
5749   else
5750     end_of_body := 'N';
5751   end if;
5752 end GetFullBodyWrapper;
5753 
5754 --
5755 -- GetAttrClob
5756 --   Get the displayed value of a PLSQLCLOB DOCUMENT-type attribute.
5757 --   Returns referenced document in format requested.
5758 --   Use GetAttrText to get retrieve the actual attr value (i.e. the
5759 --   document key string instead of the actual document).
5760 -- NOTE:
5761 --   a. Only PLSQL document type is implemented.
5762 --   b. This will be called by old mailers. This is a wrapper to the
5763 --      new implementation which returns the doctype also.
5764 -- IN:
5765 --   nid      - Notification id
5766 --   astring  - the string to substitute on (ex: '&ATTR1 is your order..')
5767 --   disptype - Requested display type.  Valid values:
5768 --               wf_notification.doc_text - 'text/plain'
5769 --               wf_notification.doc_html - 'text/html'
5770 --   document - The clob into which
5771 --   aname    - Attribute Name (the first part of the string that matches
5772 --              the attr list)
5773 --
5774 procedure GetAttrClob(
5775   nid       in number,
5776   astring   in varchar2,
5777   disptype  in varchar2,
5778   document  in out nocopy clob,
5779   aname     out nocopy varchar2)
5780 is
5781   doctype varchar2(500);
5782 begin
5783 
5784   Wf_Notification.GetAttrClob(nid, astring, disptype, document, doctype, aname);
5785 
5786 exception
5787   when others then
5788     wf_core.context('Wf_Notification', 'oldGetAttrClob', to_char(nid), aname,
5789         disptype);
5790     raise;
5791 end GetAttrClob;
5792 
5793 --
5794 -- GetAttrClob
5795 --   Get the displayed value of a PLSQLCLOB DOCUMENT-type attribute.
5796 --   Returns referenced document in format requested.
5797 --   Use GetAttrText to get retrieve the actual attr value (i.e. the
5798 --   document key string instead of the actual document).
5799 -- NOTE:
5800 --   Only PLSQL document type is implemented.
5801 -- IN:
5802 --   nid      - Notification id
5803 --   astring  - the string to substitute on (ex: '&ATTR1 is your order..')
5804 --   disptype - Requested display type.  Valid values:
5805 --               wf_notification.doc_text - 'text/plain'
5806 --               wf_notification.doc_html - 'text/html'
5807 --   document - Th clob into which
5808 --   aname    - Attribute Name (the string that matches
5809 --              the attr list)
5810 --
5811 procedure GetAttrClob(
5812   nid       in  number,
5813   astring   in  varchar2,
5814   disptype  in  varchar2,
5815   document  in  out nocopy clob,
5816   doctype   out nocopy varchar2,
5817   aname     out nocopy varchar2)
5818 is
5819   key varchar2(4000);
5820   colon pls_integer;
5821   slash pls_integer;
5822   dmstype varchar2(30);
5823   display_name varchar2(80);
5824   procname varchar2(240);
5825   launch_url varchar2(4000);
5826   procarg varchar2(32000);
5827   username  varchar2(320);
5828 
5829   --curs integer;
5830   sqlbuf varchar2(2000);
5831   --rows integer;
5832 
5833   target   varchar2(240);
5834   l_charcheck   boolean;
5835 
5836 begin
5837 
5838   -- Check args
5839   if ((nid is null) or (astring is null) or
5840      (disptype not in (wf_notification.doc_text,
5841                        wf_notification.doc_html))) then
5842     wf_core.token('NID', to_char(nid));
5843     wf_core.token('ASTRING', aname);
5844     wf_core.token('DISPTYPE', disptype);
5845     wf_core.raise('WFSQL_ARGS');
5846   end if;
5847 
5848   -- of all the possible Document type matches,
5849   -- make sure its a PLSQLCLOB
5850     dmstype := '';
5851 
5852   -- Bug 6324545: Replaced the cursor with a simple sql to fetch a single row.
5853   begin
5854     -- <7443088> improved query
5855     select NAME into aname from
5856       (select WMA.NAME
5857        from WF_NOTIFICATIONS WN,
5858             WF_MESSAGE_ATTRIBUTES WMA,
5859             WF_NOTIFICATION_ATTRIBUTES NA
5860        where WN.NOTIFICATION_ID = nid
5861        and wn.notification_id = na.notification_id
5862        and wma.name = na.name
5863        and WN.MESSAGE_TYPE = WMA.MESSAGE_TYPE
5864        and WN.MESSAGE_NAME = WMA.MESSAGE_NAME
5865        and WMA.TYPE = 'DOCUMENT'
5866        and instr( upper(astring) ,wma.name) = 1
5867        and upper(na.text_value) like 'PLSQLCLOB:%'
5868        order by length(wma.name) desc)
5869        where rownum=1;
5870   exception
5871    when no_data_found then
5872       aname:=null;
5873      return;
5874   end;
5875 
5876    if (aname is not null) then
5877      -- Retrieve key string
5878      key := wf_notification.GetAttrText(nid, aname);
5879 
5880      -- If the key is empty then return a null string
5881      if (key is not null) then
5882 
5883        -- Parse doc mgmt system type from key
5884        colon := instr(key, ':');
5885        if ((colon <> 0) and (colon < 30)) then
5886           dmstype := upper(substr(key, 1, colon-1));
5887        end if;
5888      end if;
5889    end if;
5890 
5891   -- if we didnt find any plsqlclobs then exit now
5892   if dmstype is null or (dmstype <> 'PLSQLCLOB') then
5893      aname:=null;
5894      return;
5895   end if;
5896 
5897   -- We must be processing a CLOB PLSQL doc type
5898   slash := instr(key, '/');
5899   if (slash = 0) then
5900     procname := substr(key, colon+1);
5901     procarg := '';
5902   else
5903     procname := substr(key, colon+1, slash-colon-1);
5904     procarg := substr(key, slash+1);
5905   end if;
5906 
5907   -- Dynamic sql call to procedure
5908   -- bug 2706082 using execute immediate instead of dbms_sql.execute
5909 
5910   if (procarg is null) then
5911      procarg := NULL;
5912   else
5913      procarg := Wf_Notification.GetTextInternal(procarg, nid, target,
5914                                                 FALSE, FALSE, 'text/plain');
5915   end if;
5916 
5917   -- ### Review Note 1
5918   l_charcheck := wf_notification_util.CheckIllegalChar(procname);
5919   --Throw the Illegal exception when the check fails
5920 
5921     if (wf_log_pkg.level_statement >= fnd_log.g_current_runtime_level) then
5922       wf_log_pkg.string2(wf_log_pkg.level_statement,
5923                         'wf.plsql.wf_notification.GetAttrClob.plsqlclob_callout',
5924                         'Start executing PLSQLCLOB Doc procedure  - '||procname, true);
5925     end if;
5926 
5927     sqlbuf := 'begin '||procname||'(:p1, :p2, :p3, :p4); end;';
5928     execute immediate sqlbuf using
5929      in procarg,
5930      in disptype,
5931      in out document,
5932      in out doctype;
5933 
5934     if (wf_log_pkg.level_statement >= fnd_log.g_current_runtime_level) then
5935       wf_log_pkg.string2(wf_log_pkg.level_statement,
5936                         'wf.plsql.wf_notification.GetAttrClob.plsqlclob_callout',
5937                         'End executing PLSQLCLOB Doc procedure  - '||procname, false);
5938     end if;
5939 
5940 /*  curs := dbms_sql.open_cursor;
5941   if (procarg is null) then
5942       --force a null string
5943       sqlbuf := 'begin '||procname||'('''','''||disptype||
5944             ''', :document, :doctype); end;';
5945   else
5946       -- Substitute refs to other attributes in argument
5947       -- NOTE: There is a slight chance of recursive loop here,
5948       -- if the substituted string eventually contains a reference
5949       -- back to this same docattr.
5950 
5951       procarg := Wf_Notification.GetTextInternal(procarg, nid, target, FALSE,
5952                                                  FALSE);
5953 
5954       sqlbuf := 'begin '||procname||'('''||procarg||''', '''||disptype||
5955                  ''',:document, :doctype); end;';
5956   end if;
5957 
5958   dbms_sql.parse(curs, sqlbuf, dbms_sql.v7);
5959   dbms_sql.bind_variable(curs, ':document', document);
5960   dbms_sql.bind_variable(curs, ':doctype', doctype, 240);
5961   rows := dbms_sql.execute(curs);
5962   dbms_sql.variable_value(curs, ':document', document);
5963   dbms_sql.variable_value(curs, ':doctype', doctype);
5964   dbms_sql.close_cursor(curs);
5965 
5966   -- Translate doc types if needed
5967   Dont do this anymore. The plsql must take care of it.
5968   if ((disptype = wf_notification.doc_html) and
5969       (doctype = wf_notification.doc_text)) then
5970     -- Change plain text to html by wrapping in preformatted tags
5971     return('<PRE>'||document||'</PRE>');
5972   end if;
5973 */
5974 
5975 exception
5976   when others then
5977     -- Close cursor just in case
5978     /* if (dbms_sql.is_open(curs)) then
5979       dbms_sql.close_cursor(curs);
5980     end if; */
5981 
5982     wf_core.context('Wf_Notification', 'GetAttrClob', to_char(nid), aname,
5983         disptype);
5984     raise;
5985 end GetAttrClob;
5986 
5987 
5988 
5989 -- Name: NewClob
5990 -- Creates a new record in the temp table with a clob
5991 -- this is necessary because clobs cannot reside in plsql
5992 -- but must be part of a table.
5993 Procedure NewClob  (clobloc       in out nocopy clob,
5994 --                  --  clobid        in out nocopy number,
5995                     msg_string    in varchar2) is
5996  pos integer;
5997  amt number;
5998 
5999 begin
6000 
6001    -- Before allocating TEMP LOb we should check whether existing Locator
6002    -- is pointing to a locator and whether being freed .
6003    -- bug 6511028
6004    begin
6005      if(clobloc is not null) then
6006         dbms_lob.freeTemporary(clobloc);
6007      end if;
6008    exception
6009       WHEN OTHERS THEN
6010          null;  -- ignore ORA-22275 and other exceptions
6011    end;
6012 
6013    -- make clob temporary. this may impact the speed of the UI
6014    -- such that user has to wait to see the notification.
6015    -- To improve performance make sure buffer cache is well tuned.
6016    dbms_lob.createtemporary(clobloc, TRUE, DBMS_LOB.session);
6017 
6018    if msg_string is not null then
6019       pos := 1;
6020       amt := length(msg_string);
6021       dbms_lob.write(clobloc,amt,pos,msg_string);
6022    end if;
6023 
6024 exception
6025 when others then
6026     wf_core.context('WF_NOTIFICATION','NewClob');
6027     raise;
6028 end NewClob;
6029 
6030 --Name Read_Clob
6031 --reads a specific clob in 8K chunks. Call this repeatedly until
6032 --end_of_clob is true.
6033 procedure read_clob (line out nocopy varchar2 ,
6034                      end_of_clob in out nocopy boolean) is
6035 
6036  pos number;
6037  buff_length pls_integer:=8000;
6038 begin
6039 
6040    --linenbr is always one before the line to print.
6041    --it is incremented afterwards.
6042    pos:=(buff_length * nvl(wf_notification.clob_chunk,0)  ) +1;
6043 
6044    dbms_lob.read(wf_notification.temp_clob,buff_length,pos,line);
6045 
6046    if pos+buff_length > dbms_lob.getlength(wf_notification.temp_clob)  then
6047       end_of_clob := TRUE;
6048       wf_notification.clob_chunk  := 0;
6049    else
6050       wf_notification.clob_chunk  := wf_notification.clob_chunk +1;
6051    end if;
6052 
6053 exception
6054   when others then
6055     wf_core.context('Wf_Notification', 'Read_Clob','pos => '||to_char(pos),
6056                     'line => {'||line||'}');
6057     raise;
6058 end Read_Clob;
6059 
6060 --Name ReadAttrClob (PUBLIC)
6061 --Desc : Gets full text of a PLSQLCLOB variable
6062 --       and returns the 32K chunks in the doctext out variable.
6063 --       Call this repeatedly until end_of_text is true.
6064 --USE :  use this to get the value of idividual PLSQLCLOBs such as attachments.
6065 --       to susbtitute a PLSQLSQL clob into a message body, use GetFullBody
6066 
6067 procedure ReadAttrClob(nid         in number,
6068                        aname       in varchar2,
6069                        doctext     in out nocopy varchar2,
6070                        end_of_text in out nocopy boolean) is
6071 clob_id    number;
6072 attr_name varchar2(30);
6073 begin
6074    if  nid = wf_notification.last_nid
6075    and wf_notification.clob_exists is not null then
6076          wf_notification.read_Clob(doctext, end_of_text);
6077    else
6078       --create a clob
6079       wf_notification.newclob(wf_notification.temp_clob,null);
6080       wf_notification.clob_exists :=1;
6081 
6082       --set the clob text
6083       wf_notification.getattrclob(nid, aname,
6084           wf_notification.doc_html, wf_notification.temp_clob , attr_name);
6085 
6086       --retreive all the clob text in 32K chunks.
6087       if attr_name = aname then
6088          -- the attribute was substituted with something in the clob so print it.
6089          wf_notification.clob_chunk  := 0;
6090          wf_notification.read_Clob(doctext, end_of_text);
6091 
6092       else
6093          --the aname was not substituted so just print it.
6094           doctext := aname;
6095       end if;
6096 
6097       --finally set the global vars
6098       wf_notification.last_nid:=nid;
6099 
6100 
6101   end if;
6102 
6103 exception
6104   when others then
6105     wf_core.context('Wf_Notification', 'ReadAttrClob');
6106     raise;
6107 end ReadAttrClob;
6108 
6109 --
6110 -- Denormalization of Notifications
6111 --
6112 
6113 --
6114 -- GetSessionLanguage (PRIVATE)
6115 --   Try to return the cached session language value.
6116 --   If it is not cached yet, call the real query function.
6117 --
6118 function GetSessionLanguage
6119 return varchar2
6120 is
6121   l_lang  varchar2(64);
6122   l_terr  varchar2(64);
6123   l_chrs  varchar2(64);
6124 begin
6125   -- <7514495> no cached variable in Wf_Notification now?
6126 --  if (Wf_Notification.nls_language is not null) then
6127 --    return Wf_Notification.nls_language;
6128 --  end if;
6129 
6130   GetNLSLanguage(l_lang, l_terr, l_chrs);
6131   return l_lang;
6132 
6133 end GetSessionLanguage;
6134 
6135 --
6136 -- GetNLSLanguage (PRIVATE)
6137 --   Get the NLS Lanugage setting of current session
6138 --   Try to cached the value for future use.
6139 -- NOTE:
6140 --   Because it tried to use cached values first.  The subsequent calls
6141 -- will give you the cached values instead of the current value.
6142 --
6143 procedure GetNLSLanguage(language  out nocopy varchar2,
6144                          territory out nocopy varchar2,
6145                          charset   out nocopy varchar2)
6146 is
6147   tmpbuf  varchar2(240);
6148   pos1    number;        -- position for '_'
6149   pos2    number;        -- position for '.'
6150   l_nlsDateFormat varchar2(64);
6151   l_nlsDateLang varchar2(64);
6152   l_nlsNumChars varchar2(64);
6153   l_nlsSort varchar2(64);
6154   l_nlsCalendar varchar2(64);
6155 
6156 begin
6157   -- <7514495> now uses centralized api
6158 --  if (Wf_Notification.nls_language is null) then
6159 --    tmpbuf := userenv('LANGUAGE');
6160 --    pos1 := instr(tmpbuf, '_');
6161 --    pos2 := instr(tmpbuf, '.');
6162 --
6163 --    Wf_Notification.nls_language  := substr(tmpbuf, 1, pos1-1);
6164 --    Wf_Notification.nls_territory := substr(tmpbuf, pos1+1, pos2-pos1-1);
6165 --    Wf_Notification.nls_charset   := substr(tmpbuf, pos2+1);
6166 --  end if;
6167 --
6168 --  GetNLSLanguage.language  := Wf_Notification.nls_language;
6169 --  GetNLSLanguage.territory := Wf_Notification.nls_territory;
6170 --  GetNLSLanguage.charset   := Wf_Notification.nls_charset;
6171 
6172   wf_notification_util.getNLSContext( p_nlsLanguage=> GetNLSLanguage.language,
6173                            p_nlsTerritory => GetNLSLanguage.territory,
6174                            p_nlsCode       => GetNLSLanguage.charset,
6175                            p_nlsDateFormat => l_nlsDateFormat,
6176                            p_nlsDateLanguage => l_nlsDateLang,
6177                            p_nlsNumericCharacters => l_nlsNumChars,
6178                            p_nlsSort => l_nlsSort,
6179                            p_nlsCalendar => l_nlsCalendar);
6180 
6181 end GetNLSLanguage;
6182 
6183 
6184 
6185 --
6186 -- SetNLSLanguage (PRIVATE)
6187 --   Set the NLS Lanugage setting of current session
6188 --
6189 procedure SetNLSLanguage(p_language  in varchar2,
6190                          p_territory in varchar2)
6191 is
6192    l_language varchar2(30);
6193    l_territory varchar2(30);
6194 begin
6195   -- <7514495> now we use centralized api
6196 --  if (p_language = Wf_Notification.nls_language) then
6197 --     return;
6198 --  end if;
6199 --
6200 --  l_language := ''''||p_language||'''';
6201 --  l_territory := ''''||p_territory||'''';
6202 --
6203 --  DBMS_SESSION.SET_NLS('NLS_LANGUAGE', l_language);
6204 --  DBMS_SESSION.SET_NLS('NLS_TERRITORY', l_territory);
6205 --
6206 --  -- update cache
6207 --  Wf_Notification.nls_language := p_language;
6208 --  Wf_Notification.nls_territory := p_territory;
6209 
6210   wf_notification_util.SetNLSContext( -- p_nid  ,
6211                           p_nlsLanguage  => l_language,
6212                           p_nlsTerritory => p_territory
6213                           -- ok not to pass next parameters
6214                           -- as fnd_global.set_nls_context won't set
6215                           -- if null
6216 --                          p_nlsDateFormat ,
6217 --                          p_nlsDateLanguage ,
6218 --                          p_nlsNumericCharacters ,
6219 --                          p_nlsSort ,
6220 --                          p_nlsCalendar
6221                           );
6222 exception
6223   when others then
6224      Wf_Core.Context('Wf_Notification', 'SetNLSLanguage', p_language, p_territory);
6225      raise;
6226 end SetNLSLanguage;
6227 
6228 --
6229 -- Denormalize_Notification
6230 --   Populate the donormalized value to WF_NOTIFICATIONS table according
6231 -- to the language setting of username provided.
6232 -- IN:
6233 --   nid - Notification id
6234 --   username - optional role name, if not provided, use the
6235 --              recipient_role of the notification.
6236 --   langcode - language code
6237 --
6238 -- NOTE: username has precedence over langcode.  Either username or
6239 --       langcode is needed.
6240 --
6241 procedure Denormalize_Notification(nid      in number,
6242                                    username in varchar2,
6243                                    langcode in varchar2)
6244 is
6245   l_orig_lang varchar2(64);
6246   l_user      varchar2(320);
6247   l_from_role varchar2(320);
6248   l_from_user varchar2(360);
6249   l_to_user   varchar2(360);
6250   l_subject   varchar2(2000);
6251   l_language  varchar2(64);
6252   role_info_tbl wf_directory.wf_local_roles_tbl_type;
6253   l_territory varchar2(64);
6254   l_nls_date_format varchar2(64);
6255   l_nls_date_language varchar2(64);
6256   l_nls_calendar      varchar2(64);
6257   l_nls_numeric_characters varchar2(64);
6258   l_nls_sort   varchar2(64);
6259   l_nls_currency   varchar2(64);
6260 
6261   l_defer_denormalize boolean;
6262   l_parameterlist wf_parameter_list_t;
6263   l_orig_nlsterritory varchar2(64);
6264   l_orig_nlsCode varchar2(64);
6265   l_orig_nlsDateFormat varchar2(64);
6266   l_orig_nlsDateLang varchar2(64);
6267   l_orig_nlsNumChars varchar2(64);
6268   l_orig_nlsSort varchar2(64);
6269   l_orig_nlsCalendar varchar2(64);
6270   l_logSTMT boolean;
6271   l_logPRCD boolean;
6272   l_sessionUser varchar2(320);
6273   l_sessionUserInfo wf_directory.wf_local_roles_tbl_type;
6274   l_canDefer boolean;
6275   l_module       varchar2(100):=g_plsqlName|| 'Denormalize_Notification()';
6276 
6277 begin
6278     l_logPRCD := WF_LOG_PKG.LEVEL_PROCEDURE >= fnd_log.g_current_runtime_level;
6279     l_logSTMT := WF_LOG_PKG.LEVEL_STATEMENT >= fnd_log.g_current_runtime_level;
6280     if ( l_logPRCD ) then
6281       wf_log_pkg.String(wf_log_pkg.LEVEL_PROCEDURE, l_module
6282          , 'BEGIN, nid='||nid||', username='||username||', langcode='||langcode);
6283     end if;
6284 
6285   -- 8286459. Get value, and always reset flag;
6286   l_canDefer := wf_notification_util.g_allowDeferDenormalize;
6287   wf_notification_util.g_allowDeferDenormalize := true;
6288 
6289   -- <7720908>
6290   wf_notification_util.getNLSContext( l_orig_lang, l_orig_nlsterritory, l_orig_nlsCode
6291                          , l_orig_nlsDateFormat, l_orig_nlsDateLang
6292                          , l_orig_nlsNumChars, l_orig_nlsSort, l_orig_nlsCalendar);
6293 
6294   if (l_orig_nlsCalendar is null) then
6295     -- when null (typically, online session), get calendar from user prefs
6296     l_sessionUser := Wfa_Sec.GetUser();
6297     if ( l_logSTMT ) then
6298         wf_log_pkg.String(wf_log_pkg.LEVEL_STATEMENT, l_module
6299                                    , 'l_sessionUser=>'||l_sessionUser||'<');
6300     end if;
6301 
6302     if (l_sessionUser is not null) then
6303       Wf_Directory.GetRoleInfo2(l_sessionUser, l_sessionUserInfo);
6304       l_orig_nlsCalendar := l_sessionUserInfo(1).nls_calendar;
6305       if ( l_logSTMT ) then
6306           wf_log_pkg.String(wf_log_pkg.LEVEL_STATEMENT, l_module
6307                          , 'l_sessionUser calendar=>'||l_orig_nlsCalendar||'<');
6308       end if;
6309     end if;
6310   end if;
6311 
6312   l_defer_denormalize := FALSE;
6313   l_parameterlist := wf_parameter_list_t();
6314 
6315   -- if username is supplied, use the language setting of such user
6316   -- default to Recipient's setting if no valid language is found.
6317   begin
6318     if (username is not null) then
6319       Wf_Directory.GetRoleInfo2(username, role_info_tbl);
6320       l_language := role_info_tbl(1).language;
6321 
6322       -- <7514495> full NLS support
6323       l_territory := role_info_tbl(1).territory;
6324       l_nls_date_format  := role_info_tbl(1).nls_date_format;
6325       l_nls_date_language := role_info_tbl(1).nls_date_language;
6326       l_nls_calendar  := role_info_tbl(1).nls_calendar;
6327       l_nls_numeric_characters  := role_info_tbl(1).nls_numeric_characters;
6328       l_nls_sort  := role_info_tbl(1).nls_sort;
6329       l_nls_currency   := role_info_tbl(1).nls_currency; -- </7514495>
6330 
6331       role_info_tbl.DELETE;
6332 
6333       if ( l_logSTMT ) then
6334         wf_log_pkg.String(wf_log_pkg.LEVEL_STATEMENT, l_module
6335            , 'Got '||username||'''s preferences (passed by caller), lang='||l_language);
6336       end if;
6337 
6338       -- check if user language match
6339       if (l_orig_lang <> l_language) then
6340           return;
6341       end if;
6342 
6343     elsif (langcode is not null) then
6344       -- check if langcode match
6345       if (langcode <> userenv('LANG')) then
6346           if ( l_logPRCD ) then
6347             wf_log_pkg.String(wf_log_pkg.LEVEL_PROCEDURE, l_module
6348                , 'END - did nothing, langcode <> session lang');
6349           end if;
6350           return;
6351       end if;
6352       select NLS_LANGUAGE
6353         into l_language
6354         from WF_LANGUAGES
6355        where CODE = langcode;
6356         -- ###         and INSTALLED_FLAG = 'Y';
6357         -- ### Maybe we do not need to restrict installed flag to be Y
6358     end if;
6359   exception
6360     when OTHERS then
6361       l_language := null;
6362   end;
6363 
6364   begin
6365     select RECIPIENT_ROLE, FROM_ROLE
6366       into l_user, l_from_role
6367       from WF_NOTIFICATIONS
6368      where NOTIFICATION_ID = nid;
6369   exception
6370     when NO_DATA_FOUND then
6371       wf_core.token('NID', to_char(nid));
6372       wf_core.raise('WFNTF_NID');
6373   end;
6374 
6375   if ( l_logSTMT ) then
6376     wf_log_pkg.String(wf_log_pkg.LEVEL_STATEMENT, l_module
6377        , 'Getting '||l_user||'''s (recipient) preferences');
6378   end if;
6379 
6380   Wf_Directory.GetRoleInfo2(l_user, role_info_tbl);
6381   -- in most cases, l_language should be null and we use the language setting
6382   -- of the recipient role.
6383   if (l_language is null) then
6384     l_language := role_info_tbl(1).language;
6385 
6386     -- <7514495> full NLS support
6387     l_territory := role_info_tbl(1).territory;
6388     l_nls_date_format  := role_info_tbl(1).nls_date_format;
6389     l_nls_date_language := role_info_tbl(1).nls_date_language;
6390     l_nls_calendar  := role_info_tbl(1).nls_calendar;
6391     l_nls_numeric_characters  := role_info_tbl(1).nls_numeric_characters;
6392     l_nls_sort  := role_info_tbl(1).nls_sort;
6393     l_nls_currency   := role_info_tbl(1).nls_currency; -- </7514495>
6394 
6395     -- 8286459
6396     if l_canDefer and
6397        ((l_orig_lang          <> l_language) or
6398         (l_orig_nlsDateFormat <> l_nls_date_format) or
6399         (l_orig_nlsCalendar   <> l_nls_calendar)
6400       ) then
6401       -- do not do anything if the NLS settings do not match
6402       l_defer_denormalize := TRUE;
6403 
6404       if ( l_logSTMT ) then
6405           wf_log_pkg.String(wf_log_pkg.LEVEL_STATEMENT, l_module
6406                , 'l_orig_lang:>'|| l_orig_lang ||'<, l_language:>'||l_language);
6407           wf_log_pkg.String(wf_log_pkg.LEVEL_STATEMENT, l_module
6408                , 'l_orig_nlsDateFormat:>'||l_orig_nlsDateFormat ||'<, l_nls_date_format:>'||l_nls_date_format);
6409           wf_log_pkg.String(wf_log_pkg.LEVEL_STATEMENT, l_module
6410                , 'l_orig_nlsDateLang:>'||l_orig_nlsDateLang ||'<, l_nls_date_language:>'||l_nls_date_language);
6411           wf_log_pkg.String(wf_log_pkg.LEVEL_STATEMENT, l_module
6412                , 'l_orig_nlsCalendar:>'|| l_orig_nlsCalendar ||'<, l_nls_calendar:>'||l_nls_calendar);
6413           wf_log_pkg.String(wf_log_pkg.LEVEL_STATEMENT, l_module
6414                , 'l_orig_nlsterritory:>'|| l_orig_nlsterritory ||'<, l_territory:>'||l_territory);
6415           wf_log_pkg.String(wf_log_pkg.LEVEL_STATEMENT, l_module
6416                , 'l_orig_nlsSort:>'||  l_orig_nlsSort||'<, l_nls_sort:>'||l_nls_sort);
6417       end if;
6418     end if;
6419   end if;
6420 
6421   if (l_defer_denormalize) then
6422 
6423     -- To raise an EVENT when l_defer_denormalize is set true
6424     wf_event.AddParameterToList('NOTIFICATION_ID',nid,l_parameterlist);
6425     wf_event.AddParameterToList('ROLE',l_user,l_parameterlist);
6426     wf_event.AddParameterToList('LANGUAGE',l_language,l_parameterlist);
6427     wf_event.AddParameterToList('TERRITORY',l_territory,l_parameterlist);
6428 
6429     -- <7514495>
6430     wf_event.AddParameterToList('NLS_DATE_FORMAT', l_nls_date_format,l_parameterlist);
6431     wf_event.AddParameterToList('NLS_DATE_LANGUAGE', l_nls_date_language,l_parameterlist);
6432     wf_event.AddParameterToList('NLS_CALENDAR', l_nls_calendar,l_parameterlist);
6433     wf_event.AddParameterToList('NLS_NUMERIC_CHARACTERS', l_nls_numeric_characters,l_parameterlist);
6434     wf_event.AddParameterToList('NLS_SORT', l_nls_sort,l_parameterlist);
6435     wf_event.AddParameterToList('NLS_CURRENCY', l_nls_currency,l_parameterlist);
6436     -- </7514495>
6437 
6438     if ( l_logSTMT ) then
6439       wf_log_pkg.String(wf_log_pkg.LEVEL_STATEMENT, l_module, 'defer parameters: '||
6440                           'nid('||nid||'), role('||l_user||'), language('||l_language||')');
6441       wf_log_pkg.String(wf_log_pkg.LEVEL_STATEMENT, l_module, 'territory('||l_territory||
6442                           '), date_format('||l_nls_date_format||'), date_language('||
6443                           l_nls_date_language||')');
6444       wf_log_pkg.String(wf_log_pkg.LEVEL_STATEMENT, l_module, 'calendar('||l_nls_calendar||
6445                        '), numeric_characters('||l_nls_numeric_characters||'), sort('||
6446                        l_nls_sort||'), currency('||l_nls_currency||')');
6447     end if;
6448     if ( l_logPRCD ) then
6449       wf_log_pkg.String(wf_log_pkg.LEVEL_PROCEDURE, l_module, 'END - deferring denormalization');
6450     end if;
6451 
6452     -- Raise the event
6453     wf_event.Raise(p_event_name => 'oracle.apps.wf.notification.denormalize',
6454                  p_event_key  => to_char(nid),
6455                  p_parameters => l_parameterlist);
6456 
6457     return;
6458   end if;
6459 
6460   if ( l_logSTMT ) then
6461       wf_log_pkg.String(wf_log_pkg.LEVEL_STATEMENT, l_module,
6462                 'Not deferring denormalization, so using session''s settings');
6463   end if;
6464 
6465   -- To User
6466   -- N.B.: substrb is used in stead of substr because 320 is a hard byte limit.
6467   --       substr in some characterset may return > 320 bytes.
6468   l_to_user := role_info_tbl(1).display_name;
6469   l_to_user := substrb(l_to_user,1,320);
6470 
6471   -- From User
6472   --  If FROM_ROLE has not been defined yet, we tried to draw this from
6473   --  #FROM_ROLE.
6474   if (l_from_role is NULL) then
6475     begin
6476       l_from_role := Wf_Notification.GetAttrText(nid, '#FROM_ROLE');
6477     exception
6478       when OTHERS then
6479         wf_core.clear;  -- clear the error stack
6480         l_from_role := NULL;
6481     end;
6482   end if;
6483 
6484   --  We need to make l_from_user consistant with l_from_role.
6485   if (l_from_role is not NULL) then
6486     l_from_user := Wf_Directory.GetRoleDisplayName(l_from_role);
6487     l_from_user := substrb(l_from_user, 1,320);
6488   end if;
6489 
6490 
6491   -- Subject
6492   -- skilaru 08-MAY-03 bug fix 2883247
6493   l_subject := Wf_Notification.GetSubject(nid, 'text/plain');
6494 
6495   if ( l_logSTMT ) then
6496       wf_log_pkg.String(wf_log_pkg.LEVEL_STATEMENT, l_module, 'subject('||l_subject||')');
6497   end if;
6498   -- Populate the notification values
6499   --
6500   begin
6501     update WF_NOTIFICATIONS
6502        set FROM_USER = l_from_user,
6503            FROM_ROLE = nvl(l_from_role,FROM_ROLE),
6504            TO_USER = l_to_user,
6505            SUBJECT = l_subject,
6506            LANGUAGE = userenv('LANG')
6507      where NOTIFICATION_ID = nid;
6508   exception
6509     when OTHERS then
6510       wf_core.token('NID', to_char(nid));
6511       wf_core.raise('WFNTF_DENORM_FAILED');
6512   end;
6513 
6514   if ( l_logPRCD ) then
6515       wf_log_pkg.String(wf_log_pkg.LEVEL_PROCEDURE, l_module, 'END');
6516   end if;
6517 exception
6518   when OTHERS then
6519     wf_core.context('Wf_Notification', 'Denormalize_Notification',
6520                     to_char(nid), username);
6521     raise;
6522 end Denormalize_Notification;
6523 
6524 --
6525 -- closeFYI
6526 --   Close FYI notifications that are not associated with an item.
6527 -- IN:
6528 --   itemtype - Item Type the notification belongs to.
6529 --   begindate  - Close FYI notifications that were opened on
6530 --                or before this date.
6531 --
6532 procedure closeFYI( itemtype in varchar2,
6533                     messageName in varchar2,
6534                     begindate in date) is
6535 
6536   xitemtype    varchar2(8);
6537   xmessageName varchar2(30);
6538 
6539   cursor fyiNid is
6540          select WN.NOTIFICATION_ID
6541          from   WF_NOTIFICATIONS WN
6542          where  MESSAGE_TYPE like xitemtype
6543          and    MESSAGE_NAME like xmessageName
6544          and    BEGIN_DATE<=begindate
6545          and    STATUS = 'OPEN'
6546          and not exists (
6547              select NULL
6548              from WF_MESSAGE_ATTRIBUTES WMA
6549              where WMA.MESSAGE_TYPE = WN.MESSAGE_TYPE
6550              and   WMA.MESSAGE_NAME = WN.MESSAGE_NAME
6551              and   WMA.SUBTYPE = 'RESPOND');
6552 
6553 
6554 begin
6555 
6556         xitemtype := nvl(itemtype, '%');
6557         xmessageName := nvl(messageName, '%');
6558 
6559         for c in fyiNid LOOP
6560 
6561             WF_NOTIFICATION.Close( c.NOTIFICATION_ID );
6562 
6563         end loop;
6564 
6565 end closeFYI;
6566 
6567 --
6568 -- NtfSignRequirementsMet (PUBLIC) (Bug 2698999)
6569 --   Checks if the notification's singature requirements are met
6570 -- IN
6571 --   nid - notification id
6572 -- OUT
6573 --   true - if the ntf is signed
6574 --   false - if the ntf is not signed
6575 --
6576 function NtfSignRequirementsMet(nid in number)
6577 return boolean is
6578   sig_policy    varchar2(100);
6579   sig_id        number;
6580   sig_status    number;
6581   creation_date date;
6582   signed_date   date;
6583   verified_date date;
6584   lastAttVal_date date;
6585   validated_date  date;
6586   ebuf          varchar2(4000);
6587   estack        varchar2(4000);
6588   l_attr_sigid  number;
6589   sig_required  Varchar2(1);
6590   fwk_sig_flavor   Varchar2(255);
6591   email_sig_flavor Varchar2(255);
6592   render_hint    Varchar2(255);
6593 
6594 begin
6595   -- get the signature policy for the notification
6596   -- wf_mail.GetSignaturePolicy(nid, sig_policy);
6597   begin
6598     sig_policy := Wf_Notification.GetAttrText(nid, '#WF_SIG_POLICY');
6599   exception
6600     when others then
6601       if (wf_core.error_name = 'WFNTF_ATTR') then
6602         wf_core.clear;
6603         sig_policy := 'DEFAULT';
6604       else
6605         raise;
6606       end if;
6607   end;
6608 
6609   /* check if signature is required for this sig_policy*/
6610   GetSignatureRequired(sig_policy, nid,sig_required, fwk_sig_flavor,
6611     email_sig_flavor, render_hint);
6612 
6613   If(sig_required = 'N') then
6614     return TRUE;
6615   Elsif(sig_required = 'Y') then
6616 
6617     -- bug 2779748: Cancelled Notification does not need to be signed
6618     begin
6619       if (WF_Notification.GetAttrText(nid, 'RESULT') = '#SIG_CANCEL') then
6620         return TRUE;
6621       end if;
6622     exception
6623       when others then
6624         if (wf_core.error_name = 'WFNTF_ATTR') then
6625           wf_core.clear;
6626         else
6627           raise;
6628         end if;
6629     end;
6630 
6631     sig_id := WF_DIG_SIG_EVIDENCE_STORE.GetMostRecentSigID('WF_NTF', nid);
6632     begin
6633        -- #WF_SIG_ID may be defined as text or number... Now both will work
6634        -- Eventually should use GetAttrNumber
6635        l_attr_sigid := to_number(Wf_Notification.GetAttrText(nid, '#WF_SIG_ID'));
6636     exception
6637       when others then
6638         if (wf_core.error_name = 'WFNTF_ATTR') then
6639           wf_core.clear;
6640           l_attr_sigid := -1;
6641         else
6642           raise;
6643         end if;
6644     end;
6645     if (sig_id = -1 or sig_id <> l_attr_sigid) then
6646        return FALSE;
6647     end if;
6648 
6649     WF_DIG_SIG_EVIDENCE_STORE.GetSigStatusInfo(sig_id, sig_status, creation_date,
6650                          signed_date, verified_date, lastAttVal_date, validated_date,
6651                          ebuf, estack);
6652     if (sig_status >= WF_DIGITAL_SECURITY_PRIVATE.STAT_AUTHORIZED) then
6653        return TRUE;
6654     end if;
6655     return FALSE;
6656   end if;
6657 
6658   -- Currently only two policies supported. For others assume it is signed
6659   return TRUE;
6660 
6661 exception
6662   when others then
6663     wf_core.context('Wf_Notification', 'NtfSignRequirementsMet', to_char(nid));
6664     raise;
6665 end NtfSignRequirementsMet;
6666 
6667 --
6668 -- Request More Info
6669 --
6670 
6671 --
6672 -- UpdateInfo
6673 --   non-null username: Ask this user for more information
6674 --   null username: Reply to the inquery
6675 --   comment could be question or answer
6676 -- NOTE:
6677 --   This is a Framework specific api.  Embedded version SHOULD NOT call
6678 -- this api.
6679 --   Because we cannot validate a session inside Framework, calling such
6680 -- api outside of Framework may produce erroneous result.
6681 -- IN
6682 --   nid - Notification Id
6683 --   username - User to whom the comment is intended
6684 --   comment - Comment text
6685 --   wl_user - Worklist user to whom the notfication belongs, in case a proxy is acting
6686 --   action_source - Source from where the call is made. Could be null or 'WA'
6687 --
6688 procedure UpdateInfo(nid      in number,
6689                      username in varchar2,
6690                      comment  in varchar2,
6691                      wl_user  in varchar2,
6692                      action_source in varchar2,
6693                      cnt      in number)
6694 is
6695   resource_busy exception;
6696   pragma exception_init(resource_busy, -00054);
6697 
6698   l_from_role  varchar2(320);
6699   replyby      varchar2(320);
6700   myusername   varchar2(320);
6701   mydispname   varchar2(360);
6702   l_messageType    varchar2(8);
6703   l_messageName    varchar2(30);
6704   l_groupId        number;
6705   l_parameterlist  wf_parameter_list_t := wf_parameter_list_t();
6706   mailpref	varchar2 (8);
6707   recipient_role VARCHAR2 (320);
6708   cb             varchar2(240);
6709   context varchar2(2000);
6710   sqlbuf varchar2(2000);
6711   tvalue varchar2(4000);
6712   nvalue number ;
6713   dvalue date ;
6714  --Bug 3827935
6715   l_charcheck boolean;
6716 
6717   --Bug 3065814
6718   l_recip_role  varchar2(320);
6719   l_orig_recip_role  varchar2(320);
6720   l_more_info_role  varchar2(320);
6721   l_question_role   varchar2(320);
6722   l_dummy varchar2(1);
6723 
6724   --Bug 5444378
6725   l_ruleAction varchar2(8);
6726   l_newRole    varchar2(2000);
6727   l_sysComment varchar2(320);
6728 
6729   l_language  varchar2(30);
6730   role_info_tbl  wf_directory.wf_local_roles_tbl_type;
6731 
6732   -- bug 7130745
6733   l_event_name varchar2(240);
6734 
6735   CURSOR rulecurs is
6736     select action, action_argument
6737 	 from wf_routing_rules
6738 	 where role = username
6739     and   nvl(message_type, l_messageType) = l_messageType
6740     and   nvl(message_name,l_messageName) = l_messageName
6741     and sysdate between nvl(begin_date, sysdate-1) and
6742                         nvl(end_date, sysdate+1);
6743 begin
6744 
6745   -- Framework has control of a session.
6746   -- We are not allowed to re-validate a session any more.  So we cannot
6747   -- use wfa_sec.GetSession() directly.
6748   myusername := wfa_sec.GetFWKUserName;
6749 
6750   -- Bug 3065814
6751   -- Set the global context variables to appropriate values for this mode
6752   if (action_source = 'WA') then
6753     -- Action is performed by a proxy on behalf of wl_user.
6754     g_context_proxy := myusername;
6755     g_context_user  := UpdateInfo.wl_user;
6756     -- In Answer mode, wl_user is the more_info_role and Fwk session user is the proxy_role
6757     myusername      := UpdateInfo.wl_user;
6758   else
6759     -- Action is performed by the recipient of the notification
6760     g_context_proxy := null;
6761     g_context_user  := myusername;
6762   end if;
6763 
6764   mydispname := Wf_Directory.GetRoleDisplayName(myusername);
6765   g_context_user_comment := updateinfo.comment;
6766 
6767   --Bug 3065814
6768   --Get the callback function
6769   SELECT    callback , context ,RECIPIENT_ROLE, ORIGINAL_RECIPIENT,
6770             MORE_INFO_ROLE ,from_role, message_type, message_name
6771   into      cb, context,l_recip_role , l_orig_recip_role,
6772             l_more_info_role, l_from_role, l_messageType, l_messageName
6773   FROM      wf_notifications
6774   WHERE     notification_id  = nid;
6775 
6776   g_context_recipient_role := l_recip_role;
6777   g_context_original_recipient:= l_orig_recip_role;
6778   g_context_from_role := l_from_role;
6779   --The new role will be different for 'ANSWER mode
6780   --we overwrite it there.
6781   g_context_new_role  := username;
6782   g_context_more_info_role  := l_more_info_role;
6783 
6784   -- If we are in a different Fwk session, need to clear Workflow PLSQL state
6785   if (not Wfa_Sec.CheckSession) then
6786     Wf_Global.Init;
6787   end if;
6788 
6789   -- question mode
6790   if (username is not null) then
6791     if (myusername = username) then
6792       --Bug 2474770
6793       --If the current user is the same as the one from
6794       --whom more-info is requested then raise the error
6795       --that you cannot ask for more info from yourself.
6796       wf_core.token('USER',username);
6797       wf_core.raise('WFNTF_INVALID_MOREINFO_REQUEST');
6798     else
6799        open rulecurs;
6800        fetch rulecurs INTO l_ruleAction,l_newRole;
6801        if rulecurs%NOTFOUND then
6802          l_ruleAction := 'NOOP';
6803        end if;
6804        close rulecurs;
6805 
6806        if l_ruleAction IN ('FORWARD','TRANSFER') then
6807 
6808           if l_newRole = myusername then
6809              wf_core.token('USER',myusername);
6810              wf_core.raise('WFNTF_INVALID_MOREINFO_REQUEST');
6811           elsif l_newRole = l_recip_role then
6812              wf_core.token('USER',l_recip_role);
6813              wf_core.raise('WFNTF_INVALID_MOREINFO_REQUEST');
6814           else
6815              -- Routing rule defined
6816              wf_core.token('ROLE', WF_Directory.GetRoleDisplayName(l_newRole));
6817              l_sysComment := wf_core.translate('WFNTF_AUTO_RESPONSE_TO_ROLE');
6818 
6819              if myusername is not null then
6820                 wf_notification.SetComments(nid, username, myusername, 'ANSWER', action_source, l_sysComment);
6821              else
6822                 wf_notification.SetComments(nid, username, l_recip_role, 'ANSWER', action_source, l_sysComment);
6823              end if;
6824 
6825              /* implement the above loop recursively */
6826              if (cnt > wf_notification.max_forward) then
6827                  -- it means max_forward must have been exceeded.  Treat as a loop error.
6828                  wf_core.token('NID', to_char(nid));
6829                  wf_core.raise('WFNTF_ROUTE_LOOP');
6830              end if;
6831              UpdateInfo(nid,l_newRole,comment,wl_user,action_source,cnt+1);
6832           end if;
6833        else
6834 
6835           if l_ruleAction = 'RESPOND' then
6836              l_sysComment := wf_core.translate('WFNTF_AUTO_RESPONSE');
6837              if myusername is not null then
6838                 wf_notification.SetComments(nid, username, myusername, 'ANSWER', action_source, l_sysComment);
6839              else
6840                 wf_notification.SetComments(nid, username, l_recip_role, 'ANSWER', action_source, l_sysComment);
6841              end if;
6842           end if;
6843           -- do not want it hung when some one is doing update.
6844           begin
6845             select MORE_INFO_ROLE
6846             into l_from_role
6847             from WF_NOTIFICATIONS
6848             where NOTIFICATION_ID = nid
6849             for update nowait;
6850           exception
6851             when NO_DATA_FOUND then
6852               null;
6853             when resource_busy then
6854               wf_core.raise('WFNTF_BEING_UPDATED');
6855               -- ### This notification is being updated currently, please
6856               -- ### try again in a brief moment.
6857           end;
6858 
6859           if (cb is not null) then
6860             tvalue := myusername;
6861             nvalue := nid;
6862             -- ### Review Note 2 - cb is from table
6863             --Check for bug#3827935
6864             l_charcheck := wf_notification_util.CheckIllegalChar(cb);
6865            --Throw the Illegal exception when the check fails
6866 
6867 
6868             -- BINDVAR_SCAN_IGNORE
6869              sqlbuf := 'begin '||cb||
6870                       '(:p1, :p2, :p3, :p4, :p5, :p6, :p7); end;';
6871              execute immediate sqlbuf using
6872               in 'QUESTION',
6873               in context,
6874               in l_dummy,
6875               in l_dummy,
6876               in out tvalue,
6877               in out nvalue,
6878               in out dvalue;
6879 
6880           end if;
6881 
6882           -- always allow question
6883           -- bug 2474562
6884           -- allows any role, as restriction is done in the pop list level
6885           -- if (IsValidInfoRole(nid,username)) then
6886 
6887           -- shanjgik 01-JUL-03 bug 2887130
6888           -- get mail preference of the user who will respond with more information
6889           mailpref := wf_notification.GetMailPreference (username, null, null);
6890 
6891           -- if there is a valid session, then we can update the FROM_ROLE
6892           -- and FROM_USER accurately.
6893           if (myusername is not null) then
6894             update WF_NOTIFICATIONS
6895             set MORE_INFO_ROLE = username,
6896                 FROM_USER = mydispname,
6897                 FROM_ROLE = myusername,
6898               MAIL_STATUS = decode (mailpref, 'QUERY', '',
6899                                     'SUMMARY', '',
6900                                     'SUMHTML','',
6901                                     'DISABLED', 'FAILED',
6902                                     null, '', 'MAIL')
6903             where NOTIFICATION_ID = nid;
6904 
6905           -- otherwise, we default to what it should be.  Unfortunately, if it
6906           -- is a group role, we will not be able to identify which member I am.
6907           else
6908             update WF_NOTIFICATIONS
6909             set MORE_INFO_ROLE = username,
6910                 FROM_USER = TO_USER,
6911                 FROM_ROLE = RECIPIENT_ROLE,
6912               MAIL_STATUS = decode (mailpref, 'QUERY', '',
6913                                     'SUMMARY', '',
6914                                     'SUMHTML','',
6915                                     'DISABLED', 'FAILED',
6916                                     null, '', 'MAIL')
6917           where NOTIFICATION_ID = nid;
6918           end if;
6919 
6920           Wf_Notification.SetComments(nid, myusername, username, 'QUESTION', action_source, substrb(comment,1,4000));
6921 
6922           -- LANGUAGE here is for FROM_USER which came from WF_NOTIFICATIONS above
6923           -- insert into WF_COMMENTS (
6924           --    NOTIFICATION_ID,
6925           --    FROM_ROLE,
6926           --    FROM_USER,
6927           --    COMMENT_DATE,
6928           --    ACTION,
6929           --    USER_COMMENT,
6930           --    LANGUAGE
6931           --  )
6932           --  select NOTIFICATION_ID,
6933           --         FROM_ROLE,
6934           --         FROM_USER,
6935           --         sysdate,
6936           --         'QUESTION',
6937           --         substrb(comment,1,4000),
6938           --         LANGUAGE
6939           --    from WF_NOTIFICATIONS
6940           --   where NOTIFICATION_ID = nid;
6941 
6942         -- bug 2474562
6943         -- else
6944         --   wf_core.token('ROLE',username);
6945         --   wf_core.raise('WFNTF_NOT_PARTICIPANTS');
6946         -- end if;
6947         end if;
6948     end if;
6949 
6950     -- if we are here, mean we are going to raise
6951     -- oracle.apps.wf.notification.question event.
6952     l_event_name := 'oracle.apps.wf.notification.question';
6953 
6954 
6955   -- answer mode
6956   -- NOTE: the language here is the language of the MORE_INFO_ROLE,
6957   --       no denormalization is needed here.
6958   else
6959     -- Do not allow reply when a question has not been asked, or it has
6960     -- already been answered.  In both cases, MORE_INFO_ROLE is set to null.
6961     -- Also acquire a row lock, so that we do not let multiple people to
6962     -- answer at the same time.
6963     begin
6964       select MORE_INFO_ROLE,Wf_Directory.GetRoleDisplayName(MORE_INFO_ROLE), RECIPIENT_ROLE,FROM_ROLE
6965         into l_from_role, replyby, recipient_role,l_question_role
6966         from WF_NOTIFICATIONS
6967        where NOTIFICATION_ID = nid
6968          and MORE_INFO_ROLE is not null
6969          for update nowait;
6970 
6971     exception
6972       when NO_DATA_FOUND then
6973         -- if it has no row, we cannot reply to this notification
6974         -- ### You cannot reply to a question that has not been asked
6975         -- ### or has already been answered.
6976           WF_MAIL.SendMoreInfoResponseWarning(nid);
6977           return;
6978       when resource_busy then
6979         wf_core.raise('WFNTF_BEING_UPDATED');
6980         -- ### This notification is being updated currently, please
6981         -- ### try again in a brief moment.
6982     end;
6983 
6984     if (myusername is not null and l_from_role <> myusername) then
6985       if (not Wf_Directory.IsPerformer(myusername, l_from_role)) then
6986         wf_core.token('ROLE',myusername);
6987         wf_core.raise('WFNTF_NOT_PARTICIPANTS');
6988       end if;
6989       l_from_role := myusername;
6990       replyby := mydispname;
6991     end if;
6992 
6993     if (cb is not null) then
6994       tvalue := myusername;
6995       nvalue := nid;
6996       g_context_new_role := l_question_role;
6997       -- ### Review Note 2 - cb is from table
6998       -- Check for bug#3827935
6999       l_charcheck := wf_notification_util.CheckIllegalChar(cb);
7000       --Throw the Illegal exception when the check fails
7001 
7002        -- BINDVAR_SCAN_IGNORE
7003        sqlbuf := 'begin '||cb||
7004                 '(:p1, :p2, :p3, :p4, :p5, :p6, :p7); end;';
7005        execute immediate sqlbuf using
7006         in 'ANSWER',
7007         in context,
7008         in l_dummy,
7009         in l_dummy,
7010         in out tvalue,
7011         in out nvalue,
7012         in out dvalue;
7013 
7014     end if;
7015 
7016     -- shanjgik 01-JUL-03 bug 2887130
7017     -- get the recipient's(one who requested more information) mail preference
7018     mailpref := wf_notification.GetMailPreference (recipient_role, null, null);
7019 
7020     update WF_NOTIFICATIONS
7021        set FROM_USER = replyby,
7022            FROM_ROLE = l_from_role,
7023            MORE_INFO_ROLE = null,
7024            MAIL_STATUS = decode (mailpref, 'QUERY', '',
7025                                  'SUMMARY', '',
7026                                  'SUMHTML','',
7027                                  'DISABLED', 'FAILED',
7028                                  null, '', 'MAIL')
7029        where NOTIFICATION_ID = nid;
7030 
7031     Wf_Notification.SetComments(nid, l_from_role, recipient_role, 'ANSWER', action_source, substrb(comment,1,4000));
7032     Wf_Notification.Route(nid, 0);
7033 
7034     -- LANGUAGE here is for FROM_USER which came from GetRoleDisplayName above,
7035     -- so the LANGUAGE should be current userenv('LANG').
7036     -- insert into WF_COMMENTS (
7037     --      NOTIFICATION_ID,
7038     --      FROM_ROLE,
7039     --      FROM_USER,
7040     --      COMMENT_DATE,
7041     --      ACTION,
7042     --      USER_COMMENT,
7043     --      LANGUAGE
7044     --    )
7045     --    select NOTIFICATION_ID,
7046     --           FROM_ROLE,
7047     --           FROM_USER,
7048     --           sysdate,
7049     --           'ANSWER',
7050     --           substrb(comment,1,4000),
7051     --           userenv('LANG')
7052     --      from WF_NOTIFICATIONS
7053     --     where NOTIFICATION_ID = nid;
7054 
7055     -- <<BUG 7130745>>
7056     -- if we are here, mean we are going to raise
7057     -- oracle.apps.wf.notification.answer event.
7058     l_event_name := 'oracle.apps.wf.notification.answer';
7059 
7060   end if;
7061 
7062   -- Send the notification through email
7063   -- wf_xml.EnqueueNotification(nid);
7064 
7065   -- Bug 2283697
7066   -- To raise an EVENT whenever DML operation is performed on
7067   -- WF_NOTIFICATIONS and WF_NOTIFICATION_ATTRIBUTES table.
7068   wf_event.AddParameterToList('NOTIFICATION_ID', nid, l_parameterlist);
7069   wf_event.AddParameterToList('ROLE', username, l_parameterlist);
7070   wf_event.AddParameterToList('GROUP_ID', nvl(l_groupId, nid), l_parameterlist);
7071   wf_event.addParameterToList('Q_CORRELATION_ID', l_messageType||':'||
7072                               l_messageName, l_parameterlist);
7073 
7074 
7075   Wf_Directory.GetRoleInfo2(l_recip_role, role_info_tbl);
7076   l_language := role_info_tbl(1).language;
7077 
7078   select code into l_language from wf_languages where nls_language = l_language;
7079 
7080   -- AppSearch
7081   wf_event.AddParameterToList('OBJECT_NAME',
7082   'oracle.apps.fnd.wf.worklist.server.AllNotificationsVO', l_parameterlist);
7083   wf_event.AddParameterToList('CHANGE_TYPE', 'INSERT',l_parameterlist);
7084   wf_event.AddParameterToList('ID_TYPE', 'PK', l_parameterlist);
7085   wf_event.addParameterToList('PK_NAME_1', 'NOTIFICATION_ID',l_parameterlist);
7086   wf_event.addParameterToList('PK_VALUE_1', nid, l_parameterlist);
7087   wf_event.addParameterToList('PK_NAME_2', 'LANGUAGE',l_parameterlist);
7088   wf_event.addParameterToList('PK_VALUE_2', l_language, l_parameterlist);
7089 
7090 
7091   -- Raise the event
7092   -- wf_event.Raise(p_event_name => 'oracle.apps.wf.notification.send',
7093   --               p_event_key  => to_char(nid),
7094   --               p_parameters => l_parameterlist);
7095 
7096   -- <<sstomar: bug 7130745 : use different event names for question and answer >>
7097   wf_event.Raise(p_event_name => l_event_name,
7098                  p_event_key  => to_char(nid),
7099                  p_parameters => l_parameterlist);
7100 
7101 exception
7102   when OTHERS then
7103     Wf_Core.Context('Wf_Notification', 'UpdateInfo', to_char(nid), username, wl_user, action_source);
7104     raise;
7105 end UpdateInfo;
7106 
7107 -- bug 2474562
7108 -- deprecate - this api is no longer needed, keeps this for reference only
7109 --
7110 -- IsValidInfoRole
7111 --   Check to see if a role is a participant so far
7112 function IsValidInfoRole(nid      in number,
7113                          username in varchar2)
7114 return boolean
7115 is
7116   itype varchar2(30);
7117   ikey  varchar2(240);
7118   ans   number;
7119 begin
7120   begin
7121     -- 99% of the case, it should be found in WIAS
7122     select ITEM_TYPE, ITEM_KEY
7123       into itype, ikey
7124       from WF_ITEM_ACTIVITY_STATUSES
7125      where NOTIFICATION_ID = nid;
7126   exception
7127     when NO_DATA_FOUND then
7128       begin
7129         -- rarely the nid is from WIASH, but just in case
7130         select ITEM_TYPE, ITEM_KEY
7131           into itype, ikey
7132           from WF_ITEM_ACTIVITY_STATUSES_H
7133          where NOTIFICATION_ID = nid;
7134       exception
7135         when NO_DATA_FOUND then
7136           -- Notification only
7137           begin
7138             select NULL, '#SYNCH'
7139               into itype, ikey
7140               from WF_NOTIFICATIONS
7141              where NOTIFICATION_ID = nid;
7142           exception
7143              when OTHERS then
7144                return(FALSE);
7145           end;
7146         when OTHERS then
7147           return(FALSE);
7148       end;
7149     when OTHERS then
7150       return(FALSE);
7151   end;
7152 
7153   -- check if this is item owner
7154   begin
7155     select 1 into ans
7156       from WF_ITEMS
7157      where ITEM_TYPE = itype
7158        and ITEM_KEY  = ikey
7159        and OWNER_ROLE = IsValidInfoRole.username;
7160 
7161     return(TRUE);
7162   exception
7163     when NO_DATA_FOUND then
7164       null;
7165   end;
7166 
7167   if (itype is null and ikey = '#SYNCH') then
7168     -- this is notification only
7169     begin
7170       select 1 into ans
7171         from WF_NOTIFICATIONS
7172        where IsValidInfoRole.username in (RECIPIENT_ROLE, ORIGINAL_RECIPIENT)
7173          and NOTIFICATION_ID = nid;
7174     exception
7175       when NO_DATA_FOUND then
7176         return(FALSE);
7177     end;
7178   else
7179     -- this is notification from a flow
7180     begin
7181       -- NOTE
7182       -- The following sql is suggested by Deb in the performance team
7183       -- it uses the index on group_id which is much more selective
7184       -- than those on recipient_role or original_recipient.
7185       -- Even though the explain plan seems to indicate a high cost, the
7186       -- run time performance on volumn database is much better.
7187       select 1 into ans
7188       from (
7189       select  /*+  leading(grp_id_view)  */
7190              RECIPIENT_ROLE , ORIGINAL_RECIPIENT
7191              from WF_NOTIFICATIONS a ,
7192                       ( select notification_id group_id
7193                          from WF_ITEM_ACTIVITY_STATUSES
7194                          where item_type = itype
7195                          and item_key = ikey
7196                          union all
7197                          select notification_id group_id
7198                          from WF_ITEM_ACTIVITY_STATUSES_H
7199                          where item_type = itype
7200                          and item_key = ikey
7201                        )  grp_id_view
7202            where grp_id_view.group_id = a.group_id
7203          )  recipient_view
7204       where (recipient_view.RECIPIENT_ROLE = IsValidInfoRole.username
7205              or recipient_view.ORIGINAL_RECIPIENT = IsValidInfoRole.username)
7206         and rownum < 2;
7207     exception
7208       when NO_DATA_FOUND then
7209         return(FALSE);
7210     end;
7211   end if;
7212   return(TRUE);
7213 exception
7214   when OTHERS then
7215     Wf_Core.Context('Wf_Notification','IsValidInfoRole',to_char(nid),username);
7216     raise;
7217 end IsValidInfoRole;
7218 
7219 -- UpdateInfo2 - bug 2282139
7220 --   non-null username - Ask this user for more information
7221 --   null username - Reply to the inquery
7222 --   from email - from email id of responder/requestor
7223 --   comment -  could be question or answer
7224 -- NOTE:
7225 --   This is a WF Mailer specific API. Used when a user requests more info
7226 --   or provides info through email response.
7227 --
7228 procedure UpdateInfo2(nid        in number,
7229                       username   in varchar2,
7230                       from_email in varchar2,
7231                       comment    in varchar2)
7232 is
7233   resource_busy exception;
7234   pragma exception_init(resource_busy, -00054);
7235 
7236   l_from_role      varchar2(320);
7237   replyby          varchar2(320);
7238   myusername       varchar2(320);
7239   mydispname       varchar2(360);
7240   l_messageType    varchar2(8);
7241   l_messageName    varchar2(30);
7242   l_groupId        number;
7243   l_parameterlist  wf_parameter_list_t := wf_parameter_list_t();
7244   role_info_tbl wf_directory.wf_local_roles_tbl_type;
7245   l_username       varchar2(320);
7246   l_stat           varchar2(8);
7247 
7248   --Bug 3065814
7249   l_recip_role  varchar2(320);
7250   l_orig_recip_role  varchar2(320);
7251   l_more_info_role  varchar2(320);
7252   cb             varchar2(240);
7253   context varchar2(2000);
7254   sqlbuf varchar2(2000);
7255   tvalue varchar2(4000);
7256   nvalue number;
7257   dvalue date;
7258   l_question_role   varchar2(320);
7259   l_found boolean;
7260   l_dummy varchar2(1);
7261   -- Bug 3827935
7262   l_charcheck boolean;
7263   l_language  varchar2(30);
7264 
7265   -- bug 7130745
7266   l_event_name varchar2(240);
7267 
7268 begin
7269   if (wf_log_pkg.level_procedure >= fnd_log.g_current_runtime_level) then
7270      wf_log_pkg.string(wf_log_pkg.level_procedure,
7271                       'wf.plsql.WF_NOTIFICATION.UpdateInfo2.Begin',
7272                       'NID: '||to_char(nid) ||', Username: '||username||
7273                       ' From: '||from_email);
7274   end if;
7275 
7276   -- Get notification related information
7277   SELECT callback, context, recipient_role, original_recipient,
7278          more_info_role, from_role, status, message_type, message_name
7279   INTO   cb, context, l_recip_role, l_orig_recip_role,
7280          l_more_info_role, l_from_role, l_stat, l_messageType, l_messageName
7281   FROM   wf_notifications
7282   WHERE  notification_id  = nid;
7283 
7284   -- Donot process the request if the notification is not open.
7285   if (l_stat <> 'OPEN') then
7286      if (wf_log_pkg.level_statement >= fnd_log.g_current_runtime_level) then
7287         wf_log_pkg.string(wf_log_pkg.level_statement,
7288                          'wf.plsql.WF_NOTIFICATION.UpdateInfo2.not_open',
7289                          'Notification '||to_char(nid)||' is not OPEN. Returning.');
7290      end if;
7291      return;
7292   end if;
7293 
7294   -- mailer doesnot have a valid user session. need to get
7295   -- the user name based on the from_email
7296   GetUserfromEmail(from_email, myusername, mydispname, l_found);
7297   if (l_found) then
7298      g_context_user  := myusername;
7299   else
7300      g_context_user  := 'email:' || myusername;
7301   end if;
7302 
7303   if (wf_log_pkg.level_statement >= fnd_log.g_current_runtime_level) then
7304     wf_log_pkg.string(wf_log_pkg.level_statement,
7305                       'wf.plsql.WF_NOTIFICATION.UpdateInfo2.got_user',
7306                       'Email: '||from_email||' User: '||myusername||' DispName: '||mydispname);
7307   end if;
7308 
7309   --Bug 3065814
7310   --Set the global context variables to appropriate values for this mode
7311   g_context_user_comment := updateinfo2.comment;
7312   g_context_recipient_role := l_recip_role;
7313   g_context_original_recipient:= l_orig_recip_role;
7314   g_context_from_role := l_from_role;
7315 
7316   -- The new role will be different for 'ANSWER mode we overwrite it there.
7317   g_context_new_role  := username;
7318   g_context_more_info_role  := l_more_info_role;
7319 
7320   -- question mode
7321   if (username is not null) then
7322 
7323     -- Check if the question is asked to a valid role
7324     --  i. The role should be valid within WF Directory Service.
7325     -- ii. We might also want to check if the user is a participant of the ntf??
7326 
7327     wf_directory.GetRoleInfo2(username, role_info_tbl);
7328     l_username := role_info_tbl(1).name;
7329 
7330     -- Check if it is a Display Name
7331     if (l_username is NULL) then
7332       begin
7333         SELECT name
7334         INTO   l_username
7335         FROM   wf_role_lov_vl
7336         WHERE  upper(display_name) = upper(username)
7337         AND    rownum = 1;
7338       exception
7339         when NO_DATA_FOUND then
7340            wf_core.token('ROLE', username);
7341            wf_core.raise('WFNTF_ROLE');
7342       end;
7343     end if;
7344 
7345     -- If the username was specified as display name, l_username would have the internal name
7346     if (l_username in (myusername, l_recip_role)) then
7347       -- If the current user is the same as the one from whom more-info is requested
7348       -- requested then raise the error that you cannot ask for more info from yourself.
7349       wf_core.token('USER',username);
7350       wf_core.raise('WFNTF_INVALID_MOREINFO_REQUEST');
7351     else
7352       -- do not want it hung when some one is doing update.
7353       begin
7354         select MORE_INFO_ROLE, MESSAGE_TYPE, MESSAGE_NAME, GROUP_ID
7355         into l_from_role, l_messageType, l_messageName, l_groupId
7356         from WF_NOTIFICATIONS
7357         where NOTIFICATION_ID = nid
7358         for update nowait;
7359       exception
7360         when NO_DATA_FOUND then
7361         null;
7362         when resource_busy then
7363           wf_core.raise('WFNTF_BEING_UPDATED');
7364       end;
7365 
7366       if (wf_log_pkg.level_statement >= fnd_log.g_current_runtime_level) then
7367          wf_log_pkg.string(wf_log_pkg.level_statement,
7368                           'wf.plsql.WF_NOTIFICATION.UpdateInfo2.question',
7369                           'Updating QUESTION');
7370       end if;
7371 
7372       if (cb is not null) then
7373         tvalue := myusername;
7374         nvalue := nid;
7375         -- ### Review Note 2 - cb is from table
7376 	       -- Check for bug#3827935
7377         l_charcheck := wf_notification_util.CheckIllegalChar(cb);
7378         --Throw the Illegal exception when the check fails
7379 
7380         -- BINDVAR_SCAN_IGNORE
7381          sqlbuf := 'begin '||cb||
7382                   '(:p1, :p2, :p3, :p4, :p5, :p6, :p7); end;';
7383          execute immediate sqlbuf using
7384           in 'QUESTION',
7385           in context,
7386           in l_dummy,
7387           in l_dummy,
7388           in out tvalue,
7389           in out nvalue,
7390           in out dvalue;
7391 
7392       end if;
7393 
7394       -- as we donot have a user session for the mailer, the only way to
7395       -- find FROM_ROLE and FROM_USER are through the from_addr. If the
7396       -- user name and display name are not available, email address is updated
7397      /* if (myusername is not null) then
7398         update WF_NOTIFICATIONS
7399         set MORE_INFO_ROLE = username,
7400             FROM_USER = mydispname,
7401             FROM_ROLE = myusername
7402         where NOTIFICATION_ID = nid;
7403 
7404       -- otherwise, we default to what it should be.  Unfortunately, if it
7405       -- is a group role, we will not be able to identify which member I am.
7406       else */
7407         update WF_NOTIFICATIONS
7408         set MAIL_STATUS = 'MAIL',
7409             MORE_INFO_ROLE = l_username,
7410             FROM_USER = TO_USER,
7411             FROM_ROLE = RECIPIENT_ROLE
7412       where NOTIFICATION_ID = nid;
7413       /*end if; */
7414 
7415       Wf_Notification.SetComments(nid, myusername, l_username, 'QUESTION', null, substrb(comment,1,4000));
7416 
7417       -- LANGUAGE here is for FROM_USER which came from WF_NOTIFICATIONS above
7418       -- insert into WF_COMMENTS (
7419       --    NOTIFICATION_ID,
7420       --    FROM_ROLE,
7421       --    FROM_USER,
7422       --    COMMENT_DATE,
7423       --    ACTION,
7424       --    USER_COMMENT,
7425       --    LANGUAGE
7426       --  )
7427       --  select NOTIFICATION_ID,
7428       --         FROM_ROLE,
7429       --         FROM_USER,
7430       --         sysdate,
7431       --         'QUESTION',
7432       --         substrb(comment,1,4000),
7433       --         LANGUAGE
7434       --    from WF_NOTIFICATIONS
7435       --   where NOTIFICATION_ID = nid;
7436 
7437     end if;
7438 
7439     -- <<sstomar: bug 7130745>>
7440     --  we are here, mean we are going to raise
7441     --  oracle.apps.wf.notification.question event.
7442     l_event_name := 'oracle.apps.wf.notification.question';
7443 
7444   -- answer mode
7445   -- NOTE: the language here is the language of the MORE_INFO_ROLE,
7446   --       no denormalization is needed here.
7447   else
7448     -- Do not allow reply when a question has not been asked, or it has
7449     -- already been answered.  In both cases, MORE_INFO_ROLE is set to null.
7450     -- Also acquire a row lock, so that we do not let multiple people to
7451     -- answer at the same time.
7452     begin
7453       select MORE_INFO_ROLE, Wf_Directory.GetRoleDisplayName(MORE_INFO_ROLE),
7454              MESSAGE_TYPE, MESSAGE_NAME, GROUP_ID , from_role
7455         into l_from_role, replyby, l_messageType, l_messageName, l_groupId, l_question_role
7456         from WF_NOTIFICATIONS
7457        where NOTIFICATION_ID = nid
7458          and MORE_INFO_ROLE is not null
7459          for update nowait;
7460 
7461     exception
7462       when NO_DATA_FOUND then
7463         -- if it has no row, we cannot reply to this notification
7464         -- ### You cannot reply to a question that has not been asked
7465         -- ### or has already been answered.
7466          WF_MAIL.SendMoreInfoResponseWarning(nid,from_email);
7467         return;
7468       when resource_busy then
7469         wf_core.raise('WFNTF_BEING_UPDATED');
7470     end;
7471 
7472     -- we donot validate the role, it may be email address. we donot want
7473     -- FROM_ROLE and FROM_USER to be NULL.
7474     l_from_role := myusername;
7475     replyby := mydispname;
7476     if (cb is not null) then
7477       tvalue := myusername;
7478       nvalue := nid;
7479       g_context_new_role := l_question_role;
7480       -- ### Review Note 2 - cb is from table
7481       -- BINDVAR_SCAN_IGNORE
7482       sqlbuf := 'begin '||cb||
7483                 '(:p1, :p2, :p3, :p4, :p5, :p6, :p7); end;';
7484       execute immediate sqlbuf using
7485         in 'ANSWER',
7486         in context,
7487         in l_dummy,
7488         in l_dummy,
7489         in out tvalue,
7490         in out nvalue,
7491         in out dvalue;
7492 
7493     end if;
7494 
7495     if (wf_log_pkg.level_statement >= fnd_log.g_current_runtime_level) then
7496        wf_log_pkg.string(wf_log_pkg.level_statement,
7497                         'wf.plsql.WF_NOTIFICATION.UpdateInfo2.answer',
7498                         'Updating ANSWER');
7499     end if;
7500 
7501     update WF_NOTIFICATIONS
7502        set MAIL_STATUS = 'MAIL',
7503            FROM_USER = replyby,
7504            FROM_ROLE = l_from_role,
7505            MORE_INFO_ROLE = null
7506        where NOTIFICATION_ID = nid;
7507 
7508     Wf_Notification.SetComments(nid, myusername, l_recip_role, 'ANSWER', null, substrb(comment,1,4000));
7509 
7510     -- LANGUAGE here is for FROM_USER which came from GetRoleDisplayName above,
7511     -- so the LANGUAGE should be current userenv('LANG').
7512     -- insert into WF_COMMENTS (
7513     --      NOTIFICATION_ID,
7514     --      FROM_ROLE,
7515     --      FROM_USER,
7516     --      COMMENT_DATE,
7517     --      ACTION,
7518     --      USER_COMMENT,
7519     --      LANGUAGE
7520     --    )
7521     --    select NOTIFICATION_ID,
7522     --           FROM_ROLE,
7523     --           FROM_USER,
7524     --           sysdate,
7525     --           'ANSWER',
7526     --           substrb(comment,1,4000),
7527     --           userenv('LANG')
7528     --      from WF_NOTIFICATIONS
7529     --     where NOTIFICATION_ID = nid;
7530 
7531     -- we are here, mean we are going to raise
7532     -- oracle.apps.wf.notification.answer event.
7533     l_event_name := 'oracle.apps.wf.notification.answer';
7534 
7535   end if;  -- End of Answer mode
7536 
7537   -- Send the notification through email
7538   -- Enqueuing has been moved to a subscription for forward
7539   -- compatability. The subscription need only be enabled to use
7540   -- the older mailer. The subscription will call the
7541   -- wf_xml.EnqueueNotification(nid);
7542 
7543   -- Bug 2283697
7544   -- To raise an EVENT whenever DML operation is performed on
7545   -- WF_NOTIFICATIONS and WF_NOTIFICATION_ATTRIBUTES table.
7546   wf_event.AddParameterToList('NOTIFICATION_ID', nid, l_parameterlist);
7547 
7548   -- username MAY be a display name
7549   wf_event.AddParameterToList('ROLE',  nvl(l_username, username), l_parameterlist);
7550   wf_event.AddParameterToList('GROUP_ID', nvl(l_groupId, nid), l_parameterlist);
7551   wf_event.addParameterToList('Q_CORRELATION_ID', l_messageType||':'||
7552                               l_messageName, l_parameterlist);
7553 
7554   Wf_Directory.GetRoleInfo2(l_recip_role, role_info_tbl);
7555   l_language := role_info_tbl(1).language;
7556 
7557   select code into l_language from wf_languages where nls_language = l_language;
7558 
7559 
7560   -- AppSearch
7561   wf_event.AddParameterToList('OBJECT_NAME',
7562   'oracle.apps.fnd.wf.worklist.server.AllNotificationsVO', l_parameterlist);
7563   wf_event.AddParameterToList('CHANGE_TYPE', 'INSERT',l_parameterlist);
7564   wf_event.AddParameterToList('ID_TYPE', 'PK', l_parameterlist);
7565   wf_event.addParameterToList('PK_NAME_1', 'NOTIFICATION_ID',l_parameterlist);
7566   wf_event.addParameterToList('PK_VALUE_1', nid, l_parameterlist);
7567   wf_event.addParameterToList('PK_NAME_2', 'LANGUAGE',l_parameterlist);
7568   wf_event.addParameterToList('PK_VALUE_2', l_language, l_parameterlist);
7569 
7570 
7571   -- Raise the event
7572   -- wf_event.Raise(p_event_name => 'oracle.apps.wf.notification.send',
7573   --               p_event_key  => to_char(nid),
7574   --               p_parameters => l_parameterlist);
7575 
7576   -- <<sstomar: bug 7130745 : use different event names for question and answer >>
7577   wf_event.Raise(p_event_name => l_event_name,
7578                  p_event_key  => to_char(nid),
7579                  p_parameters => l_parameterlist);
7580 
7581 exception
7582   when OTHERS then
7583     Wf_Core.Context('Wf_Notification', 'UpdateInfo2', to_char(nid), username, from_email);
7584     raise;
7585 end UpdateInfo2;
7586 
7587 
7588 -- UpdateInfoGuest:
7589 --                   Called for updating more info when access key is present,
7590 --                   responder to the request more info role as the user
7591 --                   is trying to respond to Request More Info as GUEST user
7592 --                   via E-mail without logging in.
7593 --   responder      - Responder to the request more info
7594 --   moreinfoanswer - answer to request more information
7595 --
7596 procedure UpdateInfoGuest(nid                in number,
7597                           moreinforesponder  in varchar2 default null,
7598                           moreinfoanswer     in varchar2 default null)
7599 is
7600   resource_busy exception;
7601   pragma exception_init(resource_busy, -00054);
7602 
7603   l_from_role      varchar2(320);
7604   l_recipient_role varchar2(320);
7605   replyby          varchar2(320);
7606   l_messageType    varchar2(8);
7607   l_messageName    varchar2(30);
7608   l_groupId        number;
7609   l_parameterlist  wf_parameter_list_t := wf_parameter_list_t();
7610 
7611   l_more_info_role  varchar2(320);
7612   cb             varchar2(240);
7613   context varchar2(2000);
7614   sqlbuf varchar2(2000);
7615   tvalue varchar2(4000);
7616   nvalue number;
7617   dvalue date;
7618   l_question_role   varchar2(320);
7619   l_dummy varchar2(1);
7620   l_orig_recip_role varchar2(320);
7621   l_language	    varchar2(30);
7622   role_info_tbl  wf_directory.wf_local_roles_tbl_type;
7623 
7624 begin
7625   wf_log_pkg.string(WF_LOG_PKG.LEVEL_UNEXPECTED, 'WF_NOTIFICATION.UpdateInfoGuest',
7626                         'NID: '||to_char(nid));
7627 
7628   -- Do not allow reply when a question has not been asked, or it has
7629   -- already been answered.  In both cases, MORE_INFO_ROLE is set to null.
7630   -- Also acquire a row lock, so that we do not let multiple people to
7631   -- answer at the same time.
7632   begin
7633     select ORIGINAL_RECIPIENT, RECIPIENT_ROLE, MORE_INFO_ROLE,
7634            Wf_Directory.GetRoleDisplayName(MORE_INFO_ROLE),
7635            MESSAGE_TYPE, MESSAGE_NAME, GROUP_ID , from_role, callback, context
7636       into l_orig_recip_role, l_recipient_role, l_from_role,
7637            replyby, l_messageType, l_messageName, l_groupId, l_question_role, cb, context
7638       from WF_NOTIFICATIONS
7639      where NOTIFICATION_ID = nid
7640        and MORE_INFO_ROLE is not null
7641        for update nowait;
7642 
7643     --Bug 3924931
7644     --Set the global context variables to appropriate values for this mode
7645     g_context_user := updateinfoguest.moreinforesponder;
7646     g_context_user_comment := updateinfoguest.moreinfoanswer;
7647     g_context_new_role  := l_question_role;
7648     g_context_recipient_role := l_recipient_role;
7649     g_context_original_recipient:= l_orig_recip_role;
7650     g_context_from_role := l_question_role;
7651     g_context_more_info_role  := l_from_role;
7652 
7653   exception
7654     when NO_DATA_FOUND then
7655       -- if it has no row, we cannot reply to this notification
7656       wf_core.raise('WFNTF_CANNOT_REPLY');
7657       -- ### You cannot reply to a question that has not been asked
7658       -- ### or has already been answered.
7659 
7660     when resource_busy then
7661       wf_core.raise('WFNTF_BEING_UPDATED');
7662   end;
7663 
7664   if (cb is not null) then
7665     tvalue := moreinforesponder;
7666     nvalue := nid;
7667     -- ### Note 2 - cb is from table
7668     -- BINDVAR_SCAN_IGNORE
7669     sqlbuf := 'begin '||cb||
7670               '(:p1, :p2, :p3, :p4, :p5, :p6, :p7); end;';
7671     execute immediate sqlbuf using
7672       in 'ANSWER',
7673       in context,
7674       in l_dummy,
7675       in l_dummy,
7676       in out tvalue,
7677       in out nvalue,
7678       in out dvalue;
7679 
7680   end if;
7681 
7682 
7683   wf_log_pkg.string(WF_LOG_PKG.LEVEL_UNEXPECTED, 'WF_NOTIFICATION.UpdateInfoGuest',
7684                    'Updating ANSWER');
7685 
7686   update WF_NOTIFICATIONS
7687      set MAIL_STATUS = 'MAIL',
7688          FROM_USER = moreinforesponder,
7689          FROM_ROLE = moreinforesponder,
7690          MORE_INFO_ROLE = null
7691    where NOTIFICATION_ID = nid;
7692 
7693   Wf_Notification.SetComments(nid, moreinforesponder, l_recipient_role, 'ANSWER', null, substrb(moreinfoanswer,1,4000));
7694   Wf_Notification.Route(nid, 0);
7695 
7696   -- Send the notification through email
7697   -- Enqueuing has been moved to a subscription for forward
7698   -- compatability. The subscription need only be enabled to use
7699   -- the older mailer. The subscription will call the
7700   -- wf_xml.EnqueueNotification(nid);
7701 
7702   -- Bug 2283697
7703   -- To raise an EVENT whenever DML operation is performed on
7704   -- WF_NOTIFICATIONS and WF_NOTIFICATION_ATTRIBUTES table.
7705   wf_event.AddParameterToList('NOTIFICATION_ID', nid, l_parameterlist);
7706   -- skilaru 12-MAR-04 In UpdateInfo2 username would be null in Answer mode
7707   -- to keep the behaviour same just pass null as ROLE..
7708   wf_event.AddParameterToList('ROLE', null, l_parameterlist);
7709   wf_event.AddParameterToList('GROUP_ID', nvl(l_groupId, nid), l_parameterlist);
7710   wf_event.addParameterToList('Q_CORRELATION_ID', l_messageType||':'||
7711                                l_messageName, l_parameterlist);
7712 
7713    Wf_Directory.GetRoleInfo2(l_recipient_role, role_info_tbl);
7714    l_language := role_info_tbl(1).language;
7715 
7716    select code into l_language from wf_languages where nls_language = l_language;
7717 
7718   -- AppSearch
7719   wf_event.AddParameterToList('OBJECT_NAME',
7720   'oracle.apps.fnd.wf.worklist.server.AllNotificationsVO', l_parameterlist);
7721   wf_event.AddParameterToList('CHANGE_TYPE', 'INSERT',l_parameterlist);
7722   wf_event.AddParameterToList('ID_TYPE', 'PK', l_parameterlist);
7723   wf_event.addParameterToList('PK_NAME_1', 'NOTIFICATION_ID',l_parameterlist);
7724   wf_event.addParameterToList('PK_VALUE_1', nid, l_parameterlist);
7725   wf_event.addParameterToList('PK_NAME_2', 'LANGUAGE',l_parameterlist);
7726   wf_event.addParameterToList('PK_VALUE_2', l_language, l_parameterlist);
7727 
7728 
7729   -- Raise the event
7730   -- wf_event.Raise(p_event_name => 'oracle.apps.wf.notification.send',
7731   --               p_event_key  => to_char(nid),
7732   --               p_parameters => l_parameterlist);
7733 
7734   -- <<sstomar: bug 7130745 : use different event names for question and answer >>
7735   wf_event.Raise(p_event_name => 'oracle.apps.wf.notification.answer',
7736                  p_event_key  => to_char(nid),
7737                  p_parameters => l_parameterlist);
7738 
7739 exception
7740   when OTHERS then
7741     Wf_Core.Context('Wf_Notification', 'UpdateInfoGuest', to_char(nid), moreinforesponder);
7742     raise;
7743 end UpdateInfoGuest;
7744 
7745 
7746 
7747 --
7748 -- HideMoreInfo (PUBLIC)
7749 --   Checks the notification attribute #HIDE_MOREINFO to see if the
7750 --   More Information request button is allowed or hidden. Just in case
7751 --   more_info_role becomes not null with direct table update...
7752 
7753 function HideMoreInfo(nid in number)
7754 return varchar2
7755 is
7756   l_hide  varchar2(1);
7757 begin
7758   -- Get value for #HIDE_MOREINFO attribute for the notification
7759   begin
7760      l_hide := substrb(WF_NOTIFICATION.GetAttrText(nid, '#HIDE_MOREINFO'), 1, 1);
7761      -- Bugfix 2880029 - changed sacsharm - 03/31/03
7762      -- Only if attribute value is explicitly 'Y' hide Request More Info. else
7763      -- if it is null or 'N' or any other character donot hide Request More Info.
7764      l_hide := upper(nvl(l_hide, 'N'));
7765      if (l_hide <> 'Y') then
7766          l_hide := 'N';
7767      end if;
7768   exception
7769      when others then
7770         -- Bugfix 2880029 - changed sacsharm - 03/31/03
7771         -- If attribute not defined, do not hide Request More Info.
7772         if (wf_core.error_name = 'WFNTF_ATTR') then
7773           wf_core.clear;
7774           l_hide := 'N';
7775         else
7776           raise;
7777         end if;
7778   end;
7779   return (l_hide);
7780 exception
7781   when others then
7782      wf_core.context('Wf_Notification', 'HideMoreInfo', to_char(nid));
7783      raise;
7784 end HideMoreInfo;
7785 
7786 -- GetComments
7787 --   Consolidates the questions and answers asked for the notification
7788 --   Also returns the last question asked.
7789 --   This is for the mailer to send the history with the email.
7790 --   It assumes that the table has been already opened
7791 -- IN
7792 --   nid - Notification id
7793 --   dislay_type The display type for the history
7794 -- OUT
7795 --   history in either text or html format
7796 --   last asked question
7797 procedure GetComments(nid          in  number,
7798                       display_type in varchar2,
7799                       html_history out nocopy varchar2,
7800                       last_ques    out nocopy varchar2)
7801 is
7802   CURSOR c_ques IS
7803   SELECT user_comment
7804   FROM   wf_comments
7805   WHERE  notification_id = nid
7806   AND    action = 'QUESTION'
7807   ORDER BY comment_date desc;
7808 begin
7809   open c_ques;
7810   fetch c_ques into last_ques;
7811   if (c_ques%notfound) then
7812     last_ques := '';
7813   end if;
7814   close c_ques;
7815 
7816   -- Call the GetComments2 procedure to get the Action History for only
7817   -- More Info Requests. This procedure was doing that previously
7818   Wf_Notification.GetComments2(p_nid => nid,
7819                                p_display_type => display_type,
7820                                p_hide_reassign => 'Y',
7821                                p_hide_requestinfo => 'N',
7822                                p_action_history => html_history);
7823 
7824 exception
7825   when others then
7826      wf_core.context('Wf_Notification', 'GetComments', to_char(nid), display_type);
7827      raise;
7828 end GetComments;
7829 
7830 --
7831 -- GetComments2
7832 --   Creates the Action History table for a given a notification id based on
7833 --   different filter criteria.
7834 -- IN
7835 --   p_nid - Notification id
7836 --   p_display_type - Display Type
7837 --   p_action_type - Action Type to look for (REASSIGN, RESPOND, QA,...)
7838 --   p_comment_date - Comment Date
7839 --   p_from_role - Comment provider
7840 --   p_to_role - Comment receiver
7841 --   p_hide_reassign - If Reassign comments be shown or not
7842 --   p_hide_requestinfo - If More Info request be shown or not
7843 -- OUT
7844 --   p_action_history - Action History table
7845 --
7846 procedure GetComments2(p_nid              in  number,
7847                        p_display_type     in  varchar2,
7848                        p_action_type      in  varchar2,
7849                        p_comment_date     in  date,
7850                        p_from_role        in  varchar2,
7851                        p_to_role          in  varchar2,
7852                        p_hide_reassign    in  varchar2,
7853                        p_hide_requestinfo in  varchar2,
7854                        p_action_history   out nocopy varchar2)
7855 is
7856    l_user_comment varchar2(4000);
7857    i              pls_integer;
7858    j              pls_integer;
7859    l_pos          pls_integer;
7860    l_table_dir    varchar2(1);
7861    l_dir          varchar2(10);
7862    cells          tdType;
7863    l_result       varchar2(32000);
7864    l_delim        varchar2(1);
7865    l_note         varchar2(4000);
7866    l_action       varchar2(30);
7867    l_item_type    varchar2(8);
7868    l_item_key     varchar2(240);
7869    l_actid        number;
7870    l_result_type  varchar2(30);
7871    l_result_code  varchar2(30);
7872    l_action_str   varchar2(250);
7873    l_wf_system    varchar2(360);
7874    l_count        number;
7875    l_suppress_hist  varchar2(1);
7876    l_title        varchar2(250);
7877 
7878    CURSOR c_comments IS
7879    select rownum H_SEQUENCE, H_NOTIFICATION_ID, H_FROM_USER, H_TO_USER, H_ACTION_TYPE, H_ACTION,
7880           H_COMMENT, H_ACTION_DATE from
7881    (select H_SEQUENCE, H_NOTIFICATION_ID, H_FROM_USER, H_TO_USER, H_ACTION_TYPE, H_ACTION,
7882            H_COMMENT, H_ACTION_DATE from
7883    (select
7884     99999999 H_SEQUENCE,
7885     IAS.NOTIFICATION_ID H_NOTIFICATION_ID,
7886     IAS.ASSIGNED_USER H_FROM_ROLE,
7887     wf_directory.getRoleDisplayName(IAS.ASSIGNED_USER) H_FROM_USER,
7888     'WF_SYSTEM' H_TO_ROLE,
7889     l_wf_system H_TO_USER,
7890     A.RESULT_TYPE H_ACTION_TYPE,
7891     IAS.ACTIVITY_RESULT_CODE H_ACTION,
7892     '#WF_NOTE#' H_COMMENT,
7893     nvl(IAS.END_DATE, IAS.BEGIN_DATE) H_ACTION_DATE
7894     from WF_ITEM_ACTIVITY_STATUSES IAS,
7895          WF_ACTIVITIES A,
7896          WF_PROCESS_ACTIVITIES PA,
7897          WF_ITEMS I
7898     where IAS.ITEM_TYPE           = l_item_type
7899       and IAS.ITEM_KEY            = l_item_key
7900       and IAS.PROCESS_ACTIVITY    = l_actid
7901       and IAS.ITEM_TYPE           = I.ITEM_TYPE
7902       and IAS.ITEM_KEY            = I.ITEM_KEY
7903       and IAS.ACTIVITY_RESULT_CODE IS NOT NULL
7904       and IAS.ACTIVITY_RESULT_CODE not in( '#EXCEPTION', '#FORCE', '#MAIL', '#NULL', '#STUCK', '#TIMEOUT')
7905       and I.BEGIN_DATE between A.BEGIN_DATE
7906       and nvl(A.END_DATE, I.BEGIN_DATE)
7907       and IAS.PROCESS_ACTIVITY    = PA.INSTANCE_ID
7908       and PA.ACTIVITY_NAME        = A.NAME
7909       and PA.ACTIVITY_ITEM_TYPE   = A.ITEM_TYPE
7910   union all
7911     select
7912     99999999 H_SEQUENCE,
7913     IAS.NOTIFICATION_ID H_NOTIFICATION_ID,
7914     IAS.ASSIGNED_USER H_FROM_ROLE,
7915     wf_directory.getRoleDisplayName(IAS.ASSIGNED_USER) H_FROM_USER,
7916     'WF_SYSTEM' H_TO_ROLE,
7917     l_wf_system H_TO_USER,
7918     A.RESULT_TYPE H_ACTION_TYPE,
7919     IAS.ACTIVITY_RESULT_CODE H_ACTION,
7920     '#WF_NOTE#' H_COMMENT,
7921     nvl(IAS.END_DATE, IAS.BEGIN_DATE) H_ACTION_DATE
7922     from WF_ITEM_ACTIVITY_STATUSES_H IAS,
7923          WF_ACTIVITIES A,
7924          WF_PROCESS_ACTIVITIES PA,
7925          WF_ITEMS I
7926     where IAS.ITEM_TYPE           = l_item_type
7927       and IAS.ITEM_KEY            = l_item_key
7928       and IAS.PROCESS_ACTIVITY    = l_actid
7929       and IAS.ITEM_TYPE           = I.ITEM_TYPE
7930       and IAS.ITEM_KEY            = I.ITEM_KEY
7931       and IAS.ACTIVITY_RESULT_CODE IS NOT NULL
7932       and IAS.ACTIVITY_RESULT_CODE not in( '#EXCEPTION', '#FORCE', '#MAIL', '#NULL', '#STUCK', '#TIMEOUT')
7933       and I.BEGIN_DATE between A.BEGIN_DATE
7934       and nvl(A.END_DATE, I.BEGIN_DATE)
7935       and IAS.PROCESS_ACTIVITY    = PA.INSTANCE_ID
7936       and PA.ACTIVITY_NAME        = A.NAME
7937       and PA.ACTIVITY_ITEM_TYPE   = A.ITEM_TYPE
7938     union all
7939      select C.SEQUENCE H_SEQUENCE,
7940             C.NOTIFICATION_ID H_NOTIFICATION_ID,
7941             C.FROM_ROLE H_FROM_ROLE,
7942             C.FROM_USER H_FROM_USER,
7943             C.TO_ROLE H_TO_ROLE,
7944             C.TO_USER H_TO_USER,
7945             '#WF_COMMENTS#' H_ACTION_TYPE,
7946             C.ACTION  H_ACTION,
7947             C.USER_COMMENT H_COMMENT,
7948             C.COMMENT_DATE H_ACTION_DATE
7949        from WF_ITEM_ACTIVITY_STATUSES IAS,
7950             WF_COMMENTS C
7951       where IAS.ITEM_TYPE = l_item_type
7952         and IAS.ITEM_KEY = l_item_key
7953         and IAS.PROCESS_ACTIVITY = l_actid
7954         and IAS.NOTIFICATION_ID = C.NOTIFICATION_ID
7955         and C.ACTION not in ('RESPOND', 'RESPOND_WA', 'RESPOND_RULE', 'SEND')
7956     union all
7957      select C.SEQUENCE H_SEQUENCE,
7958             C.NOTIFICATION_ID H_NOTIFICATION_ID,
7959             C.FROM_ROLE H_FROM_ROLE,
7960             C.FROM_USER H_FROM_USER,
7961             C.TO_ROLE H_TO_ROLE,
7962             C.TO_USER H_TO_USER,
7963             '#WF_COMMENTS#' H_ACTION_TYPE,
7964             C.ACTION  H_ACTION,
7965             C.USER_COMMENT H_COMMENT,
7966             C.COMMENT_DATE H_ACTION_DATE
7967        from WF_ITEM_ACTIVITY_STATUSES_H IAS,
7968             WF_COMMENTS C
7969       where IAS.ITEM_TYPE = l_item_type
7970         and IAS.ITEM_KEY = l_item_key
7971         and IAS.PROCESS_ACTIVITY = l_actid
7972         and IAS.NOTIFICATION_ID = C.NOTIFICATION_ID
7973         and C.ACTION not in ('RESPOND', 'RESPOND_WA', 'RESPOND_RULE', 'SEND')
7974    )
7975    order by H_NOTIFICATION_ID, H_ACTION_DATE, H_SEQUENCE
7976    );
7977 
7978    cursor c_ntf_hist is
7979    select rownum H_SEQUENCE, H_NOTIFICATION_ID, H_FROM_USER, H_TO_USER, H_ACTION_TYPE, H_ACTION,
7980           H_COMMENT, H_ACTION_DATE from
7981    (select H_SEQUENCE, H_NOTIFICATION_ID, H_FROM_USER, H_TO_USER, H_ACTION_TYPE, H_ACTION,
7982            H_COMMENT, H_ACTION_DATE from
7983    (select C.SEQUENCE H_SEQUENCE,
7984            C.NOTIFICATION_ID H_NOTIFICATION_ID,
7985            C.FROM_ROLE H_FROM_ROLE,
7986            C.FROM_USER H_FROM_USER,
7987            C.TO_ROLE H_TO_ROLE,
7988            C.TO_USER H_TO_USER,
7989            C.ACTION_TYPE H_ACTION_TYPE,
7990            C.ACTION  H_ACTION,
7991            C.USER_COMMENT H_COMMENT,
7992            C.COMMENT_DATE H_ACTION_DATE
7993     from   WF_ITEM_ACTIVITY_STATUSES IAS,
7994            WF_COMMENTS C
7995     where  IAS.ITEM_TYPE = l_item_type
7996     and    IAS.ITEM_KEY = l_item_key
7997     and    IAS.PROCESS_ACTIVITY = l_actid
7998     and    IAS.NOTIFICATION_ID = C.NOTIFICATION_ID
7999     and    C.ACTION_TYPE in ('REASSIGN', 'QA')
8000     union all
8001     select C.SEQUENCE H_SEQUENCE,
8002            C.NOTIFICATION_ID H_NOTIFICATION_ID,
8003            C.FROM_ROLE H_FROM_ROLE,
8004            C.FROM_USER H_FROM_USER,
8005            C.TO_ROLE H_TO_ROLE,
8006            C.TO_USER H_TO_USER,
8007            C.ACTION_TYPE H_ACTION_TYPE,
8008            C.ACTION  H_ACTION,
8009            C.USER_COMMENT H_COMMENT,
8010            C.COMMENT_DATE H_ACTION_DATE
8011     from   WF_ITEM_ACTIVITY_STATUSES_H IAS,
8012            WF_COMMENTS C
8013     where  IAS.ITEM_TYPE = l_item_type
8014     and    IAS.ITEM_KEY = l_item_key
8015     and    IAS.PROCESS_ACTIVITY = l_actid
8016     and    IAS.NOTIFICATION_ID = C.NOTIFICATION_ID
8017     and    C.ACTION_TYPE in ('REASSIGN', 'QA')
8018     )
8019     order by H_NOTIFICATION_ID, H_ACTION_DATE, H_SEQUENCE
8020     );
8021 
8022    l_comm_rec     c_comments%ROWTYPE;
8023    l_ntf_hist_rec c_ntf_hist%ROWTYPE;
8024 begin
8025 
8026 
8027    begin
8028       SELECT item_type, item_key, process_activity
8029       INTO   l_item_type, l_item_key, l_actid
8030       FROM   wf_item_activity_statuses
8031       WHERE  notification_id = p_nid;
8032    exception
8033       when NO_DATA_FOUND then
8034         begin
8035           SELECT item_type, item_key, process_activity
8036           INTO   l_item_type, l_item_key, l_actid
8037           FROM   wf_item_activity_statuses_h
8038           WHERE  notification_id = p_nid;
8039         exception
8040           when NO_DATA_FOUND then
8041             -- It is possible that notification is sent outside of a flow,
8042             -- in that case, it will not appear in Workflow runtime tables.
8043             -- Just return here.
8044             return;
8045         end;
8046    end;
8047 
8048    if (l_item_type in ('POSCHORD', 'POSUPDNT', 'POSORDNT', 'POSASNNB', 'CREATEPO', 'POAPPRV',
8049                        'POPRICAT', 'PORCOTOL', 'PONGRQCH', 'POERROR', 'POWFDS', 'RCVDMEMO',
8050                        'APVRMDER', 'POREQCHA', 'PORCPT', 'REQAPPRV', 'PORPOCHA')) then
8051       l_suppress_hist := 'Y';
8052       l_title := Wf_Core.Translate('WFNTF_NTF_HISTORY');
8053    else
8054       l_suppress_hist := 'N';
8055       l_title := Wf_Core.Translate('WFNTF_ACTION_HISTORY');
8056    end if;
8057 
8058    l_wf_system := Wf_Core.Translate('WF_SYSTEM');
8059    l_delim := ':';
8060 
8061    l_table_dir := table_direction;
8062    if (l_table_dir = 'L') then
8063      l_dir := null;
8064    else
8065      l_dir := 'dir="RTL"';
8066    end if;
8067 
8068    j := 1;
8069    -- Action History Title
8070    cells(j) := wf_core.translate('NUM');
8071    if (p_display_type = wf_notification.doc_html) then
8072      cells(j) := 'S5%:'||cells(j);
8073    end if;
8074 
8075    j := j+1;
8076    cells(j) := wf_core.translate('ACTION_DATE');
8077    if (p_display_type = wf_notification.doc_html) then
8078      cells(j) := 'S15%:'||cells(j);
8079    end if;
8080 
8081    j := j+1;
8082    cells(j) := wf_core.translate('ACTION');
8083    if (p_display_type = wf_notification.doc_html) then
8084      cells(j) := 'S10%:'||cells(j);
8085    end if;
8086 
8087    j := j+1;
8088    cells(j) := wf_core.translate('FROM');
8089    if (p_display_type = wf_notification.doc_html) then
8090      cells(j) := 'S15%:'||cells(j);
8091    end if;
8092 
8093    j := j+1;
8094    cells(j) := wf_core.translate('TO');
8095    if (p_display_type = wf_notification.doc_html) then
8096      cells(j) := 'S15%:'||cells(j);
8097    end if;
8098 
8099    j := j+1;
8100    cells(j) := wf_core.translate('DETAILS');
8101    if (p_display_type = wf_notification.doc_html) then
8102      cells(j) := 'S40%:'||cells(j);
8103    end if;
8104 
8105    j := j+1;
8106 
8107    -- OPEN l_comments_c FOR l_sql_stmt using p_action_type, p_action_type, p_action_type,
8108    --      l_action_type1, l_action_type2, p_comment_date, p_from_role, p_to_role, p_nid;
8109 
8110  if (l_suppress_hist = 'N') then
8111 
8112    OPEN c_comments;
8113    -- Construct the action history table with all the matching comments records
8114    loop
8115       fetch c_comments into l_comm_rec;
8116       exit when c_comments%NOTFOUND;
8117 
8118       cells(j) := to_char(l_comm_rec.h_sequence);
8119 
8120       j := j+1;
8121       if (p_display_type = wf_notification.doc_html) then
8122          -- <bug 7514495>
8123          cells(j) := 'S:' || wf_notification_util.GetCalendarDate(p_nid, l_comm_rec.h_action_date, null, true);
8124       else
8125          cells(j) := wf_notification_util.GetCalendarDate(p_nid, l_comm_rec.h_action_date, null, true);
8126       end if;
8127 
8128       j := j+1;
8129 
8130       -- If the record is not from WF_COMMENTS, need to resolve the action
8131       if (l_comm_rec.h_action_type <> '#WF_COMMENTS#') then
8132          l_action_str := Wf_Core.Activity_Result(l_comm_rec.h_action_type, l_comm_rec.h_action);
8133       else
8134          l_action := l_comm_rec.h_action;
8135          l_pos := instr(l_action, '_', 1);
8136          if (l_pos > 0) then
8137            l_action := substr(l_action, 1, l_pos-1);
8138          end if;
8139          l_action_str := Wf_Core.Translate(l_action);
8140       end if;
8141 
8142       if (p_display_type = wf_notification.doc_html) then
8143          cells(j) := 'S:'||l_action_str;
8144       else
8145          cells(j) := l_action_str;
8146       end if;
8147 
8148       j := j+1;
8149       if (p_display_type = wf_notification.doc_html) then
8150          cells(j) := 'S:'||Wf_Notification.SubstituteSpecialChars(l_comm_rec.h_from_user);
8151       else
8152          cells(j) := l_comm_rec.h_from_user;
8153       end if;
8154 
8155       j := j+1;
8156       if (p_display_type = wf_notification.doc_html) then
8157          cells(j) := 'S:'||Wf_Notification.SubstituteSpecialChars(l_comm_rec.h_to_user);
8158       else
8159          cells(j) := l_comm_rec.h_to_user;
8160       end if;
8161 
8162       j := j+1;
8163       l_note := l_comm_rec.h_comment;
8164       -- WF_NOTE indicates that this is a Respond attribute.
8165       if (l_note = '#WF_NOTE#') then
8166         begin
8167           SELECT text_value
8168           INTO   l_note
8169           FROM   wf_notification_attributes
8170           WHERE  notification_id = l_comm_rec.h_notification_id
8171           AND    name = 'WF_NOTE';
8172         exception
8173 	  when no_data_found then
8174             l_note := '';
8175         end;
8176       end if;
8177       if (p_display_type = wf_notification.doc_html) then
8178          l_note := substrb(Wf_Notification.SubstituteSpecialChars(l_note), 1, 4000);
8179       end if;
8180       cells(j) := l_note;
8181 
8182       if (p_display_type = wf_notification.doc_html) then
8183          if (cells(j) is null) then
8184             cells(j) := 'S: ';
8185          else
8186             cells(j) := 'S:'||cells(j);
8187          end if;
8188       end if;
8189       j := j+1;
8190    end loop;
8191 
8192    l_count := c_comments%rowcount;
8193    CLOSE c_comments;
8194 
8195  elsif (l_suppress_hist = 'Y') then
8196 
8197    OPEN c_ntf_hist;
8198 
8199    -- Construct the action history table with all the matching comments records
8200    loop
8201       fetch c_ntf_hist into l_ntf_hist_rec;
8202       exit when c_ntf_hist%NOTFOUND;
8203 
8204       cells(j) := to_char(l_ntf_hist_rec.h_sequence);
8205 
8206       j := j+1;
8207       if (p_display_type = wf_notification.doc_html) then
8208          cells(j) := 'S:'|| wf_notification_util.GetCalendarDate(p_nid, l_ntf_hist_rec.h_action_date, null, true);
8209       else
8210          cells(j) := wf_notification_util.GetCalendarDate(p_nid, l_ntf_hist_rec.h_action_date, null, true);
8211       end if;
8212 
8213       j := j+1;
8214 
8215       l_action := l_ntf_hist_rec.h_action;
8216       l_pos := instr(l_action, '_', 1);
8217       if (l_pos > 0) then
8218         l_action := substr(l_action, 1, l_pos-1);
8219       end if;
8220       l_action_str := Wf_Core.Translate(l_action);
8221 
8222       if (p_display_type = wf_notification.doc_html) then
8223          cells(j) := 'S:'||l_action_str;
8224       else
8225          cells(j) := l_action_str;
8226       end if;
8227 
8228       j := j+1;
8229       if (p_display_type = wf_notification.doc_html) then
8230          cells(j) := 'S:'||Wf_Notification.SubstituteSpecialChars(l_ntf_hist_rec.h_from_user);
8231       else
8232          cells(j) := l_ntf_hist_rec.h_from_user;
8233       end if;
8234 
8235       j := j+1;
8236       if (p_display_type = wf_notification.doc_html) then
8237          cells(j) := 'S:'||Wf_Notification.SubstituteSpecialChars(l_ntf_hist_rec.h_to_user);
8238       else
8239          cells(j) := l_ntf_hist_rec.h_to_user;
8240       end if;
8241 
8242       j := j+1;
8243       l_note := l_ntf_hist_rec.h_comment;
8244       if (p_display_type = wf_notification.doc_html) then
8245          l_note := substrb(Wf_Notification.SubstituteSpecialChars(l_note), 1, 4000);
8246       end if;
8247       cells(j) := l_note;
8248 
8249       if (p_display_type = wf_notification.doc_html) then
8250          if (cells(j) is null) then
8251             cells(j) := 'S: ';
8252          else
8253             cells(j) := 'S:'||cells(j);
8254          end if;
8255       end if;
8256       j := j+1;
8257    end loop;
8258 
8259    l_count := c_ntf_hist%rowcount;
8260    CLOSE c_ntf_hist;
8261 
8262  end if;
8263 
8264    -- If there is nothing to display, return a null
8265    if (l_count = 0) then
8266       p_action_history := '';
8267       return;
8268    end if;
8269 
8270    -- Sequence is now based on the rownum, not the reverse
8271    -- for k in 0..(i-1) loop
8272    --   if (p_display_type = wf_notification.doc_html) then
8273    --      cells((k+1)*6+1) := 'C:'||to_char(i-k-1);
8274    --   else
8275    --      cells((k+1)*6+1) := to_char(i-k-1);
8276    --   end if;
8277    -- end loop;
8278 
8279    -- Construct table from the cells
8280    if (p_display_type = wf_notification.doc_html) then
8281       table_width := '100%';
8282       -- bug 7718246 - set the table border to 1 only for action history
8283       table_border := '1';
8284       NTF_Table(cells=>cells,
8285                 col=>6,
8286                 type=>'H'||l_table_dir,
8287                 rs=>l_result);
8288 
8289       -- Display title "Action History"
8290       l_result := '<table  width='||table_width||
8291                   ' border="0" cellspacing="0" cellpadding="0" '||l_dir||'>' ||
8292                   '<tr><td class="x3w">'||
8293                   l_title||'</td></tr>'||'<tr><td>'||l_result||'</td></tr></table>';
8294       -- reset table border to default value after generating action history
8295       table_border := '0';
8296    else
8297       for k in 1..cells.LAST loop
8298          if (mod(k, 6) <> 0) then
8299             l_result := l_result||cells(k)||' '||l_delim||' ';
8300          else
8301             l_result := l_result||cells(k)||wf_core.newline;
8302         end if;
8303      end loop;
8304      l_result := wf_core.translate('WFNTF_ACTION_HISTORY')||wf_core.newline||l_result;
8305    end if;
8306 
8307    p_action_history := l_result;
8308 
8309 exception
8310   when others then
8311      wf_core.context('Wf_Notification', 'GetComments2', to_char(p_nid), p_display_type);
8312      raise;
8313 end GetComments2;
8314 
8315 --
8316 -- GetAttrblob
8317 --   Get the displayed value of a PLSQLBLOB DOCUMENT-type attribute.
8318 --   Returns referenced document in format requested.
8319 --   Use GetAttrText to get retrieve the actual attr value (i.e. the
8320 --   document key string instead of the actual document).
8321 -- NOTE:
8322 --   a. Only PLSQL document type is implemented.
8323 --   b. This will be called by old mailers. This is a wrapper to the
8324 --      new implementation which returns the doctype also.
8325 -- IN:
8326 --   nid      - Notification id
8327 --   astring  - the string to substitute on (ex: '&ATTR1 is your order..')
8328 --   disptype - Requested display type.  Valid values:
8329 --               wf_notification.doc_text - 'text/plain'
8330 --               wf_notification.doc_html - 'text/html'
8331 --   document - The blob into which
8332 --   aname    - Attribute Name (the first part of the string that matches
8333 --              the attr list)
8334 --
8335 procedure GetAttrblob(
8336   nid       in number,
8337   astring   in varchar2,
8338   disptype  in varchar2,
8339   document  in out nocopy blob,
8340   aname     out nocopy varchar2)
8341 is
8342   doctype varchar2(500);
8343 begin
8344 
8345   Wf_Notification.GetAttrblob(nid, astring, disptype, document, doctype, aname);
8346 
8347 exception
8348   when others then
8349     wf_core.context('Wf_Notification', 'oldGetAttrblob', to_char(nid), aname,
8350         disptype);
8351     raise;
8352 end GetAttrblob;
8353 
8354 -- GetAttrblob
8355 --   Get the displayed value of a PLSQLBLOB DOCUMENT-type attribute.
8356 --   Returns referenced document in format requested.
8357 --   Use GetAttrText to get retrieve the actual attr value (i.e. the
8358 --   document key string instead of the actual document).
8359 -- NOTE:
8360 --   Only PLSQL document type is implemented.
8361 -- IN:
8362 --   nid      - Notification id
8363 --   astring  - the string to substitute on (ex: '&ATTR1 is your order..')
8364 --   disptype - Requested display type.  Valid values:
8365 --               wf_notification.doc_text - 'text/plain'
8366 --               wf_notification.doc_html - 'text/html'
8367 --   document - The blob into which
8368 --   aname    - Attribute Name (the string that matches
8369 --              the attr list)
8370 --
8371 procedure GetAttrblob(
8372   nid       in  number,
8373   astring   in  varchar2,
8374   disptype  in  varchar2,
8375   document  in  out nocopy blob,
8376   doctype   out nocopy varchar2,
8377   aname     out nocopy varchar2)
8378 is
8379   key varchar2(4000);
8380   colon pls_integer;
8381   slash pls_integer;
8382   dmstype varchar2(30);
8383   display_name varchar2(80);
8384   procname varchar2(240);
8385   launch_url varchar2(4000) := null;
8386   procarg varchar2(32000);
8387   username  varchar2(320);
8388 
8389   --curs integer;
8390   sqlbuf varchar2(2000);
8391   --rows integer;
8392 
8393   target   varchar2(240);
8394   l_charcheck boolean;
8395 
8396 begin
8397 
8398   -- Check args
8399   if ((nid is null) or (astring is null) or
8400      (disptype not in (wf_notification.doc_text,
8401                        wf_notification.doc_html))) then
8402     wf_core.token('NID', to_char(nid));
8403     wf_core.token('ASTRING', aname);
8404     wf_core.token('DISPTYPE', disptype);
8405     wf_core.raise('WFSQL_ARGS');
8406   end if;
8407 
8408   -- of all the possible Document type matches,
8409   -- make sure its a PLSQLBLOB
8410     dmstype := '';
8411 
8412   -- Bug 6324545: Replaced the cursor with a simple sql to fetch a single row.
8413   begin
8414       -- <7443088> improved query
8415       select NAME into aname from
8416         (select WMA.NAME
8417          from WF_NOTIFICATIONS WN,
8418               WF_MESSAGE_ATTRIBUTES WMA,
8419               WF_NOTIFICATION_ATTRIBUTES NA
8420          where WN.NOTIFICATION_ID = nid
8421           and wn.notification_id = na.notification_id
8422           and wma.name = na.name
8423           and WN.MESSAGE_TYPE = WMA.MESSAGE_TYPE
8424           and WN.MESSAGE_NAME = WMA.MESSAGE_NAME
8425           and WMA.TYPE = 'DOCUMENT'
8426           and instr( upper(astring) ,wma.name) = 1
8427           and upper(na.text_value) like 'PLSQLBLOB:%'
8428          order by length(wma.name) desc)
8429        where rownum=1;
8430   exception
8431      when no_data_found then
8432            aname:=null;
8433            return;
8434   end;
8435 
8436   if (aname is not null) then
8437      -- Retrieve key string
8438      key := wf_notification.GetAttrText(nid, aname);
8439 
8440      -- If the key is empty then return a null string
8441      if (key is not null) then
8442 
8443        -- Parse doc mgmt system type from key
8444        colon := instr(key, ':');
8445        if ((colon <> 0) and (colon < 30)) then
8446           dmstype := upper(substr(key, 1, colon-1));
8447        end if;
8448      end if;
8449    end if;
8450 
8451   -- if we didnt find any plsqlblobs then exit now
8452   if dmstype is null or (dmstype <> 'PLSQLBLOB') then
8453      aname:=null;
8454      return;
8455   end if;
8456 
8457   -- We must be processing a BLOB PLSQL doc type
8458   slash := instr(key, '/');
8459   if (slash = 0) then
8460     procname := substr(key, colon+1);
8461     procarg := '';
8462   else
8463     procname := substr(key, colon+1, slash-colon-1);
8464     procarg := substr(key, slash+1);
8465   end if;
8466 
8467   -- Dynamic sql call to procedure
8468   -- bug 2706082 using execute immediate instead of dbms_sql.execute
8469 
8470   if (procarg is null) then
8471      procarg := NULL;
8472   else
8473      procarg := Wf_Notification.GetTextInternal(procarg, nid, target,
8474                                                 FALSE, FALSE);
8475   end if;
8476 
8477   -- ### Review Note 1
8478   -- Check for bug#3827935
8479   l_charcheck := wf_notification_util.CheckIllegalChar(procname);
8480   --Throw the Illegal exception when the check fails
8481 
8482    if (wf_log_pkg.level_statement >= fnd_log.g_current_runtime_level) then
8483      wf_log_pkg.string2(wf_log_pkg.level_statement,
8484                        'wf.plsql.wf_notification.GetAttrBlob.plsqlblob_callout',
8485                        'Start executing PLSQLBLOB Doc procedure  - '||procname, true);
8486    end if;
8487 
8488    sqlbuf := 'begin '||procname||'(:p1, :p2, :p3, :p4); end;';
8489    execute immediate sqlbuf using
8490      in procarg,
8491      in disptype,
8492      in out document,
8493      in out doctype;
8494 
8495    if (wf_log_pkg.level_statement >= fnd_log.g_current_runtime_level) then
8496      wf_log_pkg.string2(wf_log_pkg.level_statement,
8497                        'wf.plsql.wf_notification.GetAttrBlob.plsqlblob_callout',
8498                        'End executing PLSQLBLOB Doc procedure  - '||procname, false);
8499    end if;
8500 
8501 exception
8502   when others then
8503     -- Close cursor just in case
8504     /* if (dbms_sql.is_open(curs)) then
8505       dbms_sql.close_cursor(curs);
8506     end if; */
8507 
8508     wf_core.context('Wf_Notification', 'GetAttrblob', to_char(nid), aname,
8509         disptype);
8510     raise;
8511 end GetAttrblob;
8512 
8513 --
8514 -- Set_NTF_Table_Direction
8515 -- Sets the default direction of notification tables
8516 -- generated through wf_notification.wf_ntf_history
8517 -- and wf_notification.wf_msg_attr
8518 procedure Set_NTF_Table_Direction(direction in varchar2)
8519 is
8520 begin
8521    table_direction := direction;
8522 end Set_NTF_Table_direction;
8523 
8524 --
8525 -- Set_NTF_Table_Type
8526 -- Sets the default table type for attr tables
8527 -- generated through wf_notification.wf_msg_attr
8528 procedure Set_NTF_Table_Type(tableType in varchar2)
8529 is
8530 begin
8531    table_type := tableType;
8532 end Set_NTF_Table_Type;
8533 
8534 
8535 -- isFwkRegion
8536 -- verifies whether message for given notification id contains
8537 -- any framework regions.
8538 -- Algorithm: Function returns 'Y' if one of the following condition is met
8539 --            - If header region attribute #HDR_REGION of type 'DOCUMENT'
8540 --              and its value starts with 'JSP:/OA_HTML/OA.jsp?'
8541 --            - If the message body is of type framework region
8542 --
8543 
8544 function isFwkRegion(nid in number) return varchar2 is
8545 
8546 begin
8547    return isFwkRegion(nid, wf_notification.doc_html);
8548 end isFwkRegion;
8549 
8550 
8551 -- isFwkRegion
8552 -- verifies whether message for given notification id contains
8553 -- any framework regions.
8554 -- Algorithm: Function returns 'Y' if one of the following condition is met
8555 --            - If header region attribute #HDR_REGION of type 'DOCUMENT'
8556 --              and its value starts with 'JSP:/OA_HTML/OA.jsp?'
8557 --            - If the message body is of type framework region
8558 --
8559 -- Auth : SSTOMAR
8560 
8561 function isFwkRegion(nid in number, content_type in varchar2 ) return varchar2 is
8562 
8563   lv_body varchar2(32000);
8564   lv_html_body varchar2(32000);
8565   lv_final_body varchar2(32000);
8566   lv_fwk_region varchar2(1);
8567   lv_first_token varchar2(240);
8568   lv_token_start number;
8569 
8570   cursor cur_hdr_region is
8571     select WNA.NAME, WNA.TEXT_VALUE
8572     from WF_NOTIFICATION_ATTRIBUTES WNA,
8573          WF_MESSAGE_ATTRIBUTES_VL WMA
8574     where WNA.NOTIFICATION_ID = nid
8575     and WMA.NAME = WNA.NAME
8576     and WMA.TYPE = 'DOCUMENT'
8577     and WNA.NAME = '#HDR_REGION';
8578 
8579 begin
8580 
8581   lv_fwk_region := 'N';
8582 
8583   for attr_row in cur_hdr_region loop
8584     if( instr(attr_row.text_value, fwk_region_start) = 1 ) then
8585       lv_fwk_region := 'Y';
8586       exit;
8587     end if;
8588   end loop;
8589   -- If framework header region exists then return
8590   if( lv_fwk_region = 'Y' ) then
8591    return lv_fwk_region;
8592   -- else check whether message body is of type framework region
8593   else
8594     return isFwkBody( nid, content_type);
8595   end if;
8596 exception
8597   when OTHERS then
8598     wf_core.context('Wf_Notification','isFwkRegion',to_char(nid), content_type);
8599     raise;
8600 
8601 End isFwkRegion;
8602 
8603 -- isFwkBody
8604 -- verifies whether message body for given notification id contains
8605 -- any framework regions.
8606 -- Algorithm: Function returns 'Y' if one of the following condition is met
8607 --            - If the first attribute referred in the body is of
8608 --              type 'DOCUMENT' and its value starts with 'JSP:/OA_HTML/OA.jsp?'
8609 --            - If the message body does not have any attributes refered except
8610 --              for WF_NOTIFICATION macro and simple text
8611 
8612 function isFwkBody(nid in number) return varchar2 is
8613 
8614 
8615 begin
8616   -- invoke overrided API with default as 'text/html'
8617   return isFwkBody(nid, wf_notification.doc_html);
8618 
8619 End isFwkBody;
8620 
8621 -- isFwkBody
8622 -- verifies whether message body for given notification id contains
8623 -- any framework regions.
8624 -- Algorithm: Function returns 'Y' if one of the following condition is met
8625 --            - If the first attribute referred in the body is of
8626 --              type 'DOCUMENT' and its value starts with 'JSP:/OA_HTML/OA.jsp?'
8627 --            - If the message body does not have any attributes refered except
8628 --              for WF_NOTIFICATION macro and simple text
8629 -- Auth : SSTOMAR
8630 function isFwkBody(nid in number, content_type in varchar2) return varchar2 is
8631 
8632   lv_body varchar2(32000);
8633   lv_html_body varchar2(32000);
8634   lv_final_body varchar2(32000);
8635   lv_fwk_body varchar2(1);
8636   lv_first_token varchar2(240);
8637   lv_token_start number;
8638 
8639 begin
8640 
8641   lv_fwk_body := 'N';
8642 
8643   select nvl(WM.BODY, ''), nvl(WM.HTML_BODY, '')
8644   into lv_body, lv_html_body
8645   from WF_NOTIFICATIONS N, WF_MESSAGES_VL WM
8646   where N.NOTIFICATION_ID = nid
8647   and N.MESSAGE_NAME = WM.NAME
8648   and N.MESSAGE_TYPE = WM.TYPE;
8649 
8650   --  bug 5456241 (SSTOMAR)
8651   --  Based on that, we will pick up corresponding message bdoy.
8652   --
8653   --  KNOWN ISSUE: Generally the simple text in message body also considered
8654   --  as Framework based notification but If message body has simple text contains
8655   --  '& ' character without token name
8656   --  then according to below logic it will be plsql based ntf.
8657 
8658   if (content_type = wf_notification.doc_html ) then
8659     if (length(trim(lv_html_body)) > 0 ) then
8660 
8661 	  if (fwkTokenExist(nid, lv_html_body) = 'Y'
8662 	      or instr(lv_html_body, '&') = 0 ) then
8663 	    lv_fwk_body := 'Y';
8664 	  end if;
8665 	-- HTML body is blank, check text body. And if text body has any TOKEN which
8666 	-- is a DOCUMENT type OR it does not has any attribute then assume it as Fwk based Ntf.
8667     elsif (length(trim(lv_body)) > 0
8668 	       and (fwkTokenExist(nid, lv_body) = 'Y'
8669 			    or instr(lv_body, '&') = 0)) then
8670 	  lv_fwk_body := 'Y';
8671     end if;
8672   else  -- doc_type is plan/text
8673     -- Check the text body only
8674     if (length(trim(lv_body)) > 0
8675         and (fwkTokenExist(nid, lv_body) = 'Y'
8676 		     or instr(lv_body, '&') = 0) ) then
8677 	  lv_fwk_body := 'Y';
8678     end if;
8679   end if;
8680 
8681 
8682   --lv_token_start := instr( lv_final_body, '&');
8683   -- get the first token in the body
8684   --if( lv_token_start > 0 ) then
8685   --  lv_first_token := substr(lv_final_body, lv_token_start+1, 30);
8686   --  for attr_row in cur_msg_attrs(nid, lv_first_token) loop
8687   --    if( instr(attr_row.text_value, fwk_region_start) = 1 ) then
8688   --      lv_fwk_body := 'Y';
8689   --      exit;
8690   --    end if;
8691   --  end loop;
8692   -- no attributes refered in body so render framework region
8693   --else
8694   --  lv_fwk_body := 'Y';
8695   -- end if;
8696 
8697   return lv_fwk_body;
8698 exception
8699   when OTHERS then
8700     wf_core.context('Wf_Notification','isFwkBody',to_char(nid), content_type);
8701     raise;
8702 
8703 End isFwkBody;
8704 
8705 -- fwkTokenExist
8706 -- This function check whether first TOKEN within the message body exist AND
8707 -- has the value like 'JSP:/OA_HTML/OA.jsp?' (value hold by variable fwk_region_start) .
8708 -- Auther : SSTOMAR
8709 function fwkTokenExist(nid in number, msgbody in varchar2) return varchar2 is
8710   lv_token_exist varchar2(1) ;
8711   lv_token_start number;
8712   lv_first_token varchar2(240);
8713 
8714   -- Cursur to check for each message Attribute
8715   cursor cur_msg_attrs(nid number, msgToken varchar2) is
8716     select WNA.NAME, WNA.TEXT_VALUE, WMA.TYPE
8717     from WF_NOTIFICATION_ATTRIBUTES WNA, WF_NOTIFICATIONS WN,
8718          WF_MESSAGE_ATTRIBUTES_VL WMA
8719     where WNA.NOTIFICATION_ID = nid
8720     and WN.NOTIFICATION_ID = WNA.NOTIFICATION_ID
8721     and WN.MESSAGE_TYPE = WMA.MESSAGE_TYPE
8722     and WN.MESSAGE_NAME = WMA.MESSAGE_NAME
8723     and WMA.TYPE = 'DOCUMENT'
8724     and WMA.NAME = WNA.NAME
8725     and instr( msgToken, WMA.NAME ) = 1
8726     order by length(WMA.NAME) desc;
8727 
8728 begin
8729 
8730 	lv_token_exist := 'N';
8731 	lv_token_start := instr( msgbody, '&');
8732 
8733 	-- get the first token in the body
8734 	if( lv_token_start > 0 ) then
8735      -- sstomar:
8736      -- Note: No. of characters 30 are hardcoded,
8737 	 -- I think we should take upto space ' ' mark.
8738 	 lv_first_token := substr(msgbody, lv_token_start+1, 30);
8739 
8740 	 for attr_row in cur_msg_attrs(nid, lv_first_token) loop
8741 	  if( instr(attr_row.text_value, fwk_region_start) = 1 ) then
8742        lv_token_exist := 'Y';
8743 	   exit;
8744 	  end if;
8745 	 end loop;
8746 	end if;
8747 
8748  return lv_token_exist;
8749 exception
8750   when OTHERS then
8751     wf_core.context('Wf_Notification','fwkTokenExist',to_char(nid));
8752     raise;
8753 
8754 end fwkTokenExist;
8755 
8756 --
8757 -- getNtfActInfo
8758 -- Fetch Notification Activity info of a given notification. It is possible
8759 -- that there will not be an entry in WF_ITEM_ACTIVITY_STATUSES and/or
8760 -- WF_ITEM_ACTIVITY_STATUSES_H in case when the notification is sent using
8761 -- Send API instead of part of a process.
8762 -- IN
8763 --  nid - Notification ID
8764 -- OUT
8765 --  l_itype Itemtype of the notification activity
8766 --  l_itype Itemkey of the process part of which the notification was sent
8767 --  l_actid Activity ID of the Notification Activity in the process
8768 
8769 procedure getNtfActInfo (nid     in  number,
8770                          l_itype out nocopy varchar2,
8771                          l_ikey  out nocopy varchar2,
8772                          l_actid out nocopy number)
8773 is
8774   --bug 2276260 skilaru 15-July-03
8775   cursor act_info_statuses_cursor( group_nid number ) is
8776     select ITEM_TYPE, ITEM_KEY, PROCESS_ACTIVITY
8777       from WF_ITEM_ACTIVITY_STATUSES
8778      where notification_id = group_nid;
8779 
8780   cursor act_info_statuses_h_cursor( group_nid number ) is
8781     select ITEM_TYPE, ITEM_KEY, PROCESS_ACTIVITY
8782       from WF_ITEM_ACTIVITY_STATUSES_H
8783      where notification_id = group_nid;
8784 
8785   l_group_nid number;
8786 
8787 begin
8788   --skilaru 16-July-03
8789   --WF_ITEM_ACTIVITY_STATUSES.NOTIFICATION_ID is the foreing key
8790   --mapped to WF_NOTIFICATIONS.GROUP_ID
8791   SELECT group_id
8792     INTO l_group_nid
8793     FROM wf_notifications
8794    WHERE notification_id = nid;
8795 
8796   for act_status_row in act_info_statuses_cursor( l_group_nid ) loop
8797     l_itype := act_status_row.ITEM_TYPE;
8798     l_ikey := act_status_row.ITEM_KEY;
8799     l_actid := act_status_row.PROCESS_ACTIVITY;
8800   end loop;
8801 
8802   if( l_itype is null and l_ikey is null ) then
8803     for act_status_row in act_info_statuses_h_cursor( l_group_nid ) loop
8804       l_itype := act_status_row.ITEM_TYPE;
8805       l_ikey := act_status_row.ITEM_KEY;
8806       l_actid := act_status_row.PROCESS_ACTIVITY;
8807     end loop;
8808   end if;
8809 
8810 exception
8811   when OTHERS then
8812     wf_core.context('Wf_Notification','getNtfActInfo',to_char(nid));
8813     raise;
8814 
8815 End getNtfActInfo;
8816 
8817 --
8818 -- getFwkBodyURLLang
8819 --   This API returns a URL to access notification body with
8820 --   Framework content embedded.
8821 -- IN:
8822 --   nid      - Notification id
8823 --   disptype - Requested display type.  Valid values:
8824 --               'text/plain' - plain text
8825 --               'text/html' - html
8826 -- RETURNS:
8827 --   Returns the URL to access the notification detail body
8828 --
8829 function getFwkBodyURLLang(nid in number,
8830                            contenttype varchar2,
8831                            language in varchar2)
8832 return varchar2
8833  is
8834  begin
8835 
8836   return getFwkBodyURL2(nid, contenttype, language, null);
8837 
8838 end getFwkBodyURLLang;
8839 
8840 
8841 --
8842 -- getFwkBodyURL
8843 --   This API returns a URL to access notification body with
8844 --   Framework content embedded.
8845 -- IN:
8846 --   p_nid      - Notification id
8847 --   p_contenttype - Requested display type.  Valid values:
8848 --                 'text/plain' - plain text
8849 --                 'text/html' - html,
8850 --   p_language     language value of that notification / user
8851 --   p_nlsCalendar  nls calender of that user
8852 
8853 -- RETURNS:
8854 --   Returns the URL to access the notification detail body
8855 --
8856 
8857 function getFwkBodyURL2( p_nid in number,
8858                          p_contentType varchar2,
8859                          p_language varchar2,
8860                          p_nlsCalendar varchar2) return varchar2
8861 is
8862    url_value varchar2(2000);
8863    lang_code varchar2(4);
8864    l_api varchar2(250) := g_plsqlName ||'getFwkBodyURL2';
8865 begin
8866   if (WF_LOG_PKG.LEVEL_PROCEDURE >= fnd_log.g_current_runtime_level) then
8867     wf_log_pkg.String(WF_LOG_PKG.LEVEL_PROCEDURE, l_api,'BEGIN');
8868   end if;
8869 
8870    url_value := rtrim(fnd_profile.Value('WF_MAIL_WEB_AGENT'), '/');
8871 
8872    if url_value is null then
8873       url_value := rtrim(fnd_profile.Value('APPS_FRAMEWORK_AGENT'), '/');
8874    end if;
8875 
8876    url_value := url_value || wf_notification.fwk_mailer_page;
8877    url_value := url_value || '&WFRegion=NtfDetail&NtfId=' || to_char(p_nid);
8878    url_value := url_value || '&dbc=' || fnd_web_config.Database_ID;
8879 
8880    if (p_contentType = wf_notification.doc_html) then
8881      -- url_value := url_value || '&OALAF=blaf&OARF=email';
8882      url_value := url_value || '&OARF=email';
8883    elsif  (p_contentType = wf_notification.doc_text) then
8884      url_value := url_value || '&OALAF=oaText&OARF=email';
8885    end if;
8886 
8887    -- Bug 5170348
8888    -- Append the language_code to the fwk URL to set session p_language
8889    if (p_language is not null) then
8890      begin
8891 
8892        select code into lang_code from wf_languages where nls_language=p_language;
8893        url_value := url_value || '&language_code='|| lang_code;
8894 
8895      exception
8896        when others then
8897           wf_log_pkg.string(WF_LOG_PKG.LEVEL_UNEXPECTED, 'WF_NOTIFICATION.getFwkBodyURLLang',
8898                          'nid: '||to_char(p_nid)||'; language: '|| p_language);
8899      end;
8900    end if;
8901 
8902     -- Append the NLS_CALENDAR to the fwk URL only for 12.1.0 or above
8903     if (fnd_release.major_version = 12 and fnd_release.minor_version >= 1
8904         and fnd_release.point_version>=1) or (fnd_release.major_version > 12) then
8905       url_value := url_value || '&nlsCalendar='|| nvl(p_nlsCalendar, wf_core.nls_calendar);
8906     end if;
8907 
8908    if (WF_LOG_PKG.LEVEL_PROCEDURE >= fnd_log.g_current_runtime_level) then
8909      wf_log_pkg.String(WF_LOG_PKG.LEVEL_PROCEDURE, l_api,'p_nlsCalendar: '||p_nlsCalendar);
8910      wf_log_pkg.String(WF_LOG_PKG.LEVEL_PROCEDURE, l_api,'url: '||url_value);
8911      wf_log_pkg.String(WF_LOG_PKG.LEVEL_PROCEDURE, l_api,'END');
8912    end if;
8913 
8914    return url_value;
8915 
8916  exception
8917    when OTHERS then
8918      wf_core.context('Wf_Notification','getFwkBodyURL2',
8919                      to_char(p_nid), p_contentType,p_language,p_nlsCalendar );
8920      raise;
8921 
8922 END getFwkBodyURL2;
8923 
8924 
8925 
8926 --
8927 -- getFwkBodyURL
8928 --   This API returns a URL to access notification body with
8929 --   Framework content embedded
8930 -- IN:
8931 --   nid      - Notification id
8932 --   disptype - Requested display type.  Valid values:
8933 --               'text/plain' - plain text
8934 --               'text/html' - html
8935 -- RETURNS:
8936 --   Returns the URL to returned by call to getFwkBodyURLLang
8937 --
8938 function getFwkBodyURL(nid in number, contenttype varchar2 ) return varchar2
8939  is
8940  begin
8941   return getFwkBodyURLLang(nid, contenttype, null);
8942 end getFwkBodyURL;
8943 
8944 --
8945 -- getSummaryURL
8946 --   This API returns a URL to access summary of notiifications
8947 --
8948 -- IN:
8949 --   mailer_role  - role for which summary of notifications required
8950 --   disptype     - Requested display type.  Valid values:
8951 --               'text/plain' - plain text
8952 --               'text/html' - html
8953 -- RETURNS:
8954 --   Returns the URL to access the summary of notiification for the role
8955 --
8956 
8957 function getSummaryURL(mailer_role varchar2, contenttype varchar2 ) return varchar2
8958  is
8959  begin
8960 
8961    RETURN getSummaryURL2(p_mailer_role =>mailer_role,
8962                          p_contentType => contenttype,
8963                          p_nlsCalendar => null);
8964 
8965 end getSummaryURL;
8966 
8967 -- getSummaryURL
8968 --   This API returns a URL to access summary of notiifications
8969 --
8970 -- IN:
8971 --   p_mailer_role   - role for which summary of notifications required
8972 --   p_contentType   - Requested display type.  Valid values:
8973 --                   'text/plain' - plain text
8974 --                   'text/html' - html
8975 --   p_nlsCalendar   - nls Calender of that role / user
8976 --
8977 -- RETURNS:
8978 --   Returns the URL to access the summary of notiification for the role
8979 --
8980 
8981 function getSummaryURL2( p_mailer_role varchar2,
8982                         p_contentType varchar2,
8983                         p_nlsCalendar varchar2) return varchar2
8984 is
8985    url_value varchar2(2000);
8986    l_api varchar2(250) := g_plsqlName ||'getSummaryURL2';
8987 begin
8988    if (WF_LOG_PKG.LEVEL_PROCEDURE >= fnd_log.g_current_runtime_level) then
8989      wf_log_pkg.String(WF_LOG_PKG.LEVEL_PROCEDURE, l_api,'BEGIN');
8990   end if;
8991 
8992    url_value := rtrim(fnd_profile.Value('WF_MAIL_WEB_AGENT'), '/');
8993    if url_value is null then
8994      url_value := rtrim(fnd_profile.Value('APPS_FRAMEWORK_AGENT'), '/');
8995    end if;
8996 
8997    url_value := url_value || wf_notification.fwk_mailer_page;
8998    url_value := url_value ||'&WFRegion=NtfSummary&mailerRole=' || p_mailer_role;
8999    url_value := url_value || '&dbc=' || fnd_web_config.Database_ID;
9000 
9001    if (p_contentType = wf_notification.doc_html) then
9002      -- url_value := url_value || '&OALAF=blaf&OARF=email';
9003      url_value := url_value || '&OARF=email';
9004    elsif  (p_contentType = wf_notification.doc_text) then
9005      url_value := url_value || '&OALAF=oaText&OARF=email';
9006    end if;
9007 
9008    -- Append the NLS_CALENDAR to the fwk URL only for 12.1.0 or above
9009    if (fnd_release.major_version = 12 and fnd_release.minor_version >= 1
9010        and fnd_release.point_version>=1) or (fnd_release.major_version > 12) then
9011 
9012      url_value := url_value || '&nlsCalendar='|| nvl(p_nlsCalendar,
9013                                                    wf_core.nls_calendar);
9014    end if;
9015 
9016    if (WF_LOG_PKG.LEVEL_PROCEDURE >= fnd_log.g_current_runtime_level) then
9017      wf_log_pkg.String(WF_LOG_PKG.LEVEL_PROCEDURE, l_api,'p_nlsCalendar: '||p_nlsCalendar);
9018      wf_log_pkg.String(WF_LOG_PKG.LEVEL_PROCEDURE, l_api,'url: '||url_value);
9019      wf_log_pkg.String(WF_LOG_PKG.LEVEL_PROCEDURE, l_api,'END');
9020    end if;
9021 
9022    return url_value;
9023 
9024  exception
9025    when OTHERS then
9026      wf_core.context('Wf_Notification','getSummaryURL2', p_mailer_role, p_contentType, p_nlsCalendar);
9027      raise;
9028 
9029 END getSummaryURL2;
9030 
9031 
9032 
9033 
9034    -- GetSignatureRequired
9035    -- Determine signing requirements for a policy
9036    -- IN:
9037    --   nid - Notification id - used for error context only
9038    --   p_sig_policy - Policy Name
9039    -- OUT:
9040    --   p_sig_required - Y/N
9041    --   p_fwk_sig_flavor - sigFlavor for browser signing.
9042    --   p_email_sig_flavor - sigFlavor for email
9043    --   p_render_hint - hints like ATTR_ONLY or FULL_TEXT
9044 
9045    procedure GetSignatureRequired(p_sig_policy in varchar2,
9046         p_nid in number,
9047         p_sig_required out nocopy varchar2,
9048         p_fwk_sig_flavor out nocopy varchar2,
9049         p_email_sig_flavor out nocopy varchar2,
9050         p_render_hint out nocopy varchar2)
9051 
9052      is
9053 
9054      v_sig_policy varchar2(50);
9055 
9056    begin
9057      -- if the signature policy is null, set it as  default
9058      if (p_sig_policy is null) then
9059            v_sig_policy := 'DEFAULT';
9060      else
9061            v_sig_policy := p_sig_policy;
9062      end if;
9063 
9064      --select the flavors corresponding to the sig policy
9065      select SIG_REQUIRED,FWK_SIG_FLAVOR,EMAIL_SIG_FLAVOR, RENDER_HINT
9066       into p_sig_required,p_fwk_sig_flavor,p_email_sig_flavor,p_render_hint
9067       from WF_SIGNATURE_POLICIES
9068      where  sig_policy=UPPER(TRIM(v_sig_policy));
9069 
9070      --when any exception raise the error with the corresponding notification id
9071 
9072    exception
9073       when others then
9074        wf_core.context('WF_Notification', 'GetSignatureRequired', to_char(p_nid));
9075        wf_core.token('NID', to_char(p_nid));
9076        wf_core.raise('WFMLR_INVALID_SIG_POLICY');
9077    end;
9078 
9079 -- SetUIErrorMessage
9080 -- API for Enhanced error handling for OAFwk UI Bug#2845488 grengara
9081 -- This procedure can be used for handling exceptions gracefully when dynamic SQL is invloved
9082 
9083 procedure SetUIErrorMessage
9084 is
9085 begin
9086     if ((wf_core.error_name is null) AND (sqlcode <= -20000) AND (sqlcode >= -20999)) then
9087 	-- capture the SQL Error message in this global variable so that it can be propogated
9088 	-- back to OAF without the need for an OUT paramter
9089         wf_core.error_message := sqlerrm;
9090     end if;
9091 end SetUIErrorMessage;
9092 
9093 --
9094 -- SetComments
9095 --   Private procedure that is used to store a comment record into WF_COMMENTS
9096 --   table with the denormalized information. A record is inserted for every
9097 --   action performed on a notification.
9098 -- IN
9099 --   p_nid - Notification Id
9100 --   p_from_role - Internal Name of the comment provider
9101 --   p_to_role - Internal Name of the comment recipient
9102 --   p_action - Action performed
9103 --   p_action_source - Source from where the action is performed
9104 --   p_user_comment - Comment Text
9105 --
9106 procedure SetComments(p_nid           in number,
9107                       p_from_role     in varchar2,
9108                       p_to_role       in varchar2,
9109                       p_action        in varchar2,
9110                       p_action_source in varchar2,
9111                       p_user_comment  in varchar2)
9112 is
9113    l_from_role   varchar2(320);
9114    l_from_user   varchar2(360);
9115    l_to_user     varchar2(360);
9116    l_action_type varchar2(30);
9117    l_proxy_user  varchar2(320);
9118    l_action      varchar2(30);
9119    l_seq_num     number;
9120 begin
9121    -- Just because p_from_role was null due to some reason, there should not be failure.
9122    -- All cases are taken care to make sure from_role is valid. Just in case...
9123    if (p_from_role is null) then
9124       l_from_role := 'WF_SYSTEM';
9125    else
9126       l_from_role := p_from_role;
9127    end if;
9128 
9129    -- If p_from_role is an e-mail address with 'email:' prefixed, it is better remove it
9130    -- since it would not appear good on the UI
9131    if (substr(l_from_role, 1, 6) = 'email:') then
9132      l_from_role := substr(l_from_role, 7);
9133    end if;
9134 
9135    -- Sometimes p_from_role is email address when answering for more info request
9136    if (l_from_role = 'WF_SYSTEM') then
9137       l_from_user := Wf_Core.Translate(l_from_role);
9138    else
9139       l_from_user := nvl(Wf_Directory.GetRoleDisplayName(l_from_role), l_from_role);
9140    end if;
9141    if (p_to_role = 'WF_SYSTEM') then
9142       l_to_user := Wf_Core.Translate(p_to_role);
9143    else
9144       l_to_user := nvl(Wf_Directory.GetRoleDisplayname(p_to_role), p_to_role);
9145    end if;
9146    l_action := p_action;
9147 
9148    if (l_action in ('DELEGATE','TRANSFER')) then
9149       l_action_type := 'REASSIGN';
9150    elsif (l_action in ('QUESTION','ANSWER')) then
9151       l_action_type := 'QA';
9152    else
9153       -- Actions like RESPOND, CANCEL, SEND
9154       l_action_type := p_action;
9155    end if;
9156 
9157    -- suffix source to action... DELEGATE_RULE, FORWARD_WA, etc.
9158    if (p_action_source is not null) then
9159       l_action := l_action||'_'||p_action_source;
9160       -- if the action is performed from WA, the user performing the action
9161       -- should be acting as a proxy to another user
9162       if (p_action_source = 'WA') then
9163          l_proxy_user := Wfa_Sec.GetUser();
9164       end if;
9165    end if;
9166 
9167    -- Calculate sequence for comments in the same session
9168    l_seq_num := g_comments_seq + 1;
9169    g_comments_seq := g_comments_seq + 1;
9170 
9171    INSERT INTO wf_comments (
9172           sequence,
9173           notification_id,
9174           from_role,
9175           from_user,
9176           to_role,
9177 	  to_user,
9178           comment_date,
9179           action,
9180           action_type,
9181 	  proxy_role,
9182           user_comment,
9183           language
9184         ) VALUES (
9185           l_seq_num,
9186           p_nid,
9187           l_from_role,
9188           l_from_user,
9189           p_to_role,
9190           l_to_user,
9191           sysdate,
9192           l_action,
9193           l_action_type,
9194           l_proxy_user,
9195           p_user_comment,
9196           userenv('LANG')
9197         );
9198 
9199 exception
9200    when others then
9201       wf_core.context('Wf_Notification', 'SetComments', to_char(p_nid), p_from_role,
9202                       p_to_role, p_action, p_action_source);
9203       raise;
9204 end SetComments;
9205 
9206 --
9207 -- Resend
9208 --   Private procedure to resend a notification given the notification id. This
9209 --   procedure checks the mail status and recipient's notification preference to
9210 --   see if it is eligible to send e-mail.
9211 -- IN
9212 --   p_nid - Notification Id
9213 --
9214 procedure Resend(p_nid in number)
9215 is
9216   l_recipient_role varchar2(320);
9217   l_group_id       number;
9218   l_mail_status    varchar2(8);
9219   l_status         varchar2(8);
9220   l_message_type   varchar2(8);
9221   l_message_name   varchar2(30);
9222   l_paramlist      wf_parameter_list_t := wf_parameter_list_t();
9223 
9224   l_display_name    varchar2(360);
9225   l_email_address   varchar2(320);
9226   l_notification_pref varchar2(8);
9227   l_language        varchar2(30);
9228   l_territory       varchar2(30);
9229   l_orig_system     varchar2(30);
9230   l_orig_system_id  number;
9231   l_installed       varchar2(1);
9232   role_info_tbl  wf_directory.wf_local_roles_tbl_type;
9233 
9234 begin
9235 
9236   begin
9237     SELECT message_type, message_name, status, mail_status, nvl(more_info_role, recipient_role) recipient_role, group_id
9238     INTO   l_message_type, l_message_name, l_status, l_mail_status, l_recipient_role, l_group_id
9239     FROM   wf_notifications
9240     WHERE  notification_id = p_nid;
9241   exception
9242     when no_data_found then
9243       wf_core.token('NID', to_char(p_nid));
9244       wf_core.raise('WFNTF_NID');
9245   end;
9246 
9247   -- Get recipient information using Dir Service API. Select from WF_ROLES
9248   -- may not give the right information
9249   Wf_Directory.GetRoleInfoMail(l_recipient_role, l_display_name, l_email_address,
9250                                l_notification_pref, l_language, l_territory,
9251                                l_orig_system, l_orig_system_id, l_installed);
9252 
9253   -- Check if the notification is eligible to be e-mailed. We throw specific error
9254   -- for the UI to display appropriately to the user
9255   if (l_status <> 'OPEN') then
9256     wf_core.token('NID', to_char(p_nid));
9257     wf_core.raise('WFNTF_NID_OPEN');
9258   end if;
9259 
9260   if (l_notification_pref not in ('MAILHTML','MAILTEXT','MAILATTH','MAILHTM2')) then
9261     wf_core.token('NTF_PREF', l_notification_pref);
9262     wf_core.token('RECIPIENT', l_recipient_role);
9263     wf_core.raise('WFNTF_INVALID_PREF');
9264   end if;
9265 
9266   if (l_mail_status not in ('SENT', 'ERROR', 'FAILED', 'UNAVAIL')) then
9267     wf_core.token('MAILSTATUS', l_mail_status);
9268     wf_core.raise('WFNTF_EMAIL_NOTSENT');
9269   end if;
9270 
9271   -- Raise the event to send an e-mail
9272   UPDATE wf_notifications
9273   SET    mail_status = 'MAIL'
9274   WHERE  notification_id =  p_nid;
9275 
9276   Wf_Event.AddParameterToList('NOTIFICATION_ID', p_nid, l_paramlist);
9277   Wf_Event.AddParameterToList('ROLE', l_recipient_role, l_paramlist);
9278   Wf_Event.AddParameterToList('GROUP_ID', l_group_id, l_paramlist);
9279   Wf_Event.AddParameterToList('Q_CORRELATION_ID', l_message_type||':'||
9280                               l_message_name, l_paramlist);
9281 
9282    Wf_Directory.GetRoleInfo2(l_recipient_role, role_info_tbl);
9283    l_language := role_info_tbl(1).language;
9284 
9285    select code into l_language from wf_languages where nls_language = l_language;
9286 
9287   -- AppSearch
9288   wf_event.AddParameterToList('OBJECT_NAME',
9289   'oracle.apps.fnd.wf.worklist.server.AllNotificationsVO', l_paramlist);
9290   wf_event.AddParameterToList('CHANGE_TYPE', 'INSERT',l_paramlist);
9291   wf_event.AddParameterToList('ID_TYPE', 'PK', l_paramlist);
9292   wf_event.addParameterToList('PK_NAME_1', 'NOTIFICATION_ID',l_paramlist);
9293   wf_event.addParameterToList('PK_VALUE_1', p_nid, l_paramlist);
9294   wf_event.addParameterToList('PK_NAME_2', 'LANGUAGE',l_paramlist);
9295   wf_event.addParameterToList('PK_VALUE_2', l_language, l_paramlist);
9296 
9297   Wf_Event.Raise(p_event_name => 'oracle.apps.wf.notification.send',
9298                  p_event_key  => to_char(p_nid),
9299                  p_parameters => l_paramlist);
9300 
9301 exception
9302   when others then
9303     wf_core.context('Wf_Notification', 'Resend', to_char(p_nid));
9304     raise;
9305 end Resend;
9306 
9307 --
9308 -- getNtfResponse
9309 -- Fetches result(response) CODE and response display prompt to the notification
9310 -- IN
9311 --  p_nid - Notification ID
9312 -- OUT
9313 --  p_result_code    Result code of the notification
9314 --  p_result_display Display value of the result code
9315 
9316 procedure getNtfResponse (p_nid     in  number,
9317                           p_result_code out nocopy varchar2,
9318                           p_result_display  out nocopy varchar2)
9319 is
9320  l_result_type varchar2(250);
9321 begin
9322 
9323   begin
9324     select A.RESULT_TYPE, IAS.ACTIVITY_RESULT_CODE
9325     into l_result_type, p_result_code
9326       from WF_ITEM_ACTIVITY_STATUSES_H IAS,
9327            WF_ACTIVITIES A,
9328            WF_PROCESS_ACTIVITIES PA,
9329            WF_ITEMS I
9330       where IAS.NOTIFICATION_ID     = p_nid
9331         and IAS.ITEM_TYPE           = I.ITEM_TYPE
9332         and IAS.ITEM_KEY            = I.ITEM_KEY
9333         and IAS.PROCESS_ACTIVITY    = PA.INSTANCE_ID
9334         and I.BEGIN_DATE between A.BEGIN_DATE
9335         and nvl(A.END_DATE, I.BEGIN_DATE)
9336         and PA.ACTIVITY_NAME        = A.NAME
9337         and PA.ACTIVITY_ITEM_TYPE   = A.ITEM_TYPE;
9338   exception
9339     when NO_DATA_FOUND then
9340       select A.RESULT_TYPE, IAS.ACTIVITY_RESULT_CODE
9341         into l_result_type, p_result_code
9342           from WF_ITEM_ACTIVITY_STATUSES IAS,
9343                WF_ACTIVITIES A,
9344                WF_PROCESS_ACTIVITIES PA,
9345                WF_ITEMS I
9346           where IAS.NOTIFICATION_ID     = p_nid
9347             and IAS.ITEM_TYPE           = I.ITEM_TYPE
9348             and IAS.ITEM_KEY            = I.ITEM_KEY
9349             and IAS.PROCESS_ACTIVITY    = PA.INSTANCE_ID
9350             and I.BEGIN_DATE between A.BEGIN_DATE
9351             and nvl(A.END_DATE, I.BEGIN_DATE)
9352             and PA.ACTIVITY_NAME        = A.NAME
9353             and PA.ACTIVITY_ITEM_TYPE   = A.ITEM_TYPE;
9354   end;
9355 
9356   p_result_display  := wf_core.activity_result( l_result_type, p_result_code );
9357 
9358 exception
9359   when NO_DATA_FOUND then
9360     p_result_code  := null;
9361     p_result_display  := null;
9362   when others then
9363     wf_core.context('Wf_Notification', 'getNtfResponse', to_char(p_nid));
9364     raise;
9365 end getNtfResponse;
9366 
9367 --
9368 -- PropagateHistory (PUBLIC)
9369 --  This API allows Product Teams to publish custom action
9370 --  to WF_COMMENTS table.
9371 --
9372 procedure PropagateHistory(p_item_type     in varchar2,
9373                            p_item_key      in varchar2,
9374                            p_document_id   in varchar2,
9375                            p_from_role     in varchar2,
9376                            p_to_role       in varchar2,
9377                            p_action        in varchar2,
9378                            p_action_source in varchar2,
9379                            p_user_comment  in varchar2)
9380 is
9381  --Get the nids in curs_nid which have the attribute document_id
9382  cursor curs_nid(l_doc_id varchar2,l_item_type varchar2,l_item_key varchar2) is
9383    select wfn.notification_id
9384    from  wf_item_activity_statuses wfas, wf_notifications wfn , wf_notification_attributes wfna
9385    where wfna.name             = '#DOCUMENT_ID'
9386    and   wfna.text_value       =  l_doc_id
9387    and   wfas.item_type        =  l_item_type
9388    and   wfas.item_key         =  l_item_key
9389    and   wfn.notification_id   =  wfna.notification_id
9390    and   wfas.notification_id  =  wfn.group_id;
9391 begin
9392   for comment_curs in curs_nid(p_document_id,p_item_type,p_item_key) loop
9393     --Now loop through the cursor and set the comments
9394     wf_notification.SetComments(p_nid           => comment_curs.notification_id,
9395                                 p_from_role     => p_from_role,
9396                                 p_to_role       => p_to_role,
9397                                 p_action        => p_action,
9398                                 p_action_source => p_action_source,
9399                                 p_user_comment  => p_user_comment);
9400  end loop;
9401 exception
9402   when others then
9403     wf_core.context('Wf_Notification', 'propagatehistory', p_item_type,p_item_key, p_document_id);
9404     raise;
9405 end;
9406 
9407 --
9408 -- Resend_Failed_Error_Ntfs (CONCURRENT PROGRAM API)
9409 --   API to re-enqueue notifications with mail_status FAILED and/or ERROR
9410 --   in order to re-send them. Mailer had processed these notifications
9411 --   earlier and updated the status since these notifications could not be
9412 --   delivered/processed. Only FYI notifications with ERROR mail status
9413 --   can be resent.
9414 --
9415 -- OUT
9416 --   errbuf  - CP error message
9417 --   retcode - CP return code (0 = success, 1 = warning, 2 = error)
9418 -- IN
9419 --   p_mail_status - Mail status that needs to be resent.
9420 --                   ERROR - Only for FYI notifications
9421 --                   FAILED - All notifications
9422 --   p_msg_type - Message type of the notification
9423 --   p_role     - Workflow role whose notifications are to be re-enqueued
9424 --   p_from_date - Notifications sent on or after this date
9425 --   p_to_date   - Notifications sent on before this date
9426 --               - Type is varchar2 because CP reports problems with Date type
9427 procedure Resend_Failed_Error_Ntfs(errbuf        out nocopy varchar2,
9428                                    retcode       out nocopy varchar2,
9429                                    p_mail_status in varchar2,
9430                                    p_msg_type    in varchar2,
9431                                    p_role        in varchar2,
9432 				   p_from_date    in varchar2,
9433 				   p_to_date      in varchar2 )
9434 is
9435   l_errname  varchar2(30);
9436   l_errmsg   varchar2(2000);
9437   l_errstack varchar2(2000);
9438   l_nid      number;
9439   l_from_date date;
9440   l_to_date   date;
9441 
9442   -- Cursor def. for FAILED notification
9443   CURSOR c_failed_ntfs(cp_msg_type varchar2,
9444                        cp_role     varchar2,
9445 		       cp_from_date date,
9446 		       cp_to_date   date)
9447   IS
9448   SELECT notification_id
9449   FROM   wf_notifications wn
9450   WHERE  wn.status = 'OPEN'
9451   AND    wn.mail_status = 'FAILED'
9452   AND    wn.recipient_role like nvl(cp_role, '%')
9453   AND    wn.message_type like nvl(cp_msg_type, '%')
9454           --  No date conversion  is required on wn.begin_date
9455   AND   (cp_from_date is null or  wn.begin_date  >= cp_from_date)
9456   AND   (cp_to_date   is null or  wn.begin_date  <= cp_to_date ) ;
9457 
9458   -- Cussor for FYI errored out notifications
9459   CURSOR c_error_fyi_ntfs(cp_msg_type varchar2,
9460                           cp_role     varchar2 ,
9461 			  cp_from_date date,
9462 		          cp_to_date   date)
9463   IS
9464   SELECT notification_id
9465   FROM   wf_notifications wn
9466   WHERE  wn.status = 'OPEN'
9467   AND    wn.mail_status = 'ERROR'
9468   AND    wn.recipient_role like nvl(cp_role, '%')
9469   AND    wn.message_type like nvl(cp_msg_type, '%')
9470   AND   (cp_from_date is null or wn.begin_date >= cp_from_date)
9471   AND   (cp_to_date   is null or wn.begin_date <= cp_to_date )
9472   AND NOT EXISTS (
9473          SELECT 1
9474          FROM   wf_message_attributes wma,
9475                 wf_notifications wn2
9476          WHERE  wn2.notification_id = wn.notification_id
9477          AND    wma.message_type = wn2.message_type
9478          AND    wma.message_name = wn2.message_name
9479          AND    wma.subtype = 'RESPOND'
9480          AND    rownum = 1);
9481 
9482 begin
9483 
9484   -- Convert from varchar2 to date format
9485   if(p_from_date is not null) then
9486    l_from_date := to_date(p_from_date, wf_core.canonical_date_mask);
9487   end if;
9488 
9489   if(p_to_date is not null) then
9490    l_to_date   := to_date(p_to_date, wf_core.canonical_date_mask);
9491   end if;
9492 
9493   -- if mail status is specified as null, both failed and errored
9494   -- ntfs require to be resent
9495   if (nvl(p_mail_status, 'FAILED') = 'FAILED') then
9496     open c_failed_ntfs(p_msg_type, p_role, l_from_date, l_to_date);
9497     loop
9498       fetch c_failed_ntfs into l_nid;
9499       exit when c_failed_ntfs%NOTFOUND;
9500       begin
9501        	-- Raise event
9502 	Wf_Notification.Resend(l_nid);
9503 
9504       exception
9505         when others then
9506           -- ignore any error while enqueing
9507           Wf_Core.Clear();
9508       end;
9509       commit;
9510     end loop;
9511     close c_failed_ntfs;
9512   end if;
9513 
9514   -- only errored FYI notifications are resent. For response required
9515   -- ntfs, the activity would be errored which can be retried
9516   if (nvl(p_mail_status, 'ERROR') = 'ERROR') then
9517     open c_error_fyi_ntfs(p_msg_type, p_role , l_from_date, l_to_date);
9518     loop
9519       fetch c_error_fyi_ntfs into l_nid;
9520       exit when c_error_fyi_ntfs%NOTFOUND;
9521       begin
9522          -- Raise event
9523 	 Wf_Notification.Resend(l_nid);
9524       exception
9525         when others then
9526           -- ignore any error while enqueing
9527           Wf_Core.Clear();
9528       end;
9529       commit;
9530     end loop;
9531     close c_error_fyi_ntfs;
9532   end if;
9533 
9534   -- successful completion
9535   errbuf := '';
9536   retcode := '0';
9537 exception
9538   when others then
9539     -- get error message into errbuf
9540     wf_core.get_error(l_errname, l_errmsg, l_errstack);
9541     if (l_errmsg is not null) then
9542       errbuf := l_errmsg;
9543     else
9544       errbuf := sqlerrm;
9545     end if;
9546 
9547     -- return 2 for error
9548     retcode := '2';
9549 end Resend_Failed_Error_Ntfs;
9550 
9551 begin
9552   -- Loads the user's nls date mask
9553   g_nls_date_mask := sys_context('USERENV','NLS_DATE_FORMAT');
9554 
9555   if (instr(upper(g_nls_date_mask), 'HH') = 0) then
9556     g_nls_date_mask := g_nls_date_mask||' HH24:MI:SS';
9557   end if;
9558 
9559 End WF_Notification;
9560