DBA Data[Home] [Help]

PACKAGE BODY: APPS.WF_NOTIFICATION

Source


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