1<?php
2	/**************************************************************************\
3	* AngleMail	http://www.anglemail.org								*
4	*/
5	/**************************************************************************\
6	* AngleMail - E-Mail Message Processing Core Functions					*
7	* This file written by Angelo "Angles" Puglisi <angles@aminvestments.com>	*
8	* Copyright (C) 2001-2003 Angelo "Angles" Puglisi						*
9	* -------------------------------------------------------------------------			*
10	* Originally Based on Aeromail by Mark Cushman <mark@cushman.net>		*
11	* http://the.cushman.net/											*
12	* AngleMail appreciates the origins of the phpGroupWare email app from		*
13	* from Cushman. The phpGroupWare email module was maintained by Angles	*
14	* prior to developing into what is today known as AngleMail				*
15	* -------------------------------------------------------------------------			*
16	* This file designed to work as part of a drop in module for phpGroupWare		*
17	* http://www.phpgroupware.org							*
18	* -------------------------------------------------------------------------			*
19	* This library is free software; you can redistribute it and/or modify it		*
20	* under the terms of the GNU Lesser General Public License as published by	*
21	* the Free Software Foundation; either version 2.1 of the License,			*
22	* or any later version.								*
23	* This library is distributed in the hope that it will be useful, but			*
24	* WITHOUT ANY WARRANTY; without even the implied warranty of		*
25	* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	*
26	* See the GNU Lesser General Public License for more details.			*
27	* You should have received a copy of the GNU Lesser General Public License 	*
28	* along with this library; if not, write to the Free Software Foundation,		*
29	* Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA			*
30	\**************************************************************************/
31
32	/* $Id: class.mail_msg_base.inc.php 15724 2005-02-15 06:48:06Z skwashd $ */
33
34	/**************************************************************************\
35	*	STRUCTURES
36	\**************************************************************************/
37
38	/*!
39	@class mail_dcom_holder
40	@abstract  This class has one purpose, PHP3 compatibility. It simply holds the DataCommunications Object.
41	@author Angles
42	@discussion   In tests PHP3 could not handle having an object as a simple array item. Therefor we had to
43	make this class to be the thing that holdes a class.dcom (DataCommunications) object. PHP4 had no problem
44	with the previous code but it was changed to this for backwards compatibility with PHP3, otherwise the
45	script exits with a parse error. Multiple account capability requires that any account can have a stream open
46	to its server at any time, independant of any other DataCommunications object, simce one account may be IMAP
47	while another account may be POP3. Using an array of these mail_dcom_holders, we can achieve this goal and
48	still have PHP3 compatibility.
49	@example
50		$GLOBALS['phpgw_dcom_'.$acctnum] = new mail_dcom_holder;
51		$GLOBALS['phpgw_dcom_'.$acctnum]->dcom = CreateObject("email.mail_dcom", $this_server_type);
52	now we have an array based structure where each array key can have a DataCommunications object of its
53	own, thereby allowing for multiple mail accounts of any type to be open simultaneously.
54	*/
55	class mail_dcom_holder
56	{
57		var $dcom = '';
58	}
59
60	/**************************************************************************\
61	*	BASE MAIL CLASS
62	\**************************************************************************/
63	/*!
64	@class mail_msg_base
65	@abstract  One of three classes that combine to form the mail_msg object. The other classes are mail_msg_wrappers
66	and filename mail_msg_display which is actually called mail_msg because it is the final class extension loaded and
67	therefor bears the name we want the class as a whole to have, This class forms the common  core of the email functionality,
68	@author Angles, and with some small remnants of Aeromail present
69	@discussion The three files and classes inter relate in the following way to end up with the mail_msg object
70	FIRST,  include class mail_msg_base, then SECONDLY  incluse mail_msg_wrappers extending mail_msg_base,
71	then THIRDLY  include mail_msg which extends mail_msg_wrappers and, by inheritance, mail_msg_base
72	All functions that are at the heart of email functionality are in this class. This class is in the process of being further OOPd so
73	programmers can more easily use it without having to know abou the internal details. When multiple accounts are in
74	use, each active account can be accessed and controlled through this class. Each active account with a stream open to its
75	maiul server has its own DataCommunications object which used to be a part of this class but had to be moved elsewhere
76	for PHP3 compatibility, but still each DataCommunications object is in an array and is accessed via an account number
77	which is comtrolled in this class. This class handles organizing the preferences for each of the multiple accounts. In general,
78	a simple class var array keeps the multiple account information organized as a numbered array based on integer account number.
79	There are already many OOP methods that hide complexities from the programmer, such as the preference and arg access functions.
80	Many of those functions can optionally take an account number and foilder name, but if none is supplies the functions uses logic to
81	obtain valid account number and folder name for whatever account you are dealing with. There is extensice debug output available
82	by setting the various debug flags between 1 to 3, or to 0 if no debug is wanted. More documentation is provided for each function
83	in this class.
84	*/
85
86	/*!
87	@classvar known_external_args
88	@abstract List of GET POST variables that the email class and app is supposed to be aware of.
89	@discussion In a complex app such as email it becomes difficult to keep track of all the GET POST vars that the app
90	is expected to know about. Therefor, all GPC vars this class is expected to be aware of are listed here with an explanation
91	of what they do.
92	@param msgball (typed array) msgball "object" is a message-descriptive "object" or associative arrays that has all
93	important message reference data as array data, passed via URI (real or embedded). With multiple accounts enabled
94	most data such as a folder name or a message number, mean nothing by themselves because we do not know which
95	account they are supposed to apply to. Msgball typed array combines all necessary data, the acctnum, folder, msgnum,
96	and sometimes other data such as part_no, into one thing. Use msgball anytime you are dealing with messages, if you only
97	you do not care about individual email messages, such as when switching from one folder to another, then you can use the
98	fldball typed array, see below, which does not require such detailed information.
99	@param fldball (typed array) ldball "object" is an assiciative array of folder data passed via URI (real or embedded).
100	Use fldball when instructing this class to do things that are not specific to any particular message number, such as when
101	opening a stream to an account, or when switching from folder to folder. Generally the least amount of information necessary is
102	fldball[acctnum] (int)and fldball[folder] (string). This class know to expect less information in a fldball, whereas a msgball is expected
103	to contain very detailed information.
104	@param fldball_fake_uri (string in the form of a URI request) This is usually sourced from a folder combobox where
105	HTML only allows a single value to be passed, thus we make a string in the syntax of a URI to contain multiple data values
106	in that single HTML element, in this way we embed extra data in an otherwise very limiting HTML element.
107	Note: even php's POST vars array handling can not do anything with a HTML combobox option value.
108	See this example:  POST data:
109		folder_fake_uri="fldball['folder']=INBOX&fldball['acctnum']=0"
110	Will be processed into this (using php function "parse_str()" to emulate URI GET behavior)
111		fldball[folder] => INBOX
112		fldball[acctnum] => 0
113	@param delmov_list (numbered array with each element being a msgball Fake URI string)
114	Used with mail message moves, appends, and deletes, holds the "from" data, as in move this message "from"
115	here to... and the "to" destimation data is contained in the to_fldball, see below.
116	This comes from the checkbox form data in uiindex.index page, where multiple boxes may be checked
117	but the POST data is limited to a simple string per checkbox, so additional information is embedded in delmov_list
118	and converted to an associative array via php function "parse_str". This is typically used to move or delete a list of
119	messages, but since this array are all msgball items, mail functions such as append and move are no longer limited
120	to one account or one folder, the msgball array can instruct the class to move messages beween different mail accounts
121	of different types and to and from any folder therein.
122	@param to_fldball_fake_uri (string in the form of a URI get request) Used to pass complex data deom a combo box
123	which is limited to submitting only a single string. This is generally used to describe the destination acctnum and folder
124	for message moves, appends, and deletes. Php function parse_str is used to make this string data into the typed array
125	to_fldball, see below.
126	@param to_fldball (typed array with emements [acctnum] as int, and [folder]  as string) Used to describe the destination
127	in mail moves, or appends and it is formed by using php function parse_str on a POST submitted arg "to_fldball_fake_uri".
128	@param move_postmove_goto ? When moving a message while viewing it in the view message page, this var will be passed
129	used to tell us what to show the user after we do the move, it will be a URI string that begins with "menuaction".
130	@param sort ?
131	@param order ?
132	@param start these three vars preserve the users current choice of sort and order between page views and message actions,
133	and start is used to help the app in nextmatches behavior.
134	@param td (int) ?
135	@param tm (int) ?
136	@param tf (string) these three vars are used to === REPORT ON MOVES/DELETES ===
137	td = total deleted ; tm = total moved, tm used with tf, folder messages were moved to.
138	usage: (outgoing) class.boaction: when action on a message is taken, report info is passed in these.
139	(in) uiindex.index: here the report is diaplayed above the message list, used to give user feedback.
140	Generally these are in the URI (GET var, not a form POST var)
141	@param what (string) === MOVE/DELETE MESSAGE INSTRUCTIONS ===
142	Possible Values: (outgoing) class.uiindex "move", "delall", used with delmov_list to move or delete messages.
143	AND with "to_fldball" which is the destination acctnum and folder for the move.
144	(outgoing) uimessage: "delete" used with a msgball to delete or move an individual message directly from the view
145	message page.
146	 (in) class.boaction: instruction on what action to preform on 1 or more message(s) (move or delete)
147	NOTE: the destination for the move is described in "delmov_list" which is a msgball list of
148	msgball's which are message-descriptive "objects" or associative arrays that have all
149	the necessary data on each message that is to be deleted or moved.
150	The iuindex.index page uses the same form with different submit buttons (what)
151	so the "delmov_list" is applicable to either deleting or moving messages depending on which submit button was clicked.
152	@param action (string) used for === INSTRUCTIONS FOR ACTION ON A MESSAGE OR FOLDER ===
153	 (a) (out and in) uifolder: used with "target_folder" and (for renaming) "source_folder"
154	and has instructions to add/delete/rename folders: create(_expert), delete(_expert), rename(_expert)
155	where "X_expert" indicates do not modify the target_folder, the user know about of namespaces and delimiters.
156	(b) uicompose: can be "reply" "replyall" "forward" which is passed on to bosend
157	(c) bosend: when set to "forward" and used with "fwd_proc" instructs on how to construct the SMTP mail
158	@param msgball[part_no] (string)  representing a specific MIME part number (example "2.1.2") within a multipart message.
159	Used by (a) uicompose: used in combination with msgball,  (b) boaction.get_attach: used in combination with msgball.
160	@param encoding (string) possible values  "base64" "qprint" Used by
161	(a) uicompose: if replying to, we get the body part to reply to, it may need to be un-qprint-ed, and
162	(b) boaction.get_attach: appropriate decoding of the part to feed to the browser.
163	@param fwd_proc (string) Possible Values  "encapsulation", "pushdown (not yet supported)" Used as
164	(outgoing) uimessage much detail is known about the messge, there the forward proc method is determined. Used by:
165	(a) uicompose: used with action = forward, (outgoing) passed on to bosend,
166	(b) bosend: used with action = forward, instructs on how the SMTP message should be structured.
167	@param name (string) the name of an attachment
168	@param type (string) the mime type of an attachment
169	@param subtype (string) the mime subtype of an attachment.
170	These 3 args comprise this info is passed to the browser to help the browser know what to do with the part a.k.a. attachment.
171	 (outgoing) uimessage: "name" is set in the link to the addressbook,  it's the actual "personal" name part of the email address
172	and boaction.get_attach: the name of the attachment.
173	Note these params are NOT part of the msgball array, because with the other data already in msgball, it should be obvious
174	what these items are supposed to apply to.
175	@param target_fldball (typed array) and
176	@param source_fldball_fake_uri (string of type URI Get) used to make the source_fldball param, see below.
177	@param source_fldball (typed array) used for === FOLDER ADD/DELETE/RENAME & DISPLAY ===
178	Note param source_fldball is used in renaming folders only. Used for:
179	(outgoing) and (in) bofolder: used with "action" to add/delete/rename a mailbox folder,
180	where "action" can be: create, delete, rename, create_expert, delete_expert, rename_expert.
181	@param show_long (string "true" if set) Used by uifolder it is set there and sent back to itself uifolder.
182	If set:  indicates to show 'long' folder names with namespace and delimiter NOT stripped off.
183	@param to (string) part of === COMPOSE VARS ===
184	@param cc (string) ?
185	@param bcc (string) ?
186	@param body (string) These compose vars, as most commonly NOT used with "mailto" have following usage
187	(note if used with "mailto", less common, then see "mailto" below). Used as:
188	(outgoing) uiindex, uimessage: any click on a clickable email address in these pages, will call uicompose
189	passing "to" (possibly in rfc long form address),
190	(outgoing) uimessage: when reading a message and you click reply, replyall, or forward calls uicompose with EITHER
191	(1) a msgball so that compose gets all needed info, (more effecient than passing all those GPC args) OR
192	(2) to,cc,subject,body may be passed.
193	 (outgoing) uicompose: ALL contents of input items to, cc, subject, body, etc... are passed as GPC args to bosend.
194	 (in) (a) compose.php: text that should go in to and cc (and maybe subject and body) text boxes
195	are passed as incoming GPC args, and
196	(in) (b) bosend: (fill me in - I got lazy)
197	@param sender (string) Less Common Usage  RFC says use header "Sender" ONLY WHEN the sender of the
198	email is NOT the author, this is somewhat rare.
199	@param req_notify This is a recent addition to request notification of delivery for the message being composed
200	@param attach_sig (boolean True is set, or not present or unset if False) USAGE
201	(outgoing) uicompose: if checkbox attach sig is checked, this is passed as GPC var to bosent, and
202	(in) bosend: indicate if message should have the user's "sig" added to the message.
203	@param msgtype (string) DEPRECIATED, flag to tell phpgw to invoke "special" custom processing of the message
204	extremely rare, may be obsolete (not sure), most implementation code is commented out. Used as:
205	(outgoing) currently NO page actually sets this var, and
206	(a) bosend: will add the flag, if present, to the header of outgoing mail, and
207	(b) bomessage identify the flag and call a custom proc.
208	@param personal (string) the name part of an email address,m used with the following param
209	@param mailto (string) === MAILTO URI SUPPORT === USAGE
210	(in and out) bocompose: support for the standard mailto html document mail app call can be used with the typical compose
211	vars (see above), indicates that to, cc, and subject should be treated as simple MAILTO args.
212	@param no_fmt (boolean) === MESSAGE VIEWING MOD === Usage
213	(in and outgoing) uimessage: will display plain body parts without any html formatting added.
214	@param html_part (string) === VIEW HTML INSTRUCTIONS ===
215	actually a pre-processed HTML/RELATED MIME part with the image ID's swapped with msgball data
216	for each "related" image, so the MUA may obtain the images from the email server using these msgball details.
217	@param force_showsize (boolean) === FOLDER STATISTICS - CALCULATE TOTAL FOLDER SIZE.
218	As a speed up measure, and to reduce load on the IMAP server, there is an option to skip the calculating of the total folder size
219	if certain conditions are met, such as more then 100 messages in a folder, the user may request an override of this for 1 page view.
220	@param mlist_set === SEARCH RESULT MESSAGE SET === DEPRECIATED - not yet fixed.
221	@param folder (string) most often a part of a msgball or fldball, the function begin_request will obtain the folder value from
222	(1) args_array, the args passed directly to the begin_request function in the form of folder => Sent, if any,
223	or (2) a fldball GPC or (3) a msgball GPC, or  (4) default "INBOX". === THE FOLDER ARG discussion ===
224	Folder name is used in almost every procedure, IMAP can be logged into only one folder at a time and POP3
225	has only one folder anyway (INBOX),  INBOX is the assumed default value for "folder".
226	@expunge_folders (array) list of folder names (not urlencoded) that need to be expunged, if any, for an account
227	@param ex_acctnum (int) all preference handling of extra accounts passes this as the account number "ex" = "extra".
228	@param COMPLETE_ME, are there more GPC args we use in the email app?
229	*/
230
231	class mail_msg_base
232	{
233		// ----  account - an array where key=mail_account  and  value=all_class_vars for that account
234		var $a = array();
235		var $acctnum = 0;
236		var $fallback_default_acctnum = 0;
237
238		// this object is 3 files, each an object "extending" the other, this prevents 3 constructor calls
239		var $been_constructed = False;
240		// data storage for caching functions moved to SO object
241		var $so = '##NOTHING##';
242
243		// ---- compat for PHP < 4.1 vs. > 4.2
244		var $ref_GET = '##NOTHING##';
245		var $ref_POST = '##NOTHING##';
246		var $ref_SERVER = '##NOTHING##';
247		var $ref_FILES = '##NOTHING##';
248		var $ref_SESSION = '##NOTHING##';
249
250		// ----  args that are known to be used for email
251		// externally filled args, such as thru GPC values, or xmlrpc call
252		var $known_external_args = array();
253		// args that are typically set and controlled internally by this class
254		var $known_internal_args = array();
255		// ----  class-wide settings - not account specific
256		// some functions use $not_set instead of actuallt having something be "unset"
257		var $not_set = '-1';
258		// EXPERIMENTAL: functions required to return a refernce can return a ref to this to indicate a failure
259		var $nothing = '##NOTHING##';
260		// EXPERIMENTAL: straight delete (not a move to trash) use this psudo acct. folder name to fill the "to_fldball"
261		var $del_pseudo_folder = '##DELETE##';
262		// when uploading files for attachment to outgoing mail, use this location in the filesystem
263		var $att_files_dir;
264		// a limited group of folder related langs are handled here, most others are page specific not here
265		var $common_langs=array();
266		// *maybe* future use - does the client's browser support CSS
267		var $browser = 0;
268		// use message UIDs instead of "message sequence numbers" in requests to the mail server
269		var $force_msg_uids = True;
270		// phpgw 0.9.16 was last for old template system, after that is xslt, make note of version below
271		var $phpgw_before_xslt = '-1';
272		// raw prefs, before we process them to extract extra acct and/or filters data, not of much use
273		var $unprocessed_prefs=array();
274		// raw filters array for use by the filters class, we just put the data here, that is all, while collecting other prefs
275		var $raw_filters=array();
276		// move URI data is buffered to here, then executed at one time
277		var $buffered_move_commmands = array();
278		// since move URIs are added in a speed sensitive loop, manually track the count, avoids repeated count() commands
279		var $buffered_move_commmands_count=0;
280		// delete URI data is buffered to here, then executed at one time (FUTURE)
281		var $buffered_delete_commmands = array();
282		// I think crypto var this is no longer used, uses global crypto now I think (which does little anyway, w/o mcrypt)
283		//var $crypto;
284
285		// reply messages get this "quoting" prefix to each line, see bocompose and bosend
286		//var $reply_prefix = '>';
287		var $reply_prefix = '> ';
288		//var $reply_prefix = '| ';
289
290		// ---- Data Caching  ----
291		var $use_cached_prefs = True;
292		//var $use_cached_prefs = False;
293
294		// (A) session data caching in appsession, for data that is temporary in nature
295		// right now this means msgball_list in appsession, and a bunch of stuff we generate (mailsvr_str) stored in L1 cache
296		// also tries to appsession cache the "processed prefs" during begin_request (NOTE: expire this on pref subit so new prefs actually take effect)
297		var $session_cache_enabled=True;
298		//var $session_cache_enabled=False;
299
300		// ----  session cache runthru without actuall saving data to appsession (for debugging only, rarely useful anyway)
301		//var $session_cache_debug_nosave = True;
302		var $session_cache_debug_nosave = False;
303
304		// ----  session cache uses "events" to directly "freshen" the cache without the mailserver
305		// NOTE msgball_list is ALWAYS appsession cached in "session_cache_enabled" even if "session_cache_extreme" is false,
306		// repeat: msgball_list is still appsession cached in non-extreme mode as long as "session_cache_enabled" is True.
307		// also, note that folder_info is ONLY appsession cached in extreme-mode, BUT folder_info is only L1 cached in non-extreme mode
308		var $session_cache_extreme = True;
309		//var $session_cache_extreme = False;
310
311		// ---- Private Table Caching  ---- data store is migrating to anglemails own DB table, should we use it?
312		// value will be double checked to make sure the table is present, if not present, it does to False
313		var $use_private_table = True;
314		//var $use_private_table = False;
315
316		// ---- how long to assume appsession cached "folder_status_info" is deemed VALID in seconds
317		// ---- only applies if "session_cache_extreme" is true
318		// ---- please no lower than 10 seconds
319		var $timestamp_age_limit = 240;
320
321		// EXTRA ACCOUNTS
322		// used for looping thru extra account data during begin request
323		var $ex_accounts_count = 0;
324		// extra_accounts[X][acctnum] = integer
325		// extra_accounts[X][status] = empty | enabled | disabled
326		var $extra_accounts = array();
327		// same as above but includes the default account, makes checking streams easier
328		var $extra_and_default_acounts = array();
329
330		// svc_debug object goes here in the constructor
331		var $dbug='##NOTHING##';
332
333		// DEBUG FLAGS generally take int 0, 1, 2, or 3
334		var $debug_logins = 0;
335		var $debug_session_caching = 0;
336		// email so object debug level
337		var $debug_so_class = 0;
338			// debuugging level3 can lead to dumping is msgball_list may have thousands of elements
339		var $debug_allow_magball_list_dumps = False;
340		//var $debug_allow_magball_list_dumps = True;
341			// these "events" are used to alter cached data to keep it reasonably in sync with the server, so we do not need
342			// to contact the server again if we can emulate the data change resulting from an event.
343		var $debug_events = 0;
344		var $debug_wrapper_dcom_calls = 0;
345		var $debug_accts = 0;
346		var $debug_args_input_flow = 0;
347		var $debug_args_oop_access = 0;
348		var $debug_args_special_handlers = 0;
349		var $debug_index_page_display = 0;
350		// this is just being implemented
351		var $debug_message_display = 0;
352		// dormant code, "longterm_caching" currently OBSOLETE
353		var $debug_longterm_caching = 0;
354		//var $skip_args_special_handlers = 'get_mailsvr_callstr, get_mailsvr_namespace, get_mailsvr_delimiter, get_folder_list';
355		//var $skip_args_special_handlers = 'get_folder_list';
356		var $skip_args_special_handlers = '';
357
358		/*!
359		@function mail_msg_base
360		@abstract CONSTRUCTOR place holder, does nothing
361		*/
362		function mail_msg_base()
363		{
364			if (($this->debug_logins > 0) && (is_object($this->dbug->out))) { $this->dbug->out('mail_msg('.__LINE__.'): *constructor*: $GLOBALS[PHP_SELF] = ['.$GLOBALS['PHP_SELF'].'] $this->acctnum = ['.$this->acctnum.']  get_class($this) : "'.get_class($this).'" ; get_parent_class($this) : "'.get_parent_class($this).'"<br />'); }
365			if ($this->debug_logins > 0) { echo 'mail_msg('.__LINE__.'): *constructor*: $GLOBALS[PHP_SELF] = ['.$GLOBALS['PHP_SELF'].'] $this->acctnum = ['.$this->acctnum.']  get_class($this) : "'.get_class($this).'" ; get_parent_class($this) : "'.get_parent_class($this).'"<br />'; }
366			return;
367		}
368
369		/*!
370		@function initialize_mail_msg
371		@abstract the real CONSTRUCTOR needs to be called by name.
372		@discussion This used to be called in the final extends file to this aggregrate class.
373		NEW now called only from bootstrap class, because the preferences API class keeps constructing
374		this object for every account it makes preferences for, I would change that but changing the API
375		is like moving a mountain, so I remove all auto constructor functions and make this have to
376		be called explicitly to stop useless runthroughs caused by preferences API.
377		*/
378		function initialize_mail_msg()
379		{
380			if ($this->been_constructed == True)
381			{
382				// do not run thru this again, probably one of the "extends" objects call this
383				return;
384			}
385			// Set this so we do not run thru this again
386			$this->been_constructed = True;
387
388			// ... OK ... now we actually do the CONSTRUCTOR
389			// svc_debug object goes here
390			if ($this->dbug == '##NOTHING##')
391			{
392				$this->dbug = CreateObject('email.svc_debug');
393			}
394
395			if ($this->debug_logins > 0) { $this->dbug->out('mail_msg.initialize_mail_msg('.__LINE__.'): ENTERING manual *constructor*: $GLOBALS[PHP_SELF] = ['.$GLOBALS['PHP_SELF'].'] $this->acctnum = ['.$this->acctnum.']  get_class($this) : "'.get_class($this).'" ; get_parent_class($this) : "'.get_parent_class($this).'"<br />'); }
396			if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.initialize_mail_msg('.__LINE__.'): manual *constructor*: $this->acctnum = ['.$this->acctnum.'] ; $this->a  DUMP:', $this->a); }
397			if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.initialize_mail_msg('.__LINE__.'): manual *constructor*: extra data $p1 (if provided): '.serialize($p1).'<br />'); }
398
399			if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.initialize_mail_msg('.__LINE__.'): manual *constructor*: checking and or setting GET and POST reference based on PHP version<br />'); }
400			// make GPC reference for php versions < 4.1 and > 4.2
401			// since this constructor is apparently called many times
402			// during the script run (not sure why) we check if we've already done it first
403			$force_GPC_new = False;
404			//$force_GPC_new = True;
405			if (($this->ref_GET == '##NOTHING##')
406			|| ($this->ref_POST == '##NOTHING##')
407			|| ($this->ref_SERVER == '##NOTHING##')
408			|| ($this->ref_FILES == '##NOTHING##')
409			|| ($this->ref_SESSION == '##NOTHING##'))
410			{
411				// set this to force using the new superglobals
412				if ($force_GPC_new == True)
413				{
414					$this->ref_GET = &$_GET;
415					$this->ref_POST = &$_POST;
416					$this->ref_SERVER = &$_SERVER;
417					$this->ref_FILES = &$_FILES;
418					$this->ref_SESSION = &$_SESSION;
419				}
420				// make the appropriate reference (pointer) based on php version 4.1.0
421				elseif ($this->minimum_version("4.1.0"))
422				{
423					$this->ref_GET = &$_GET;
424					$this->ref_POST = &$_POST;
425					$this->ref_SERVER = &$_SERVER;
426					$this->ref_FILES = &$_FILES;
427					$this->ref_SESSION = &$_SESSION;
428				}
429				// fallback to the "old way"
430				else
431				{
432					$this->ref_GET = &$GLOBALS['HTTP_GET_VARS'];
433					$this->ref_POST = &$GLOBALS['HTTP_POST_VARS'];
434					$this->ref_SERVER = &$GLOBALS['HTTP_SERVER_VARS'];
435					//$this->ref_FILES = &$HTTP_POST_FILES;
436					$this->ref_FILES = &$GLOBALS['HTTP_POST_FILES'];
437					$this->ref_SESSION = &$GLOBALS['HTTP_SESSION_VARS'];
438				}
439			}
440
441			// SO object has data storage functions
442			if ($this->so == '##NOTHING##')
443			{
444				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.initialize_mail_msg('.__LINE__.'): manual *constructor*: creating sub SO object "so_mail_msg"<br />'); }
445				$this->so = CreateObject('email.so_mail_msg');
446			}
447
448			// Data Store Double Check
449			// TEMPORARY ONLY DURING MIGRATION AND TABLE DEVELOPMENT
450			if ($this->use_private_table)
451			{
452				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.initialize_mail_msg('.__LINE__.'): manual *constructor*: checking if "so_am_table_exists"<br />'); }
453				if ($this->so->so_am_table_exists() == False)
454				{
455					$this->use_private_table = False;
456				}
457			}
458
459			// UNDER DEVELOPMENT when to use cached preferences
460			if ($this->use_cached_prefs == True)
461			{
462				// any preferences page menuaction is a NO NO to cached prefs
463				if (stristr($this->ref_GET['menuaction'], 'preferences.'))
464				{
465					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.initialize_mail_msg('.__LINE__.'): manual *constructor*: string "preferences." is in menuaction so NO CACHED PREFS, setting $this->use_cached_prefs to False<br />'); }
466					$this->use_cached_prefs = False;
467				}
468			}
469
470			// UNDER DEVELOPMENT bulk data query from AngleMail DB
471			// only necessary to grab huge bulk data for INDEX page
472			// and some other menuactions too, but we will add more later
473			if ((stristr($this->ref_GET['menuaction'], 'email.uiindex'))
474			|| (stristr($this->ref_GET['menuaction'], 'email.uimessage.message')))
475			{
476				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.initialize_mail_msg('.__LINE__.'): manual *constructor*: calling $this->so->so_prop_use_group_data(True)<br />'); }
477				//$this->so->use_group_data = True;
478				$this->so->so_prop_use_group_data(True);
479			}
480			else
481			{
482				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.initialize_mail_msg('.__LINE__.'): manual *constructor*: calling $this->so->so_prop_use_group_data(False)<br />'); }
483				//$this->so->use_group_data = False;
484				$this->so->so_prop_use_group_data(False);
485			}
486
487			// trying this new thing for template porting issues
488			// relfbecker recommends NOT using a version test for xslt check
489			if ($this->phpgw_before_xslt == '-1')
490			{
491				if (is_object($GLOBALS['phpgw']->xslttpl))
492				{
493					$this->phpgw_before_xslt = False;
494				}
495				else
496				{
497					$this->phpgw_before_xslt = True;
498				}
499			}
500			/*
501			// relfbecker recommends NOT using a version test for xslt check
502			if ($this->phpgw_before_xslt == '-1')
503			{
504				$this_ver = $GLOBALS['phpgw_info']['server']['versions']['phpgwapi'];
505				$pre_xslt_ver = '0.9.14.0.1.1';
506				if (!$this_ver)
507				{
508					// damn stupid fallback if the api moves the version to another place
509					$this->phpgw_before_xslt = True;
510				}
511				// this is a function in phpgwapi "common_functions" file for phpgw 0.9.15+
512				elseif (function_exists(amorethanb))
513				{
514					if (amorethanb($this_ver, $pre_xslt_ver))
515					{
516						// this phpgw version is after the switch to xslt templates
517						$this->phpgw_before_xslt = False;
518					}
519					else
520					{
521						// this phpgw version is NOT in the xslt era
522						$this->phpgw_before_xslt = True;
523					}
524				}
525				else
526				{
527					if ($GLOBALS['phpgw']->common->cmp_version_long($this_ver, $pre_xslt_ver))
528					{
529						// this phpgw version is after the switch to xslt templates
530						$this->phpgw_before_xslt = False;
531					}
532					else
533					{
534						// this phpgw version is NOT in the xslt era
535						$this->phpgw_before_xslt = True;
536					}
537				}
538			}
539			*/
540
541			$this->known_external_args = array(
542				// === NEW GPC "OBJECTS" or Associative Arrays ===
543				// msgball "object" is a message-descriptive "object" or associative arrays that has all
544				// important message reference data as array data, passed via URI (real or embedded)
545				'msgball',
546				// fldball "object" is an assiciative array of folder data passed via URI (real or embedded)
547				'fldball',
548
549				// === NEW HTTP POST VARS Embedded Associative Arrays ===
550				// "fldball_fake_uri" HTTP_POST_VARS varsion of a URI GET "fldball"
551				// usually sourced from a folder combobox where HTML only allows a single value to be passed
552				// thus we make a string in the syntax of a URI to contain multiple data values in that single HTML element
553				// in this way we embed extra data in an otherwise very limiting HTML element
554				// note: even php's POST vars array handling can not do anything with a HTML combobox option value.
555				// example: POST data
556				// folder_fake_uri="fldball['folder']=INBOX&fldball['acctnum']=0"
557				// Will be processed into this (using php function "parse_str()" to emulate URI GET behavior)
558				// fldball[folder] => INBOX
559				// fldball[acctnum] => 0
560				'fldball_fake_uri',
561
562				// "delmov_list_fake_uri"
563				// comes from the checkbox form data in uiindex.index page, where multiple
564				// boxes may be checked but the POST data is limited to a simple string per checkbox,
565				// so additional information is embedded in delmov_list_fake_uri and converted to an
566				// associative array via php function "parse_str"
567				//'delmov_list_fake_uri',
568				'delmov_list',
569				// if moving msgs, this is where they should go
570				'to_fldball_fake_uri',
571				'to_fldball',
572				// when moving a message while viewing it in the uimessage page, this var will be passed
573				// telling us what to show the user after we do the move, it will be a URI string that begins with "menuaction"
574				'move_postmove_goto',
575				// === SORT/ORDER/START ===
576				// if sort,order, and start are sometimes passed as GPC's, if not, default prefs are used
577				'sort',
578				'order',
579				'start',
580
581				// newsmode is NOT yet implemented
582				//'newsmode',
583
584				// === REPORT ON MOVES/DELETES ===
585				// ----  td, tm: integer  ----
586				// ----  tf: string  ----
587				// USAGE:
588				//	 td = total deleted ; tm = total moved, tm used with tf, folder messages were moved to
589				// (outgoing) class.boaction: when action on a message is taken, report info is passed in these
590				// (in) index.php: here the report is diaplayed above the message list, used to give user feedback
591				// generally these are in the URI (GET var, not a form POST var)
592				'td',
593				'tm',
594				'tf',
595
596				// === MOVE/DELETE MESSAGE INSTRUCTIONS ===
597				// ----  what: string ----
598				// USAGE:
599				// (outgoing) class.uiindex "move", "delall"
600				//	used with msglist (see below) an array (1 or more) of message numbers to move or delete
601				//	AND with "toacctnum" which is the acctnum associated with the "tofolder"
602				// (outgoing) message.php: "delete" used with msgnum (see below) what individual message to delete
603				// (in) class.boaction: instruction on what action to preform on 1 or more message(s) (move or delete)
604				'what',
605					//'tofolder',
606					//'toacctnum',
607				// *update*
608				// both "tofolder" and "toacctnum" are incorporated into "delmov_list" which is a msgball list of
609				// msgball's which are message-descriptive "objects" or associative arrays that have all
610				// the necessary data on each message that is to be deleted or moved.
611				// the iuindex.index page uses the same form with different submit buttons (what)
612				// so the "delmov_list" is applicable to either deleting or moving messages depending
613				// on which submit button was clicked
614				// 'delmov_list', (see above)
615
616				// (passed from class.uiindex) this may be an array of numbers if many boxes checked and a move or delete is called
617				//'msglist',
618
619				// *update* "msglist" is being depreciated!
620
621				// === INSTRUCTIONS FOR ACTION ON A MESSAGE OR FOLDER ===
622				// ----  action: string  ----
623				// USAGE:
624				// (a) (out and in) folder.php: used with "target_folder" and (for renaming) "source_folder"
625				//	instructions to add/delete/rename folders: create(_expert), delete(_expert), rename(_expert)
626				//	where "X_expert" indicates do not modify the target_folder, the user know about of namespaces and delimiters
627				// (b) compose.php: can be "reply" "replyall" "forward"
628				//	passed on to send_message.php
629				// (c) send_message.php: when set to "forward" and used with "fwd_proc" instructs on how to construct
630				//	the SMTP mail
631				'action',
632				// ----  orig_action: string  ----
633				// USAGE:
634				// preserves the original "action" of the compose page because new and forward body lines
635				// need to be shorter then reply to we need to remember the desired "action" and store it here
636				// also used to preserve this thru the spell check process too
637				// initially we only put this only in the GET part of GPC
638				// why is this different, "orig_action" can have the value "new" meaning new mail
639				// whereas plain old "action" can not tell us of a new mail situation, not right now anyway,
640				// so the "new" value can be preserved to the send code and also thru the spell page and back too
641				'orig_action',
642
643				// === MESSAGE NUMBER AND MIME PART REFERENCES ===
644				// *update* now in msgball
645				// msgnum: integer
646				// USAGE:
647				// (a) class.boaction, called from from message.php: used with "what=delete" to indicate a single message for deletion
648				// (b) compose.php: indicates the referenced message for reply, replyto, and forward handling
649				// (c) boaction.get_attach: the msgnum of the email that contains the desired body part to get
650				// *update* now in msgball
651				//'msgnum',
652
653				// ----  part_no: string  ----
654				// representing a specific MIME part number (example "2.1.2") within a multipart message
655				// (a) compose.php: used in combination with msgnum
656				// (b) boaction.get_attach: used in combination with msgnum
657
658				// *update* now in msgball
659				//'part_no',
660
661				// ----  encoding: string  ----
662				// USAGE: "base64" "qprint"
663				// (a) compose.php: if replying to, we get the body part to reply to, it may need to be un-qprint'ed
664				// (b) boaction.get_attach: appropriate decoding of the part to feed to the browser
665				'encoding',
666
667				// ----  fwd_proc: string  ----
668				// USAGE: "encapsulation", "pushdown (not yet supported 9/01)"
669				// (outgoing) message.php much detail is known about the messge, there the forward proc method is determined
670				// (a) compose.php: used with action = forward, (outgoing) passed on to send_message.php
671				// (b) send_message.php: used with action = forward, instructs on how the SMTP message should be structured
672				'fwd_proc',
673				// ----  name, type, subtype: string  ----
674				// the name, mime type, mime subtype of the attachment
675				// this info is passed to the browser to help the browser know what to do with the part
676				// (outgoing) message.php: "name" is set in the link to the addressbook,  it's the actual "personal" name part of the email address
677				// boaction.get_attach: the name of the attachment
678
679				// NOT in msgball, with the other data already in msgball, it should be obvious
680				// what these items are ment to apply to
681				'name',
682				'type',
683				'subtype',
684
685				// === FOLDER ADD/DELETE/RENAME & DISPLAY ===
686				// ----  "target_folder" , "source_folder" (source used in renaming only)  ----
687				// (outgoing) and (in) folder.php: used with "action" to add/delete/rename a mailbox folder
688				// 	where "action" can be: create, delete, rename, create_expert, delete_expert, rename_expert
689				//'target_folder',
690				'target_fldball',
691				//'source_folder',
692				'source_fldball',
693				'source_fldball_fake_uri',
694				// ----  show_long: unset / true  ----
695				// folder.php: set there and sent back to itself
696				// if set - indicates to show 'long' folder names with namespace and delimiter NOT stripped off
697				'show_long',
698
699				// === COMPOSE VARS ===
700				// as most commonly NOT used with "mailto" then the following applies
701				//	(if used with "mailto", less common, then see "mailto" below)
702				// USAGE:
703				// ----  to, cc, body, subject: string ----
704				// (outgoing) index.php, message.php: any click on a clickable email address in these pages
705				//	will call compose.php passing "to" (possibly in rfc long form address)
706				// (outgoing) message.php: when reading a message and you click reply, replyall, or forward
707				//	calls compose.php with EITHER
708				//		(1) a msgnum ref then compose gets all needed info, (more effecient than passing all those GPC args) OR
709				//		(2) to,cc,subject,body may be passed
710				// (outgoing) compose.php: ALL contents of input items to, cc, subject, body, etc...
711				//	are passed as GPC args to send_message.php
712				// (in) (a) compose.php: text that should go in to and cc (and maybe subject and body) text boxes
713				//	are passed as incoming GPC args
714				// (in) (b) send_message.php: (fill me in - I got lazy)
715				'to',
716				'cc',
717				// bcc: we send the MTA the "RCPT TO" command for these BUT no bcc info is put in the message headers
718				'bcc',
719				// body - POST var, never in URI (GET) that I know of, but it is possible, URI (EXTREMELY rare)
720				'body',
721				'subject',
722				// Less Common Usage:
723				// ----  sender : string : set or unset
724				// RFC says use header "Sender" ONLY WHEN the sender of the email is NOT the author, this is somewhat rare
725				'sender',
726				// ----  attach_sig: set-True/unset  ----
727				// USAGE:
728				// (outgoing) compose.php: if checkbox attach sig is checked, this is passed as GPC var to sent_message.php
729				// (in) send_message.php: indicate if message should have the user's "sig" added to the message
730				'attach_sig',
731				// ---- req_notify: set-True/unset ----
732				// USAGE:
733				// (outgoing) compose.php: if checkbox req notify is checked, this should go as GPC to sent_message.php
734				// (in) send_message.php: FIXME! should (somehow) attach the appropiate headers to the outgoing mail
735				'req_notify',
736				// ----  msgtype: string  ----
737				// USAGE:
738				// flag to tell phpgw to invoke "special" custom processing of the message
739				// 	extremely rare, may be obsolete (not sure), most implementation code is commented out
740				// (outgoing) currently NO page actually sets this var
741				// (a) send_message.php: will add the flag, if present, to the header of outgoing mail
742				// (b) message.php: identify the flag and call a custom proc
743				'msgtype',
744
745				// === MAILTO URI SUPPORT ===
746				// ----  mailto: unset / ?set?  ----
747				// USAGE:
748				// (in and out) compose.php: support for the standard mailto html document mail app call
749				// 	can be used with the typical compose vars (see above)
750				//	indicates that to, cc, and subject should be treated as simple MAILTO args
751				'mailto',
752				'personal',
753
754				// === MESSAGE VIEWING MODS ===
755				// ----  no_fmt: set-True/unset  ----
756				// USAGE:
757				// (in and outgoing) message.php: will display plain body parts without any html formatting added
758				'no_fmt',
759
760				// === VIEW HTML INSTRUCTIONS ===
761				// html_part: string : actually a pre-processed HTML/RELATED MIME part with
762				// the image ID's swapped with msgball data for each "related" image, so the
763				// MUA may obtain the images from the email server using these msgball details
764				'html_part',
765
766				// === FOLDER STATISTICS - CALCULATE TOTAL FOLDER SIZE
767				// as a speed up measure, and to reduce load on the IMAP server
768				// there is an option to skip the calculating of the total folder size
769				// user may request an override of this for 1 page view
770				'force_showsize',
771
772				// === SEARCH RESULT MESSAGE SET ===
773				'mlist_set',
774				// *update* DEPRECIATED - not yet fixed
775
776				// === THE FOLDER ARG ===
777				// used in almost every procedure, IMAP can be logged into only one folder at a time
778				// and POP3 has only one folder anyway (INBOX)
779				// this *may* be overrided elsewhere in the class initialization and/or login
780				// if not supplied anywhere, then INBOX is the assumed default value for "folder"
781
782				// *update* "folder" obtains it's value from (1) args_array, (2) fldball, (3) msgball, (4) default "INBOX"
783				'folder',
784
785				// keeps track of what folders, if any, need to be "expunged" for an account
786				// MOVED to internal arg, this has nothing to do with GPC vars (see below)
787				//'expunge_folders',
788
789				// which email account is the object of this operation
790				// *update* now in fldball
791				//'acctnum',
792				// all preference handling of extra accounts passes this as the account number "ex" = "extra"
793				'ex_acctnum'
794				);
795
796			$this->known_internal_args = array(
797				// === OTHER ARGS THAT ARE USED INTERNALLY  ===
798				'folder_status_info',
799				'folder_list',
800				'mailsvr_callstr',
801				'mailsvr_namespace',
802				'mailsvr_delimiter',
803				'mailsvr_stream',
804				'mailsvr_account_username',
805				// use this uri in any auto-refresh request - filled during "fill_sort_order_start_msgnum()"
806				'index_refresh_uri',
807				'verified_trash_folder_long',
808
809				// keeps track of what folders, if any, need to be "expunged" for an account
810				// UPDATE: "expunge_folders" can NOT BE HERE because it should NOT EXIST unless set during a move or delete
811				//  putting it here will initialize it to a value of "" (empty string) which is different than unset.
812				//'expunge_folders',
813
814				// experimental: Set Flag indicative we've run thru this function
815				'already_grab_class_args_gpc'
816			);
817			//if ($this->debug_logins > 2) { $this->dbug->out('mail_msg.initialize_mail_msg('.__LINE__.'): manual constructor: $this->known_args[] DUMP:', $this->known_args); }
818			if ($this->debug_logins > 0) { $this->dbug->out('mail_msg.initialize_mail_msg('.__LINE__.'): manual *constructor*: LEAVING<br />'); }
819		}
820
821		/*!
822		@function begin_request
823		@abstract initializes EVERYTHING, do not forget to call end_session before you leave this transaction.
824		@param $args_array May be phased out, but right now the most used param is "do_login" => True
825		@author Angles
826		@description the who enchalada happens here. Recently only class msg_bootstrap calls this directly.
827		*/
828		// ----  BEGIN request from Mailserver / Initialize This Mail Session  -----
829		function begin_request($args_array)
830		{
831			if ($this->debug_logins > 0) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): ENTERING'.'<br />'); }
832			if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): feed var args_array[] DUMP:', $args_array); }
833
834			// Grab GPC vars, after we get an acctnum, we'll put them in the appropriate account's "args" data
835			// issue?: which acctnum arg array would this be talking to when we inquire about "already_grab_class_args_gpc"?
836			if ( ($this->get_isset_arg('already_grab_class_args_gpc'))
837			&& ((string)$this->get_arg_value('already_grab_class_args_gpc') != '') )
838			{
839				// somewhere, there's already been a call to grab_class_args_gpc(), do NOT re-run
840				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): "already_grab_class_args_gpc" is set, do not re-grab<br />'); }
841				if ($this->debug_logins > 2) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): "already_grab_class_args_gpc" pre-existing $this->get_all_args() DUMP:', $this->get_all_args()); }
842				$got_args=array();
843			}
844			else
845			{
846				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): "already_grab_class_args_gpc" is NOT set, call grab_class_args_gpc() now<br />'); }
847				$got_args=array();
848				$got_args = $this->grab_class_args_gpc();
849			}
850
851			// FIND THE "BEST ACCTNUM" and set it
852			if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): about to call:  get_best_acctnum($args_array, $got_args) <br />'); }
853			$acctnum = $this->get_best_acctnum($args_array, $got_args);
854			if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): "get_best_acctnum" returns $acctnum ['.$acctnum.']<br />'); }
855			if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): * * * *SETTING CLASS ACCTNUM* * * * by calling $this->set_acctnum('.serialize($acctnum).')<br />'); }
856			$this->set_acctnum($acctnum);
857
858			// SET GOT_ARGS TO THAT ACCTNUM
859			// use that acctnum to set "got_args" to the appropiate acctnum
860			if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): about to call: $this->set_arg_array($got_args); <br />'); }
861			$this->set_arg_array($got_args, $acctnum);
862			if ($this->debug_logins > 2) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): post set_arg_array $this->get_all_args() DUMP:', $this->get_all_args()); }
863
864			// Initialize Internal Args
865			if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): about to call: "init_internal_args_and_set_them('.$acctnum.')"<br />'); }
866			$this->init_internal_args_and_set_them($acctnum);
867
868			if ($this->debug_logins > 2) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): POST "grab_class_args_gpc", "get_best_acctnum", and "init_internal_args_and_set_them" : this->get_all_args() DUMP:', $this->get_all_args()); }
869
870			// (chopped out the re-use existing object code - never worked right, maybe later...)
871
872			// ----  Things To Be Done Whether You Login Or Not  -----
873
874			// UNDER DEVELOPMEMT - backwards_compat with sessions_db where php4 sessions are not being used
875			// ALSO UNDER DEVELOPMENT - using private table for anglemail
876			if (($GLOBALS['phpgw_info']['server']['sessions_type'] == 'db')
877			|| ($this->use_private_table == True))
878			{
879				/*
880				if (! is_object($this->so))
881				{
882					$this->initialize_mail_msg();
883				}
884				*/
885
886				// REF_SESSION should not really be in $_SESSION namespace so RE-CREATE all this outside of php4 sessions
887				$this->so->prep_db_session_compat('begin_request LINE '.__LINE__);
888			}
889
890
891			if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): about to handle email preferences and setup extra accounts<br />'); }
892			// ----  Obtain Preferences Data  ----
893
894			/*
895			// UNDER DEVELOPMEMT: caching the prefs data
896			// data we need to DB save to cache final processed prefs
897			$this->unprocessed_prefs
898			$this->raw_filters
899			$this->ex_accounts_count
900			$this->extra_accounts
901			$this->extra_and_default_acounts
902			$this->a[X]->prefs
903			// where X is the account number, we can use "set_pref_array(array_data, acctnum) for each account
904
905			// ok lets make an array to hold this data in the DB
906			$cached_prefs = array();
907			$cached_prefs['unprocessed_prefs'] = array();
908			$cached_prefs['raw_filters'] = array();
909			$cached_prefs['ex_accounts_count'] = '0';
910			$cached_prefs['extra_accounts'] = array();
911			$cached_prefs['extra_and_default_acounts'] = array();
912			$cached_prefs['a'] = array();
913			*/
914			// ---- GET FROM CACHE THE COMPLETED PREF DATA
915			//$this->use_cached_prefs = True;
916			//$this->use_cached_prefs = False;
917			if ($this->use_cached_prefs == False)
918			{
919				$cached_prefs = $this->nothing;
920			}
921			else
922			{
923				/*
924				// data we need to DB save to cache final processed prefs
925				$this->unprocessed_prefs
926				$this->raw_filters
927				$this->ex_accounts_count
928				$this->extra_accounts
929				$this->extra_and_default_acounts
930				$this->a[X]->['prefs']
931				// where X is the account number, we can use "set_pref_array(array_data, acctnum) for each account
932
933				// ok this is what we should get from the DB storage (we use appsession for now)
934				$cached_prefs = array();
935				$cached_prefs['unprocessed_prefs'] = array();
936				$cached_prefs['raw_filters'] = array();
937				$cached_prefs['ex_accounts_count'] = '0';
938				$cached_prefs['extra_accounts'] = array();
939				$cached_prefs['extra_and_default_acounts'] = array();
940				$cached_prefs['a'] = array();
941				*/
942				// get the data from appsession, we use compression to avoid problems unserializing
943				$my_location = '0;cached_prefs';
944				$cached_prefs = $this->so->so_appsession_passthru($my_location);
945				if ($this->debug_logins > 2) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): raw $cached_prefs as returned from cache DUMP:', $cached_prefs); }
946				if ($this->debug_logins > 2) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): raw serialized $cached_prefs is '.htmlspecialchars(serialize($cached_prefs)).'<br />'); }
947			}
948
949			// ok if we actually got cached_prefs then maybe we can use them
950			if (($this->use_cached_prefs == True)
951			&& ((string)$cached_prefs != $this->nothing)
952			&& (is_array($cached_prefs))
953			&& (isset($cached_prefs['extra_and_default_acounts'])))
954			{
955				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): raw $cached_prefs deemed to actually have usable data, so process it<br />'); }
956				// UN-defang the filters
957				// NO remember that filters are left in defang (htmlquotes encoded) form
958				// UNTIL they are going to be used, then bofilters defangs them
959				//for ($x=0; $x < count($cached_prefs['raw_filters']); $x++)
960				//{
961				//	$cached_prefs['raw_filters'][$x]['filtername'] = $this->db_defang_decode($cached_prefs['raw_filters'][$x]['filtername']);
962				//	$cached_prefs['raw_filters'][$x]['source_accounts']['folder'] = $this->db_defang_decode($cached_prefs['raw_filters'][$x]['source_accounts']['folder']);
963				//	for ($y=0; $y < count($cached_prefs['raw_filters']['matches']); $y++)
964				//	{
965				//		$cached_prefs['raw_filters'][$x]['matches'][$y]['matchthis']
966				//			= $this->db_defang_decode($cached_prefs['raw_filters'][$x]['matches'][$y]['matchthis']);
967				//	}
968				//	for ($y=0; $y < count($cached_prefs['raw_filters']['actions']); $y++)
969				//	{
970				//		$cached_prefs['raw_filters'][$x]['actions'][$y]['folder']
971				//			= $this->db_defang_decode($cached_prefs['raw_filters'][$x]['actions'][$y]['folder']);
972				//	}
973				//}
974				// UN-defang the rest of the prefs that may need it
975				$defang_these = array();
976				$defang_these[0] = 'passwd';
977				$defang_these[1] = 'email_sig';
978				$defang_these[2] = 'trash_folder_name';
979				$defang_these[3] = 'sent_folder_name';
980				$defang_these[4] = 'userid';
981				$defang_these[5] = 'address';
982				$defang_these[6] = 'mail_folder';
983				$defang_these[7] = 'fullname';
984				$defang_these[8] = 'account_name';
985				$loops = count($cached_prefs['extra_and_default_acounts']);
986				for ($i=0; $i < $loops; $i++)
987				{
988					for ($x=0; $x < count($defang_these); $x++)
989					{
990						$defang_word = $defang_these[$x];
991						if (isset($cached_prefs['a'][$i]['prefs'][$defang_word]))
992						{
993							$cached_prefs['a'][$i]['prefs'][$defang_word]
994								= $this->db_defang_decode($cached_prefs['a'][$i]['prefs'][$defang_word]);
995						}
996					}
997				}
998				if ($this->debug_logins > 2) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): retrieved $cached_prefs AFTER UN-defang DUMP:', $cached_prefs); }
999				// lets fill the data
1000				$this->unprocessed_prefs = $cached_prefs['unprocessed_prefs'];
1001				$this->raw_filters = $cached_prefs['raw_filters'];
1002				$this->ex_accounts_count = $cached_prefs['ex_accounts_count'];
1003				$this->extra_accounts = $cached_prefs['extra_accounts'];
1004				$this->extra_and_default_acounts = $cached_prefs['extra_and_default_acounts'];
1005				$loops = count($this->extra_and_default_acounts);
1006				for ($i=0; $i < $loops; $i++)
1007				{
1008					$this->set_pref_array($cached_prefs['a'][$i]['prefs'], $i);
1009				}
1010				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): successfully retrieved and applied $cached_prefs<br />'); }
1011			}
1012			//$allow_prefs_shortcut = True;
1013			//$allow_prefs_shortcut = False;
1014			//if ((is_array($GLOBALS['phpgw_info']['user']['preferences']['email']) == True)
1015			//&& ($allow_prefs_shortcut == True))
1016			//{
1017			//	if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): prefs array already created by the API, NOT calling "create_email_preferences"<br />'); }
1018			//	$this->unprocessed_prefs = array();
1019			//	$this->unprocessed_prefs['email'] = array();
1020			//	$this->unprocessed_prefs['email'] = $GLOBALS['phpgw_info']['user']['preferences']['email'];
1021			//	if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): did NOT call create_email_preferences, prefs were already available in $GLOBALS["phpgw_info"]["user"]["preferences"]["email"] <br />'); }
1022			//}
1023			else
1024			{
1025				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): $cached_prefs either disabled or no data was cached<br />'); }
1026				// make this empty without question, since cached prefs were not recovered
1027				$cached_prefs = array();
1028				// IT SEEMS PREFS FOR ACCT 0 NEED TO RUN THRU THIS TO FILL "Account Name" thingy
1029				// obtain the preferences from the database, put them in $this->unprocessed_prefs, note THIS GETS ALL PREFS for some reason, not just email prefs?
1030				if ($this->debug_logins > 2) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): BEFORE processing email prefs, GLOBALS[phpgw_info][user][preferences][email] DUMP:', $GLOBALS['phpgw_info']['user']['preferences']['email']); }
1031
1032				//$this->unprocessed_prefs = $GLOBALS['phpgw']->preferences->create_email_preferences();
1033				$tmp_email_only_prefs = array();
1034				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): calling create_email_preferences, may be time consuming<br />'); }
1035				$tmp_email_only_prefs = $GLOBALS['phpgw']->preferences->create_email_preferences();
1036				// clean "unprocessed_prefs" so all prefs oher than email are NOT included
1037				$this->unprocessed_prefs = array();
1038				$this->unprocessed_prefs['email'] = array();
1039				$this->unprocessed_prefs['email'] = $tmp_email_only_prefs['email'];
1040				$tmp_email_only_prefs = array();
1041				unset($tmp_email_only_prefs);
1042				//if ($this->debug_logins > 2) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): AFTER "create_email_preferences" GLOBALS[phpgw_info][user][preferences] DUMP<pre>'; print_r($GLOBALS['phpgw_info']['user']['preferences']); echo '</pre>'); }
1043
1044				// BACKWARDS COMPAT for apps that have no clue what multiple accounts are about
1045				// fill email's $GLOBALS['phpgw_info']['user']['preferences'] with the data for backwards compatibility (we don't use that)
1046				// damn, where did email's prefs get filled already? Where are they getting filled, anyway do not re-fill if not needed
1047				// NO - IT IS POSSIBLE THIS MAY NOT CATCH ALL PREF CHANGES IN CORNER CASES
1048				if (is_array($GLOBALS['phpgw_info']['user']['preferences']['email']) == False)
1049				{
1050					//$GLOBALS['phpgw_info']['user']['preferences'] = $this->unprocessed_prefs;
1051					$GLOBALS['phpgw_info']['user']['preferences']['email'] = array();
1052					$GLOBALS['phpgw_info']['user']['preferences']['email'] = $this->unprocessed_prefs['email'];
1053				}
1054				//echo 'dump3 <pre>'; print_r($GLOBALS['phpgw_info']['user']['preferences']); echo '</pre>';
1055				// BUT DO NOT put unneeded stuff in there, [ex_accounts] and [filters] multilevel arrays
1056				// are not needed for mackward compat, we need them internally but external apps do not use this raw data
1057				//if (isset($GLOBALS['phpgw_info']['user']['preferences']['email']['ex_accounts']))
1058				//{
1059				//	$GLOBALS['phpgw_info']['user']['preferences']['email']['ex_accounts'] = array();
1060				//	unset($GLOBALS['phpgw_info']['user']['preferences']['email']['ex_accounts']);
1061				//}
1062				//if (isset($GLOBALS['phpgw_info']['user']['preferences']['email']['filters']))
1063				//{
1064				//	$GLOBALS['phpgw_info']['user']['preferences']['email']['filters'] = array();
1065				//	unset($GLOBALS['phpgw_info']['user']['preferences']['email']['filters']);
1066				//}
1067				if ($this->debug_logins > 2) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): AFTER backwards_compat and cleaning GLOBALS[phpgw_info][user][preferences] DUMP:', $GLOBALS['phpgw_info']['user']['preferences']); }
1068
1069
1070				// first, put the filter data from prefs in a holding var for use by the filters class if needed
1071				// raw filters array for use by the filters class, we just put the data here, that is all, while collecting other prefs
1072				$this->raw_filters = array();
1073				if ((isset($this->unprocessed_prefs['email']['filters']))
1074				&& (is_array($this->unprocessed_prefs['email']['filters'])))
1075				{
1076					$this->raw_filters = $this->unprocessed_prefs['email']['filters'];
1077					// not get that out of "unprocessed_prefs" because it is not needed there any more
1078					$this->unprocessed_prefs['email']['filters'] = array();
1079					unset($this->unprocessed_prefs['email']['filters']);
1080				}
1081				//if ($this->debug_logins > 2) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): put filter data in $this->raw_filters DUMP<pre>'; print_r($this->raw_filters); echo '</pre>'); }
1082
1083				// second, set the prefs for the default, base acct 0, BUT do not give it data it does not need
1084				// we already got "filters" out of "unprocessed_prefs", so when setting acct0 prefs, do not give it the "ex_accounts" array
1085				$acct0_prefs_cleaned = array();
1086				$acct0_prefs_cleaned = $this->unprocessed_prefs;
1087				if ((isset($acct0_prefs_cleaned['email']['ex_accounts']))
1088				&& (is_array($acct0_prefs_cleaned['email']['ex_accounts'])))
1089				{
1090					$acct0_prefs_cleaned['email']['ex_accounts'] = array();
1091					unset($acct0_prefs_cleaned['email']['ex_accounts']);
1092				}
1093				// now we can use that to set the prefs for the base account
1094
1095				// ---  process pres for in multi account enviornment ---
1096				// for our use, put prefs in a class var to be accessed thru OOP-style access calls in mail_msg_wrapper
1097				// since we know these prefs to be the  top level prefs, for the default email account, force them into acctnum 0
1098				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): putting top level, default account, pref data in acct 0 with $this->set_pref_array($acct0_prefs_cleaned[email], 0); <br />'); }
1099				if ($this->debug_logins > 2) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): $acct0_prefs_cleaned[email] DUMP:', $acct0_prefs_cleaned['email']); }
1100				//$this->set_pref_array($this->unprocessed_prefs['email'], 0);
1101				$this->set_pref_array($acct0_prefs_cleaned['email'], 0);
1102				$acct0_prefs_cleaned = array();
1103				unset($acct0_prefs_cleaned);
1104
1105
1106				// ===  EXTRA ACCOUNTS  ===
1107				// they are located in an array based at $this->unprocessed_prefs['email']['ex_accounts'][]
1108				// determine what extra accounts have been defined
1109				// note: php3 DOES have is_array(), ok to use it here
1110				if ((isset($this->unprocessed_prefs['email']['ex_accounts']))
1111				&& (is_array($this->unprocessed_prefs['email']['ex_accounts'])))
1112				{
1113					$this->ex_accounts_count = count($this->unprocessed_prefs['email']['ex_accounts']);
1114					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): $this->unprocessed_prefs[email][ex_accounts] is set and is_array, its count: $this->ex_accounts_count: ['.$this->ex_accounts_count.']<br />'); }
1115					if ($this->debug_logins > 2) { $this->dbug->out('$this->unprocessed_prefs[email][ex_accounts] DUMP:', $this->unprocessed_prefs['email']['ex_accounts']); }
1116					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): about to process extra account data ; $this->ex_accounts_count: ['.$this->ex_accounts_count.']<br />'); }
1117					// note: extra accounts lowest possible value = 1, NOT 0
1118					// also, $key, although numbered integers, may not be conticuous lowest to highest (may be empty or missing elements inbetween)
1119
1120					// ---- what accounts have some data defined
1121					// array_extra_accounts[X]['acctnum'] : integer
1122					// array_extra_accounts[X]['status'] string = "enabled" | "disabled" | "empty"
1123					//while(list($key,$value) = each($this->unprocessed_prefs['email']['ex_accounts']))
1124					while(list($key,$value) = each($this->unprocessed_prefs['email']['ex_accounts']))
1125					{
1126						if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): inside loop: for each $this->unprocessed_prefs[email][ex_accounts] ; $key: ['.serialize($key).'] $value DUMP:', $value); }
1127						// if we are here at all then this array item must have some data defined
1128						$next_pos = count($this->extra_accounts);
1129						$this->extra_accounts[$next_pos] = array();
1130						$this->extra_accounts[$next_pos]['acctnum'] = (int)$key;
1131						// ----  is this account "enabled", "disabled" or is this array item "empty"
1132						// first, see if it has essential data, if not, it's an empty array item
1133						if ( (!isset($this->unprocessed_prefs['email']['ex_accounts'][$key]['fullname']))
1134						|| (!isset($this->unprocessed_prefs['email']['ex_accounts'][$key]['email_sig']))
1135						|| (!isset($this->unprocessed_prefs['email']['ex_accounts'][$key]['layout'])) )
1136						{
1137							// this account lacks essential data needed to describe an account, it must be an "empty" element
1138							if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): inside loop: account ['.$key.'] is *empty*: $this->unprocessed_prefs[email][ex_accounts]['.$key.']: ['.serialize($this->unprocessed_prefs['email']['ex_accounts'][$key]).']<br />'); }
1139							$this->extra_accounts[$next_pos]['status'] = 'empty';
1140						}
1141						// ... so the account is not empty ...
1142						elseif ( (isset($this->unprocessed_prefs['email']['ex_accounts'][$key]['ex_account_enabled']))
1143						&& ((string)$this->unprocessed_prefs['email']['ex_accounts'][$key]['ex_account_enabled'] != ''))
1144						{
1145							// this account is defined AND enabled,
1146							if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): inside loop: account ['.$key.'] is *enabled*: $this->unprocessed_prefs[email][ex_accounts]['.$key.'][ex_account_enabled]:  ['.serialize($this->unprocessed_prefs['email']['ex_accounts'][$key]['ex_account_enabled']).']<br />'); }
1147							$this->extra_accounts[$next_pos]['status'] = 'enabled';
1148						}
1149						else
1150						{
1151							// this account is defined BUT not enabled
1152							if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): inside loop: account ['.$key.'] is *disabled*: $this->unprocessed_prefs[email][ex_accounts]['.$key.'][ex_account_enabled]:  ['.serialize($this->unprocessed_prefs['email']['ex_accounts'][$key]['ex_account_enabled']).']<br />'); }
1153							$this->extra_accounts[$next_pos]['status'] = 'disabled';
1154						}
1155
1156						// IF ENABLED, then
1157						if ($this->extra_accounts[$next_pos]['status'] == 'enabled')
1158						{
1159							// PROCESS EXTRA ACCOUNT PREFS
1160							// run thru the create prefs function requesting this particular acctnum
1161							// fills in certain missing data, and does some sanity checks, and any data processing that may be necessary
1162							$sub_tmp_prefs = array();
1163							// we "fool" create_email_preferences into processing extra account info as if it were top level data
1164							// by specifing the secong function arg as the integer of this particular enabled account
1165							$this_ex_acctnum = $this->extra_accounts[$next_pos]['acctnum'];
1166							if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): CALLING create_email_preferences("", $this_ex_acctnum) for specific account, where $this_ex_acctnum: ['.serialize($this_ex_acctnum).'] <br />'); }
1167							$sub_tmp_prefs = $GLOBALS['phpgw']->preferences->create_email_preferences('', $this_ex_acctnum);
1168							// now put these processed prefs in the correct location  in our prefs array
1169							$this->set_pref_array($sub_tmp_prefs['email'], $this_ex_acctnum);
1170						}
1171					}
1172					// extra_and_default_acounts is the same as above but has default account inserted at position zero
1173					$this->extra_and_default_acounts = array();
1174					// first put in the default account
1175					$this->extra_and_default_acounts[0]['acctnum'] = 0;
1176					$this->extra_and_default_acounts[0]['status'] = 'enabled';
1177					// now add whetever extra accounts we processed above
1178					$loops = count($this->extra_accounts);
1179					for ($i=0; $i < $loops; $i++)
1180					{
1181						$this->extra_and_default_acounts[$i+1]['acctnum'] = $this->extra_accounts[$i]['acctnum'];
1182						$this->extra_and_default_acounts[$i+1]['status'] = $this->extra_accounts[$i]['status'];
1183					}
1184					if ($this->debug_logins > 2) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): $this->extra_accounts DUMP:', $this->extra_accounts); }
1185					if ($this->debug_logins > 2) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): $this->extra_and_default_acounts DUMP:', $this->extra_and_default_acounts); }
1186				}
1187				else
1188				{
1189					$this->ex_accounts_count = 0;
1190					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): $this->unprocessed_prefs[email][ex_accounts] NOT set or NOT is_array, $this->ex_accounts_count: ['.$this->ex_accounts_count.']<br />'); }
1191				}
1192				// if NO extra accounts axist, we STILL need to put the default account inextra_and_default_acounts
1193				// extra_and_default_acounts will not have been handled whatsoever if no extra accounts exist
1194				// so make sure the default account is there
1195				if (count($this->extra_and_default_acounts) == 0)
1196				{
1197					$this->extra_and_default_acounts = array();
1198					// first put in the default account
1199					$this->extra_and_default_acounts[0]['acctnum'] = 0;
1200					$this->extra_and_default_acounts[0]['status'] = 'enabled';
1201				}
1202				// -end- extra account init handling
1203			}
1204
1205			//if ($this->debug_logins > 2) { echo 'mail_msg.begin_request('.__LINE__.'): POST create_email_preferences GLOBALS[phpgw_info][user][preferences][email] dump:<pre>'; print_r($GLOBALS['phpgw_info']['user']['preferences']['email']) ; echo '</pre>';}
1206			//if ($this->debug_logins > 2) { echo 'mail_msg.begin_request('.__LINE__.'): POST create_email_preferences $this->get_all_prefs() dump:<pre>'; print_r($this->get_all_prefs()) ; echo '</pre>';}
1207			if ($this->debug_logins > 2) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): POST create_email_preferences direct access dump of $this->a DUMP:', $this->a); }
1208			//if ($this->debug_logins > 2) { echo 'mail_msg.begin_request('.__LINE__.'):  preferences->create_email_preferences called, GLOBALS[phpgw_info][user][preferences] dump:<pre>'; print_r($GLOBALS['phpgw_info']['user']['preferences']) ; echo '</pre>';}
1209			//if ($this->debug_logins > 2) { echo 'mail_msg.begin_request('.__LINE__.'):  preferences->create_email_preferences called, GLOBALS[phpgw_info][user] dump:<pre>'; print_r($GLOBALS['phpgw_info']['user']) ; echo '</pre>';}
1210			//if ($this->debug_logins > 2) { echo 'mail_msg.begin_request('.__LINE__.'): preferences->create_email_preferences called, GLOBALS[phpgw_info] dump:<pre>'; print_r($GLOBALS['phpgw_info']) ; echo '</pre>';}
1211			//if ($this->debug_logins > 2) { echo 'mail_msg.begin_request('.__LINE__.'): preferences->create_email_preferences called, GLOBALS[phpgw] dump:<pre>'; print_r($GLOBALS['phpgw']) ; echo '</pre>';}
1212
1213			// ---- CACHE THE COMPLETED PREF DATA
1214			if (($this->use_cached_prefs == True)
1215			&& (!$cached_prefs))
1216			{
1217				// for whever reason we did not get any data from the stored prefs
1218				/*
1219				// data we need to DB save to cache final processed prefs
1220				$this->unprocessed_prefs
1221				$this->raw_filters
1222				$this->ex_accounts_count
1223				$this->extra_accounts
1224				$this->extra_and_default_acounts
1225				$this->a[X]->['prefs']
1226				// where X is the account number, we can use "set_pref_array(array_data, acctnum) for each account
1227				*/
1228				// ok lets make an array to hold this data in the DB
1229				$cached_prefs = array();
1230				$cached_prefs['unprocessed_prefs'] = array();
1231				$cached_prefs['raw_filters'] = array();
1232				$cached_prefs['ex_accounts_count'] = '0';
1233				$cached_prefs['extra_accounts'] = array();
1234				$cached_prefs['extra_and_default_acounts'] = array();
1235				$cached_prefs['a'] = array();
1236				// lets fill the data
1237				$cached_prefs['unprocessed_prefs'] = $this->unprocessed_prefs;
1238				$cached_prefs['raw_filters'] = $this->raw_filters;
1239				// defang the filters
1240				// NO remember bofilters defangs, htmlquotes encodes, the filters FOR US
1241				// they are stored in the preferences DB already in defanged state
1242				// we never need to degang or UN-defang filters
1243				// because bofilters handles ALL that for us
1244				//for ($x=0; $x < count($cached_prefs['raw_filters']); $x++)
1245				//{
1246				//	$cached_prefs['raw_filters'][$x]['filtername'] = $this->db_defang_encode($cached_prefs['raw_filters'][$x]['filtername']);
1247				//	$cached_prefs['raw_filters'][$x]['source_accounts']['folder'] = $this->db_defang_encode($cached_prefs['raw_filters'][$x]['source_accounts']['folder']);
1248				//	for ($y=0; $y < count($cached_prefs['raw_filters']['matches']); $y++)
1249				//	{
1250				//		$cached_prefs['raw_filters'][$x]['matches'][$y]['matchthis']
1251				//			= $this->db_defang_encode($cached_prefs['raw_filters'][$x]['matches'][$y]['matchthis']);
1252				//	}
1253				//	for ($y=0; $y < count($cached_prefs['raw_filters']['actions']); $y++)
1254				//	{
1255				//		$cached_prefs['raw_filters'][$x]['actions'][$y]['folder']
1256				//			= $this->db_defang_encode($cached_prefs['raw_filters'][$x]['actions'][$y]['folder']);
1257				//	}
1258				//}
1259				$cached_prefs['ex_accounts_count'] = $this->ex_accounts_count;
1260				$cached_prefs['extra_accounts'] = $this->extra_accounts;
1261				$cached_prefs['extra_and_default_acounts'] = $this->extra_and_default_acounts;
1262				$cached_prefs['a'] = array();
1263				$defang_these = array();
1264				$defang_these[0] = 'passwd';
1265				$defang_these[1] = 'email_sig';
1266				$defang_these[2] = 'trash_folder_name';
1267				$defang_these[3] = 'sent_folder_name';
1268				$defang_these[4] = 'userid';
1269				$defang_these[5] = 'address';
1270				$defang_these[6] = 'mail_folder';
1271				$defang_these[7] = 'fullname';
1272				$defang_these[8] = 'account_name';
1273				$loops = count($this->extra_and_default_acounts);
1274				for ($i=0; $i < $loops; $i++)
1275				{
1276					$cached_prefs['a'][$i] = array();
1277					$cached_prefs['a'][$i]['prefs'] = array();
1278					$cached_prefs['a'][$i]['prefs'] = $this->a[$i]['prefs'];
1279					// defang
1280					for ($x=0; $x < count($defang_these); $x++)
1281					{
1282					$defang_word = $defang_these[$x];
1283						if (isset($cached_prefs['a'][$i]['prefs'][$defang_word]))
1284						{
1285							$cached_prefs['a'][$i]['prefs'][$defang_word]  = $this->db_defang_encode($cached_prefs['a'][$i]['prefs'][$defang_word]);
1286						}
1287					}
1288				}
1289				// just use account 0 for this eventhough the prefs are for every account
1290				$my_location = '0;cached_prefs';
1291				if ($this->debug_logins > 2) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): POST create_email_preferences we made the $cached_prefs for storage DUMP:', $cached_prefs); }
1292				$this->so->so_appsession_passthru($my_location, $cached_prefs);
1293			}
1294
1295			// ---- SET important class vars  ----
1296			$this->att_files_dir = $GLOBALS['phpgw_info']['server']['temp_dir'].SEP.$GLOBALS['phpgw_info']['user']['sessionid'];
1297
1298			// and.or get some vars we will use later in this function
1299			$mailsvr_callstr = $this->get_arg_value('mailsvr_callstr');
1300			if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): $mailsvr_callstr '.$mailsvr_callstr.'<br />'); }
1301
1302			// set class var "$this->cache_mailsvr_data" based on prefs info
1303			// FIXME: why have this in 2 places, just keep it in prefs (todo)
1304			// THIS IS DEPRECIATED but may be used again in the future.
1305			if ((isset($this->cache_mailsvr_data_disabled))
1306			&& ($this->cache_mailsvr_data_disabled == True))
1307			{
1308				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): OLD DEFUNCT OPTION folder cache DISABLED, $this->cache_mailsvr_data_disabled = '.serialize($this->cache_mailsvr_data_disabled).'<br />'); }
1309				$this->cache_mailsvr_data = False;
1310			}
1311			elseif (($this->get_isset_pref('cache_data'))
1312			&& ($this->get_pref_value('cache_data') != ''))
1313			{
1314				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): OLD DEFUNCT OPTION folder cache is enabled in user prefs'.'<br />'); }
1315				$this->cache_mailsvr_data = True;
1316			}
1317			else
1318			{
1319				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): OLD DEFUNCT OPTION folder cache is NOT enabled in user prefs'.'<br />'); }
1320				$this->cache_mailsvr_data = False;
1321			}
1322
1323			// ----  Should We Login  -----
1324			if (!isset($args_array['do_login']))
1325			{
1326				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): $args_array[do_login] was NOT set, so we set it to default value "FALSE"'.'<br />'); }
1327				$args_array['do_login'] = False;
1328			}
1329			// ---- newer 3 way do_login_ex value from the bootstrap class
1330			if ( (!defined(BS_LOGIN_NOT_SPECIFIED))
1331			|| (!isset($args_array['do_login_ex'])) )
1332			{
1333				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): $args_array[do_login_ex] not set, getting default from a temp local bootstrap object'.'<br />'); }
1334				// that means somewhere the bootstrap class has been run
1335				$local_bootstrap = CreateObject('email.msg_bootstrap');
1336				$local_bootstrap->set_do_login($args_array['do_login'], 'begin_request');
1337				$args_array['do_login_ex'] = $local_bootstrap->get_do_login_ex();
1338				$local_bootstrap = '';
1339				unset($local_bootstrap);
1340			}
1341			if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): $args_array[] DUMP ['.serialize($args_array).']'.'<br />'); }
1342
1343			/*
1344			// ----  Are We In Newsmode Or Not  -----
1345			// FIXME: !!! this needs better handling
1346			if ((isset($args_array['newsmode']))
1347			&& (($args_array['newsmode'] == True) || ($args_array['newsmode'] == "on")))
1348			{
1349				$args_array['newsmode'] = True;
1350				$this->set_arg_value('newsmode', True);
1351				$this->set_pref_value('mail_server_type', 'nntp');
1352			}
1353			else
1354			{
1355				$args_array['newsmode'] = False;
1356				$this->set_arg_value('newsmode', False);
1357			}
1358			*/
1359
1360			// Browser Detection =FUTURE=
1361			// 0 = NO css ; 1 = CSS supported ; 2 = text only
1362			// currently not implemented, use default 0 (NO CSS support in browser)
1363			$this->browser = 0;
1364			//$this->browser = 1;
1365
1366			// ----  Process "sort" "order" and "start" GPC args (if any) passed to the script  -----
1367			// these args are so fundamental, they get stored in their own class vars
1368			// no longer referenced as args after this
1369			// requires args saved to $this->a[$this->acctnum]['args'], only relevant if you login
1370			$this->fill_sort_order_start();
1371
1372			// ----  Things Specific To Loging In, and Actually Logging In  -----
1373			// $args_array['folder'] gets prep_folder_in and then is stored in class var $this->get_arg_value('folder')
1374
1375			// test for previous login, meaning we have all the cached data we need
1376			//if (($args_array['do_login'] == True)
1377
1378			/*!
1379			@capability do_login if False prevents even trying to login
1380			@abstract this is for preferences pages or other pages where we may
1381			not need nor want a mailserver login BUT we still want the prefs handling and
1382			other functions available in the msg class. Note that caching can eliminate
1383			the need for some logins, but that is a different issue. do_login set to
1384			False will disallow testing cache (which may itself require a login) or trying to login
1385			anyway.
1386			*/
1387
1388
1389			// test for previous login, meaning we have all the cached data we need
1390			if (($args_array['do_login_ex'] >= BS_LOGIN_ONLY_IF_NEEDED)
1391			&& ($this->session_cache_enabled == True)
1392			&& ($this->session_cache_extreme == True))
1393			{
1394				// IF we already have a cached_folder_list, we DO NOT NEED to immediately log in
1395				// if and when a login is required, calls to "ensure_stream_and_folder" will take care of that login
1396				// actually, we could even test the L1 class cashed folder_list first, that is a sure sign we have the data
1397				// note _direct_access_arg_value returns NULL (nothing) if that arg is not set
1398				$L1_cached_folder_list = $this->_direct_access_arg_value('folder_list', $acctnum);
1399				if ($this->debug_logins > 1) { $this->dbug->out('begin_request: LINE '.__LINE__.' check for $L1_cached_folder_list DUMP:', $L1_cached_folder_list); }
1400				if ((isset($L1_cached_folder_list) == False)
1401				|| (!$L1_cached_folder_list))
1402				{
1403					$appsession_cached_folder_list = $this->read_session_cache_item('folder_list', $acctnum);
1404					if ($this->debug_logins > 1) { $this->dbug->out('begin_request: LINE '.__LINE__.' check for $appsession_cached_folder_list DUMP:', $appsession_cached_folder_list); }
1405					// while we are here, if we got a folder list now put it in L1 cache so no more aueries to the DB
1406					// but only if it a new style, full folder_list including the folder_short elements
1407					if (isset($appsession_cached_folder_list[0]['folder_short']))
1408					{
1409						// cache the result in "Level 1 cache" class object var
1410						if (($this->debug_logins > 1) || ($this->debug_args_special_handlers > 1)) { $this->dbug->out('begin_request: LINE '.__LINE__.' while we are here, put folder_list into Level 1 class var "cache" so no more queries to DB for this<br />'); }
1411						$this->set_arg_value('folder_list', $appsession_cached_folder_list, $acctnum);
1412					}
1413				}
1414				else
1415				{
1416					// we have L1 data, no need to query the database
1417					$appsession_cached_folder_list = $L1_cached_folder_list;
1418				}
1419				if (($L1_cached_folder_list)
1420				|| ($appsession_cached_folder_list))
1421				{
1422					// in this case, extreme caching is in use, AND we already have cached data, so NO NEED TO LOGIN
1423					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): session extreme caching IS in use, AND we have a cached "folder_list", which means should also have all necessary cached data, so NO LOGIN NEEDED<br />'); }
1424					$decision_to_login = False;
1425
1426					// get a few more things that we would otherwise get during the login code (which we'll be skiping)
1427					$processed_folder_arg = $this->get_best_folder_arg($args_array, $got_args, $acctnum);
1428					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): session extreme caching IS in use, Login may NOT occur, so about to issue: $this->set_arg_value("folder", '.$processed_folder_arg.', '.serialize($acctnum).')<br />'); }
1429					$this->set_arg_value('folder', $processed_folder_arg, $acctnum);
1430					if ( $this->get_isset_pref('userid')
1431					&& ($this->get_pref_value('userid') != ''))
1432					{
1433						$user = $this->get_pref_value('userid');
1434						if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): session extreme caching IS in use, Login may NOT occur, so about to issue: $this->set_arg_value("mailsvr_account_username", '.$user.', '.serialize($acctnum).')<br />'); }
1435						$this->set_arg_value('mailsvr_account_username', $user, $acctnum);
1436					}
1437				}
1438				else
1439				{
1440					// in this case, extreme caching is in use, HOWEVER we do not have necessary cached data, so WE NEED A LOGIN
1441					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): session extreme caching IS in use, but we do NOT have a cached "folder_list", meaning we probably do NOT have any cached data, so we NEED A LOGIN, allow it if requested<br />'); }
1442					$decision_to_login = True;
1443				}
1444			}
1445			elseif ($args_array['do_login_ex'] == BS_LOGIN_NEVER)
1446			{
1447				// whether or not extreme caching is on, if  "BS_LOGIN_NEVER" then we DO NOT login
1448				$decision_to_login = False;
1449
1450				// get a few more things that we would otherwise get during the login code (which we'll be skiping)
1451				$processed_folder_arg = $this->get_best_folder_arg($args_array, $got_args, $acctnum);
1452				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): we are NOT allowed to log in (see code this line) but we still need to get this info, so about to issue: $this->set_arg_value("folder", '.$processed_folder_arg.', '.serialize($acctnum).')<br />'); }
1453				$this->set_arg_value('folder', $processed_folder_arg, $acctnum);
1454				if ( $this->get_isset_pref('userid')
1455				&& ($this->get_pref_value('userid') != ''))
1456				{
1457					$user = $this->get_pref_value('userid');
1458					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): we are NOT allowed to log in (see code this line) but we still need to get this info, so about to issue: $this->set_arg_value("mailsvr_account_username", '.$user.', '.serialize($acctnum).')<br />'); }
1459					$this->set_arg_value('mailsvr_account_username', $user, $acctnum);
1460				}
1461			}
1462			/*
1463			elseif ($args_array['do_login_ex'] == BS_LOGIN_ONLY_IF_NEEDED)
1464			{
1465				// if extreme is on and "BS_LOGIN_ONLY_IF_NEEDED" then that's taken care of above,
1466				// therefor
1467				// * if we are here then caching is NOT on
1468				// * we are told a login is "not 100% necessary"
1469				// in that case we'll pass thru this function without logging in
1470				// and rely on the rest of the code to open a stream if it is needed
1471				$decision_to_login = False;
1472
1473				// get a few more things that we would otherwise get during the login code (which we'll be skiping)
1474				$processed_folder_arg = $this->get_best_folder_arg($args_array, $got_args, $acctnum);
1475				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: begin_request ('.__LINE__.'): we are NOT allowed to log in (see code this line) but we still need to get this info, so about to issue: $this->set_arg_value("folder", '.$processed_folder_arg.', '.serialize($acctnum).')<br />'); }
1476				$this->set_arg_value('folder', $processed_folder_arg, $acctnum);
1477				if ( $this->get_isset_pref('userid')
1478				&& ($this->get_pref_value('userid') != ''))
1479				{
1480					$user = $this->get_pref_value('userid');
1481					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: begin_request ('.__LINE__.'): we are NOT allowed to log in (see code this line) but we still need to get this info, so about to issue: $this->set_arg_value("mailsvr_account_username", '.$user.', '.serialize($acctnum).')<br />'); }
1482					$this->set_arg_value('mailsvr_account_username', $user, $acctnum);
1483				}
1484			}
1485			*/
1486			else
1487			{
1488				// extreme caching and logins handled above in the first if .. then
1489				// if we are here, generally we are allowed to login
1490				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): session extreme caching is NOT in use, any begin_request logins ARE allowed <br />'); }
1491				$decision_to_login = True;
1492			}
1493
1494			if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): <u>maybe about to enter login sequence</u>, $args_array[]: ['.serialize($args_array).'] ; $decision_to_login ['.serialize($decision_to_login).'] <br />'); }
1495
1496			// now actually use that test result
1497			if ($decision_to_login == True)
1498			{
1499				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): entered and starting login sequence <br />'); }
1500
1501				//  ----  Get Email Password
1502				if ($this->get_isset_pref('passwd') == False)
1503				{
1504					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): this->a[$this->acctnum][prefs][passwd] NOT set, fallback to $GLOBALS[phpgw_info][user][passwd]'.'<br />'); }
1505					// DO NOT alter the password and put that altered password BACK into the preferences array
1506					// why not? used to have a reason, but that was obviated, no reason at the moment
1507					//$this->set_pref_value('passwd',$GLOBALS['phpgw_info']['user']['passwd']);
1508					//$this->a[$this->acctnum]['prefs']['passwd'] = $GLOBALS['phpgw_info']['user']['passwd'];
1509					$pass = $GLOBALS['phpgw_info']['user']['passwd'];
1510					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): pass grabbed from GLOBALS[phpgw_info][user][passwd] = '.htmlspecialchars(serialize($pass)).'<br />'); }
1511				}
1512				else
1513				{
1514					// DO NOT alter the password and do NOT put that altered password BACK into the preferences array
1515					// keep the one in GLOBALS in encrypted form if possible ????
1516					$pass = $this->get_pref_value('passwd');
1517					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): pass from prefs: already defanged for us, but still encrypted <pre>'.$pass.'</pre><br />'."\r\n"); }
1518					// IMPORTANT: (this note on "defanging" still valid as of Jan 24, 2002
1519					// the last thing you do before saving to the DB is "de-fang"
1520					// so the FIRST thing class prefs does when reading from the db MUST be to "UN-defang", and that IS what happens there
1521					// so by now phpgwapi/class.preferences has ALREADY done the "de-fanging"
1522					$pass = $this->decrypt_email_passwd($pass);
1523					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): pass from prefs: decrypted: <pre>'.$pass.'</pre><br />'."\r\n"); }
1524				}
1525				// ----  ISSET CHECK for userid and passwd to avoid garbage logins  ----
1526				if ( $this->get_isset_pref('userid')
1527				&& ($this->get_pref_value('userid') != '')
1528				&& (isset($pass))
1529				&& ($pass != '') )
1530				{
1531					$user = $this->get_pref_value('userid');
1532				}
1533				else
1534				{
1535					// FIXME make this use an official error function
1536					// problem - invalid or nonexistant info for userid and/or passwd
1537					//if ($this->debug_logins > 0) {
1538						echo 'mail_msg.begin_request('.__LINE__.'): ERROR: userid or passwd empty'."<br />\r\n"
1539							.' * * $this->get_pref_value(userid) = '
1540								.$this->get_pref_value('userid')."<br />\r\n"
1541							.' * * if the userid is filled, then it must be the password that is missing'."<br />\r\n"
1542							.' * * tell your admin if a) you have a custom email password or not when reporting this error'."<br />\r\n";
1543					//}
1544					if ($this->debug_logins > 0) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): LEAVING with ERROR: userid or passwd empty<br />'); }
1545					return False;
1546				}
1547
1548				// ----  Create email server Data Communication Class  ----
1549				// 1st arg to the constructor is the "mail_server_type"
1550				// we feed from here because when there are multiple mail_msg objects
1551				// we need to make sure we load the appropriate type dcom class
1552				// which that class may not know which accounts prefs to use, so tell it here
1553
1554				//$this->a[$this->acctnum]['dcom'] = CreateObject("email.mail_dcom",$this->get_pref_value('mail_server_type'));
1555
1556				// ----  php3 compatibility  ----
1557				// make a "new" holder object to hold the dcom object
1558				// remember, by now we have determined an acctnum
1559				$this_server_type = $this->get_pref_value('mail_server_type');
1560				// ok, now put that object into the array
1561				//$this_acctnum = $this->get_acctnum();
1562				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): creating new dcom_holder at $GLOBALS[phpgw_dcom_'.$acctnum.'] = new mail_dcom_holder'.'<br />'); }
1563				$GLOBALS['phpgw_dcom_'.$acctnum] = new mail_dcom_holder;
1564				$GLOBALS['phpgw_dcom_'.$acctnum]->dcom = CreateObject("email.mail_dcom", $this_server_type);
1565				// initialize the dcom class variables
1566				$GLOBALS['phpgw_dcom_'.$acctnum]->dcom->mail_dcom_base();
1567
1568				// ----  there are 2 settings from this mail_msg object we need to pass down to the child dcom object:  ----
1569				// (1)  Do We Use UTF7 encoding/decoding of folder names
1570				if (($this->get_isset_pref('enable_utf7'))
1571				&& ($this->get_pref_value('enable_utf7')))
1572				{
1573					$GLOBALS['phpgw_dcom_'.$acctnum]->dcom->enable_utf7 = True;
1574				}
1575				// (2)  Do We Force use of msg UID's
1576				if ($this->force_msg_uids == True)
1577				{
1578					$GLOBALS['phpgw_dcom_'.$acctnum]->dcom->force_msg_uids = True;
1579				}
1580
1581				//@set_time_limit(60);
1582				// login to INBOX because we know that always(?) should exist on an imap server and pop server
1583				// after we are logged in we can get additional info that will lead us to the desired folder (if not INBOX)
1584				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): about to call dcom->open: $GLOBALS["phpgw_dcom_".$acctnum('.$acctnum.')]->dcom->open('.$mailsvr_callstr."INBOX".', '.$user.', '.$pass.', )'.'<br />'); }
1585				if ($this->debug_logins > 0) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): <font color="red">MAIL SERVER COMMAND</font>'.'<br />'); }
1586				$mailsvr_stream = $GLOBALS['phpgw_dcom_'.$acctnum]->dcom->open($mailsvr_callstr."INBOX", $user, $pass, '');
1587				$pass = '';
1588				//@set_time_limit(0);
1589
1590				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): open returns $mailsvr_stream = ['.serialize($mailsvr_stream).']<br />'); }
1591
1592				// Logged In Success or Faliure check
1593				if ( (!isset($mailsvr_stream))
1594				|| ($mailsvr_stream == '') )
1595				{
1596					// set the "mailsvr_stream" to blank so all will know the login failed
1597					$this->set_arg_value('mailsvr_stream', '');
1598					if ($this->debug_logins > 0) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): LEAVING with ERROR: failed to open mailsvr_stream : '.$mailsvr_stream.'<br />'); }
1599					// we return false, but SHOULD WE ERROR EXIT HERE?
1600					return False;
1601				}
1602
1603				// SUCCESS - we are logged in to the server, at least we got to "INBOX"
1604				$this->set_arg_value('mailsvr_stream', $mailsvr_stream, $acctnum);
1605				$this->set_arg_value('mailsvr_account_username', $user, $acctnum);
1606				// BUT if "folder" != "INBOX" we still have to "reopen" the stream to that "folder"
1607
1608				// ----  Get additional Data now that we are logged in to the mail server  ----
1609				// namespace is often obtained by directly querying the mailsvr
1610				$mailsvr_namespace = $this->get_arg_value('mailsvr_namespace');
1611				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): $mailsvr_namespace: '.serialize($mailsvr_namespace).'<br />'); }
1612				$mailsvr_delimiter = $this->get_arg_value('mailsvr_delimiter');
1613				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): $mailsvr_delimiter: '.serialize($mailsvr_delimiter).'<br />'); }
1614
1615
1616				// FIND FOLDER VALUE
1617				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): <b> *** FIND FOLDER VALUE *** </b><br />'); }
1618				// get best available, most legit, folder value that we can find, and prep it in
1619				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): about to call: "get_best_folder_arg($args_array, $got_args, $acctnum(='.$acctnum.'))"<br />'); }
1620				$processed_folder_arg = $this->get_best_folder_arg($args_array, $got_args, $acctnum);
1621				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): "get_best_folder_arg" returns $processed_folder_arg ['.htmlspecialchars(serialize($processed_folder_arg)).']<br />'); }
1622
1623				// ---- Switch To Desired Folder If Necessary  ----
1624				if ($processed_folder_arg == 'INBOX')
1625				{
1626					// NO need to switch to another folder
1627					// put this $processed_folder_arg in arg "folder", replacing any unprocessed value that may have been there
1628					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): NO need to switch folders, about to issue: $this->set_arg_value("folder", '.$processed_folder_arg.', '.serialize($acctnum).')<br />'); }
1629					$this->set_arg_value('folder', $processed_folder_arg, $acctnum);
1630				}
1631				else
1632				{
1633					// switch to the desired folder now that we are sure we have it's official name
1634					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): need to switch folders (reopen) from INBOX to $processed_folder_arg: '.$processed_folder_arg.'<br />'); }
1635					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): about to issue: $GLOBALS[phpgw_dcom_'.$acctnum.']->dcom->reopen('.$mailsvr_stream.', '.$mailsvr_callstr.$processed_folder_arg,', )'.'<br />'); }
1636					//$did_reopen = $tmp_a['dcom']->reopen($mailsvr_stream, $mailsvr_callstr.$processed_folder_arg, '');
1637					if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: begin_request('.__LINE__.'): <font color="red">MAIL SERVER COMMAND</font>'.'<br />'); }
1638					$did_reopen = $GLOBALS['phpgw_dcom_'.$acctnum]->dcom->reopen($mailsvr_stream, $mailsvr_callstr.$processed_folder_arg, '');
1639					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): reopen returns: '.serialize($did_reopen).'<br />'); }
1640					// error check
1641					if ($did_reopen == False)
1642					{
1643						if ($this->debug_logins > 0) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): LEAVING with re-open ERROR, closing stream, FAILED to reopen (change folders) $mailsvr_stream ['.$mailsvr_stream.'] INBOX to ['.$mailsvr_callstr.$processed_folder_arg.'<br />'); }
1644						// log out since we could not reopen, something must have gone wrong
1645						$this->end_request();
1646						return False;
1647					}
1648					else
1649					{
1650						if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): Successful switch folders (reopen) from (default initial folder) INBOX to ['.$processed_folder_arg.']<br />'); }
1651						// put this $processed_folder_arg in arg "folder", since we were able to successfully switch folders
1652						if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): switched folders (via reopen), about to issue: $this->set_arg_value("folder", '.$processed_folder_arg.', $acctnum(='.$acctnum.'))<br />'); }
1653						$this->set_arg_value('folder', $processed_folder_arg, $acctnum);
1654					}
1655				}
1656
1657				// now we have folder, sort and order, make a URI for auto-refresh use
1658				// we can NOT put "start" in auto refresh or user may not see the 1st index page on refresh
1659				$this_index_refresh_uri =
1660					'menuaction=email.uiindex.index'
1661					.'&fldball[folder]='.$this->prep_folder_out()
1662					.'&fldball[acctnum]='.$this->get_acctnum()
1663					.'&sort='.$this->get_arg_value('sort')
1664					.'&order='.$this->get_arg_value('order');
1665				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): about to call $this->set_arg_value(index_refresh_uri, $this_index_refresh_uri, $acctnum(='.$acctnum.')); ; where $this_index_refresh_uri: '.htmlspecialchars($this_index_refresh_uri).'<br />'); }
1666				$this->set_arg_value('index_refresh_uri', $this_index_refresh_uri, $acctnum);
1667
1668				if ($this->debug_logins > 2) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): about to leave, direct access dump of $this->a  DUMP:', $this->a); }
1669				if ($this->debug_logins > 0) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): LEAVING, success'.'<br />'); }
1670				// returning this is vestigal, not really necessary, but do it anyway
1671				// it's importance is that it returns something other then "False" on success
1672				return $this->get_arg_value('mailsvr_stream', $acctnum);
1673			}
1674			else
1675			{
1676				// EXPERIMENTAL since we did not login can we still get a good refresh URI?
1677				// now we have folder, sort and order, make a URI for auto-refresh use
1678				// we can NOT put "start" in auto refresh or user may not see the 1st index page on refresh
1679				$this_index_refresh_uri =
1680					'menuaction=email.uiindex.index'
1681					.'&fldball[folder]='.$this->prep_folder_out()
1682					.'&fldball[acctnum]='.$this->get_acctnum()
1683					.'&sort='.$this->get_arg_value('sort')
1684					.'&order='.$this->get_arg_value('order');
1685				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg.begin_request('.__LINE__.'): about to call $this->set_arg_value(index_refresh_uri, $this_index_refresh_uri, $acctnum(='.$acctnum.')); ; where $this_index_refresh_uri: '.htmlspecialchars($this_index_refresh_uri).'<br />'); }
1686				$this->set_arg_value('index_refresh_uri', $this_index_refresh_uri, $acctnum);
1687
1688				//if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: begin_request ('.__LINE__.'): LEAVING, we were NOT allowed to, $args_array[do_login]: ['.serialize($args_array['do_login']).'] if TRUE, then we must return *something* so calling function does NOT think error, so return $args_array[do_login] <br />'); }
1689				//return $args_array['do_login'];
1690				if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: begin_request ('.__LINE__.'): LEAVING, we did NOT login, see above debug output, $args_array[do_login]: ['.serialize($args_array['do_login']).'] if TRUE, then we must return *something* so calling function does NOT think error, so return TRUE (what impact does this have??) <br />'); }
1691				return True;
1692			}
1693		}
1694
1695		/*!
1696		@function logout
1697		@abstract  simply calls this->end_request with no args, so it closes all open streams.
1698		@author Angles
1699		@discussion Simplified way to logout. Closes all open streams for all accounts. Usually
1700		closing selected streams only is an internal only thing used in special circumstances,
1701		so this function SHOULD BE CALLED AT THE END of your page view, for example,
1702		just before the last template "pfp" (or whatever output function you use). NOTE:
1703		IF THERE IS A WAY TO "HOOK" THIS so it happens AUTOMATICALLY after
1704		the last parse of the api template, that would be a "good thing"
1705		*/
1706		function logout()
1707		{
1708			if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: logout: ENTERING, about to call ->end_request with no args'.'<br />'); }
1709			$this->end_request(array());
1710		}
1711
1712		/*!
1713		@function end_request
1714		@abstract  Closes open streams.
1715		@author Angles
1716		@param $args_array OPTIONAL array of type fldball. If noy provided, all open streams are closed.
1717		@discussion Streams are left open during any particular mail operation and are not closed until this
1718		function is called. If this function is not called then the stream becomes a zombie and the mail server
1719		will close it after a certain amount of time. Mail streams before PHP 4,2 are not persistent, they last
1720		only as long as the page view or mail operation. This function should be called so the streams are
1721		properly closed with the logout command to the mail server.
1722		*/
1723		function end_request($args_array='')
1724		{
1725			if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: end_request: ENTERING'.'<br />'); }
1726			if ($this->debug_logins > 2) { $this->dbug->out('mail_msg: end_request: $args_array DUMP', $args_array); }
1727			$check_streams = array();
1728			if ((isset($args_array['acctnum']))
1729			&& ((string)$args_array['acctnum'] != ''))
1730			{
1731				// we were asked to close only this specific stream, not all possible open streams
1732				$check_streams[0]['acctnum'] = (int)$args_array['acctnum'];
1733			}
1734			else
1735			{
1736				// we were asked to close all possible open streams
1737				// put together a list of all enabled accounts so we will check them for an open stream
1738				for ($i=0; $i < count($this->extra_and_default_acounts); $i++)
1739				{
1740					if ($this->extra_and_default_acounts[$i]['status'] == 'enabled')
1741					{
1742						$next_idx = count($check_streams);
1743						$check_streams[$next_idx]['acctnum'] = $this->extra_and_default_acounts[$i]['acctnum'];
1744					}
1745				}
1746			}
1747			if ($this->debug_logins > 2) { $this->dbug->out('mail_msg: end_request: $check_streams DUMP', $check_streams); }
1748
1749			// so now we know what acctnums we need to check (at least they are enabled), loop thru them
1750			for ($i=0; $i < count($check_streams); $i++)
1751			{
1752				$this_acctnum = $check_streams[$i]['acctnum'];
1753				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: end_request: stream check, will examine $this_acctnum = $check_streams['.$i.'][acctnum] = ['.$check_streams[$i]['acctnum'].']<br />'); }
1754				if (($this->get_isset_arg('mailsvr_stream', $this_acctnum) == True)
1755				&& ((string)$this->get_arg_value('mailsvr_stream', $this_acctnum) != ''))
1756				{
1757					$mailsvr_stream = $this->get_arg_value('mailsvr_stream', $this_acctnum);
1758					if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: end_request: stream exists, for $this_acctnum ['.$this_acctnum.'] , $mailsvr_stream : ['.$mailsvr_stream.'] ; logging out'.'<br />'); }
1759					// SLEEP seems to give the server time to send its OK response, used tcpdump to confirm this
1760					//sleep(1);
1761					if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: end_request('.__LINE__.'): <font color="red">MAIL SERVER COMMAND</font>'.'<br />'); }
1762					$GLOBALS['phpgw_dcom_'.$this_acctnum]->dcom->close($mailsvr_stream);
1763					// sleep here does not have any effect
1764					//sleep(1);
1765					$this->set_arg_value('mailsvr_stream', '', $this_acctnum);
1766				}
1767			}
1768			if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: end_request: LEAVING'.'<br />'); }
1769		}
1770
1771		/*!
1772		@function ensure_stream_and_folder
1773		@abstract  make sure a stream is open and the desired folder is selected, can automatically do this for us
1774		@author Angles
1775		@param $fldball descrfibes the acctnum and folder to open, SPECIAL NOTE you *may* pass a
1776		$fldball["no_switch_away"] = True value IF the command you will issue does not require a specific opened folder, SUCH
1777		AS STATUS, which does not require that folder to be selected in order to get information about it.
1778		THIS FUNCTION UNDERSTANDS THIS SPECIAL CIRCUMSTANCE of this possible empty
1779		$fldball["folder"] value.
1780		@param $called_from (string) name of the function that you called this from, used to aid in debugging.
1781		@discussion Typically used for moving mail between seperate accounts, use this function to make sure
1782		the source or destination mail  server stream is open and the required folder is selected. If not, this
1783		function will open the connection and select the desired folder.
1784		*/
1785		function ensure_stream_and_folder($fldball='', $called_from='')
1786		{
1787			if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: ensure_stream_and_folder: ENTERING, $fldball: ['.serialize($fldball).'] ; $called_from: ['.$called_from.']<br />'); }
1788
1789			if ((isset($fldball['acctnum']))
1790			&& ((string)$fldball['acctnum'] != ''))
1791			{
1792				$acctnum = (int)$fldball['acctnum'];
1793			}
1794			else
1795			{
1796				$acctnum = $this->get_acctnum();
1797			}
1798			if ((isset($fldball['folder']))
1799			&& ((string)$fldball['folder'] != ''))
1800			{
1801				$input_folder_arg = $fldball['folder'];
1802				//$input_folder_arg = urldecode($fldball['folder']);
1803				//$input_folder_arg = $this->prep_folder_in($fldball['folder']);
1804			}
1805			else
1806			{
1807				// an empty string means folder is NOT important, such as with "listmailbox"
1808				//$input_folder_arg = '';
1809
1810				// DAMN - this thing has been moved to the "no_select_away"
1811				// therefor this *should* be INBOX if none was given
1812				$input_folder_arg = 'INBOX';
1813			}
1814
1815			// initialize this stuff
1816			$ctrl_info = array();
1817			$ctrl_info['first_open'] = '';
1818			$ctrl_info['pre_existing_folder_arg'] = '';
1819			$ctrl_info['no_switch_away'] = '';
1820			$ctrl_info['do_reopen_to_folder'] = '';
1821
1822			// fill it with what we know
1823			if (($this->get_isset_arg('folder', $acctnum))
1824			&& ($this->get_arg_value('folder', $acctnum) != ''))
1825			{
1826				$ctrl_info['pre_existing_folder_arg'] = $this->get_arg_value('folder', $acctnum);
1827				// folder arg is stored urlDEcoded, but fldball and all other folder stuff is urlENcoded until the last second
1828				$ctrl_info['pre_existing_folder_arg'] = $this->prep_folder_out($ctrl_info['pre_existing_folder_arg']);
1829			}
1830			if ((isset($fldball['no_switch_away']))
1831			&& ($fldball['no_switch_away']))
1832			{
1833				// "no_switch_away" means folder is NOT important, such as with "listmailbox"
1834				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder: there may be NO need to switch folders: setting $ctrl_info[no_switch_away] because $fldball[no_switch_away] is ['.serialize($fldball['no_switch_away']).'],  $called_from: ['.$called_from.']<br />'); }
1835				$ctrl_info['no_switch_away'] = True;
1836			}
1837
1838
1839			if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder: $acctnum: ['.serialize($acctnum).'] ; $input_folder_arg: ['.serialize($input_folder_arg).']<br />'); }
1840			// get mailsvr_callstr now, it does not require a login stream
1841			$mailsvr_callstr = $this->get_arg_value('mailsvr_callstr', $acctnum);
1842			if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder: $mailsvr_callstr: '.serialize($mailsvr_callstr).'<br />'); }
1843
1844			if (($this->get_isset_arg('mailsvr_stream', $acctnum))
1845			&& ((string)$this->get_arg_value('mailsvr_stream', $acctnum) != ''))
1846			{
1847				$ctrl_info['first_open'] = False;
1848				$mailsvr_stream = $this->get_arg_value('mailsvr_stream', $acctnum);
1849				if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: ensure_stream_and_folder: PRE-EXISTING stream, do not re-login, $mailsvr_stream ['.serialize($mailsvr_stream).'] <br />'); }
1850			}
1851			else
1852			{
1853				$ctrl_info['first_open'] = True;
1854				$mailsvr_stream = '';
1855				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder: stream for this account needs to be opened, login to $acctnum ['.$acctnum.']'.'<br />'); }
1856				if ($this->get_isset_pref('passwd', $acctnum) == False)
1857				{
1858					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder: this->a[$this->acctnum][prefs][passwd] NOT set, fallback to $GLOBALS[phpgw_info][user][passwd]'.'<br />'); }
1859					$pass = $GLOBALS['phpgw_info']['user']['passwd'];
1860					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder: pass grabbed from GLOBALS[phpgw_info][user][passwd] = '.htmlspecialchars(serialize($pass)).'<br />'); }
1861				}
1862				else
1863				{
1864					$pass = $this->get_pref_value('passwd', $acctnum);
1865					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder: pass from prefs: already "defanged" for us, but still ancrypted '.htmlspecialchars(serialize($pass)).'<br />'); }
1866					$pass = $this->decrypt_email_passwd($pass);
1867					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder: pass from prefs: decrypted: '.htmlspecialchars(serialize($pass)).'<br />'); }
1868				}
1869				if ( $this->get_isset_pref('userid', $acctnum)
1870				&& ($this->get_pref_value('userid', $acctnum) != '')
1871				&& (isset($pass))
1872				&& ($pass != '') )
1873				{
1874					$user = $this->get_pref_value('userid', $acctnum);
1875				}
1876				else
1877				{
1878						echo 'mail_msg: ensure_stream_and_folder: ERROR: userid or passwd empty'."<br />\r\n"
1879							.' * * $this->get_pref_value(userid, '.$acctnum.') = '
1880								.$this->get_pref_value('userid', $acctnum)."<br />\r\n"
1881							.' * * if the userid is filled, then it must be the password that is missing'."<br />\r\n"
1882							.' * * tell your admin if a) you have a custom email password or not when reporting this error'."<br />\r\n";
1883					if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: ensure_stream_and_folder: LEAVING with ERROR: userid or passwd empty<br />'); }
1884					return False;
1885				}
1886
1887				// ----  Create email server Data Communication Class  ----
1888				$this_server_type = $this->get_pref_value('mail_server_type', $acctnum);
1889				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder: creating new dcom_holder at $GLOBALS["phpgw_dcom_'.$acctnum.'] = new mail_dcom_holder'.'<br />'); }
1890				$GLOBALS['phpgw_dcom_'.$acctnum] = new mail_dcom_holder;
1891				$GLOBALS['phpgw_dcom_'.$acctnum]->dcom = CreateObject("email.mail_dcom", $this_server_type);
1892				$GLOBALS['phpgw_dcom_'.$acctnum]->dcom->mail_dcom_base();
1893
1894				if (($this->get_isset_pref('enable_utf7', $acctnum))
1895				&& ($this->get_pref_value('enable_utf7', $acctnum)))
1896				{
1897					$GLOBALS['phpgw_dcom_'.$acctnum]->dcom->enable_utf7 = True;
1898				}
1899				if ($this->force_msg_uids == True)
1900				{
1901					$GLOBALS['phpgw_dcom_'.$acctnum]->dcom->force_msg_uids = True;
1902				}
1903				// log in to INBOX because we know INBOX should exist on every mail server, "reopen" to desired folder (if different) later
1904				//@set_time_limit(60);
1905				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder: about to call dcom->open: $GLOBALS[phpgw_dcom_'.$acctnum.']->dcom->open('.$mailsvr_callstr."INBOX".', '.$user.', '.$pass.', )'.'<br />'); }
1906				if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: ensure_stream_and_folder('.__LINE__.'): <font color="red">MAIL SERVER COMMAND</font>'.'<br />'); }
1907				$mailsvr_stream = $GLOBALS['phpgw_dcom_'.$acctnum]->dcom->open($mailsvr_callstr."INBOX", $user, $pass, '');
1908				$pass = '';
1909				//@set_time_limit(0);
1910				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder: open returns $mailsvr_stream = ['.serialize($mailsvr_stream).']<br />'); }
1911
1912				if ( (!isset($mailsvr_stream)) || ($mailsvr_stream == '') )
1913				{
1914					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder ('.__LINE__.'): $mailsvr_stream FAILS ['.serialize($mailsvr_stream).']<br />'); }
1915					//$this->set_arg_value('mailsvr_stream', '', $acctnum);
1916					// this error function will try to call this function again to attempt RedHat bug recovery
1917					// the "ensure_stream_and_folder_already_tried_again" lets us try again before exiting
1918					// otherwise the code would never continue below to a place where recovery could be detected
1919					//$GLOBALS['phpgw']->msg->login_error($GLOBALS['PHP_SELF'].', mail_msg: ensure_mail_msg_exists(), called_from: '.$called_from);
1920					// DIRECTLY call the retry logic
1921					$mail_server_type = $this->get_pref_value('mail_server_type', $acctnum);
1922					//$this->loginerr_tryagain_buggy_cert('ensure_stream_and_folder line ('.__LINE__.'), which was called_from: '.$called_from, 'error_report_HUH?', $mail_server_type, $acctnum);
1923					// oops, that means we just skipped possible showing the right login error message
1924					$this->login_error($GLOBALS['PHP_SELF'].', mail_msg: ensure_stream_and_folder(), called_from: '.$called_from, $acctnum);
1925
1926					//if ($this->debug_logins > 0) { echo 'mail_msg: ensure_stream_and_folder: LEAVING with ERROR: failed to open mailsvr_stream : '.$mailsvr_stream.'<br />';}
1927					//return False;
1928					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder ('.__LINE__.'): code just called the "login_error" function, did we get to here?<br />'); }
1929				}
1930
1931				// if we get here either
1932				// (a) all is fine and dandy,
1933				// or (b) then the error function has called us again with the RehHat buggy server fix attempt
1934				// in case of (b) we need to test the $mailsvr_stream again
1935				// if the failure was not recoverable or if already tried, the above error function would have exited the script by now
1936				if ( (!isset($mailsvr_stream)) || ($mailsvr_stream == '') )
1937				{
1938					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder ('.__LINE__.'): 2nd test, $mailsvr_stream fails ['.serialize($mailsvr_stream).'] as expected, but the recursive call may have left behind a a sign this has been fixed ...<br />'); }
1939					// try to obtain the mailsvr_stream that the recursive call to here may have left for us
1940					$mailsvr_stream_test2 = $this->get_arg_value('mailsvr_stream', $acctnum);
1941					if ( (isset($mailsvr_stream_test2))
1942					&& ((string)$mailsvr_stream_test2 != '') )
1943					{
1944						// recursive call to this function has done the job for us
1945						if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: ensure_stream_and_folder: LEAVING, apparently a recursive call to this function fixed the RH bug for us, returning $this->get_arg_value(mailsvr_stream, '.$acctnum.') ['.$mailsvr_stream_test2.']<br />'); }
1946						// IF THE RECURSIVE FUNCION DID THE JOB, I GUESS WE JUST EXIT NOW
1947						return $mailsvr_stream_test2;
1948					}
1949					else
1950					{
1951						if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: ensure_stream_and_folder: LEAVING, 2nd test using $mailsvr_stream_test2 looked for recursive fix but not found, now calling this->login_error which will exit the script probably<br />'); }
1952						//$GLOBALS['phpgw']->msg->login_error($GLOBALS['PHP_SELF'].', mail_msg ('.__LINE__.'): ensure_mail_msg_exists(), called_by: '.$called_by);
1953						$this->login_error($GLOBALS['PHP_SELF'].', mail_msg ('.__LINE__.'): ensure_stream_and_folder(), called_by: '.$called_by);
1954						if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder ('.__LINE__.'): 2nd test, code just called the "login_error" function, did we get to here?<br />'); }
1955					}
1956				}
1957				else
1958				{
1959					// if login_error is able to recover, it will set "mailsvr_stream", we do not want to over write the recovered mailsvr_stream
1960					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder ('.__LINE__.'): $mailsvr_stream is GOOD<br />'); }
1961					$this->set_arg_value('mailsvr_stream', $mailsvr_stream, $acctnum);
1962				}
1963				$this->set_arg_value('mailsvr_account_username', $user, $acctnum);
1964				// SET FOLDER ARG NOW because we'll need to check against it below!!!
1965				// WHY: because we DID actually OPEN a stream AND we DID select the INBOX
1966				// as a practice we ALWAYS open the inbox and then LATER switch to the desired folder
1967				// unless fldball["no_select_away"] is set
1968				$this->set_arg_value('folder', 'INBOX', $acctnum);
1969				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder: ... we just opened stream for $acctnum: ['.serialize($acctnum).'] continue ...<br />'); }
1970			}
1971			if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder: we have a stream for $acctnum: ['.serialize($acctnum).'] continue ...<br />'); }
1972
1973			$current_folder_arg = '';
1974			if (($this->get_isset_arg('folder', $acctnum))
1975			&& ($this->get_arg_value('folder', $acctnum) != ''))
1976			{
1977				$current_folder_arg = $this->get_arg_value('folder', $acctnum);
1978				// folder arg is stored urlDEcoded, but fldball and all other folder stuff is urlENcoded until the last second
1979				$current_folder_arg = $this->prep_folder_out($current_folder_arg);
1980			}
1981			// ---- Switch To Desired Folder If Necessary  ----
1982			// NOTE1: get_arg_value "folder" MAY BE SET before an actual stream is established
1983			//  because we can cache stuff and only open the stream when we lack info in the cache
1984			// NOTE2: fldball["no_select_away"] tells us the calling function does not *require*
1985			//  a particular folder to be selected, HOWEVER
1986			// NOTE3:
1987			//  IF (a) if this is the FIRST REAL opening of the stream
1988			// - - AND  - -
1989			//  (b) the calling function does not care about the selected folder
1990			//  - - THEN - -
1991			// we MUST ACTUALLY SELECT (reopen) TO THE PRE-EXISTING FOLDER ARG
1992			// because that folder arg is the folder we were dealing with, we just had no need to
1993			// actually open the stream till now. Furthermore, since we are now opening it
1994			// and the calling func does not *require* us to change folders, WE MUST USE
1995			// the folder arg we had before.
1996			if (($ctrl_info['first_open'])
1997			&& ($ctrl_info['pre_existing_folder_arg'])
1998			&& ($ctrl_info['no_switch_away']))
1999			{
2000				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder('.__LINE__.'): already had a folder arg, first open of stream, calling func does not care about reopen, so we MUST open to the preexisting folder arg ['.htmlspecialchars($ctrl_info['pre_existing_folder_arg']).'], $called_from: ['.$called_from.']<br />'); }
2001				$ctrl_info['do_reopen_to_folder'] = $ctrl_info['pre_existing_folder_arg'];
2002			}
2003			elseif (($ctrl_info['no_switch_away'])
2004			&& ($ctrl_info['first_open']) == False)
2005			{
2006				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder('.__LINE__.'): stream was already open, calling func does not care about reopen, so NO NEED to switch folder, $called_from: ['.$called_from.']<br />'); }
2007			}
2008			elseif (($input_folder_arg == 'INBOX')
2009			&& ($current_folder_arg == 'INBOX' ))
2010			{
2011				// no need to do anything because
2012				// 1) "INBOX" does not need to be passed thru $this->prep_folder_in(), so we directly can test against $input_folder_arg
2013				// 2) if we're here then it's because we (a) had an existing stream opened to INBOX or (b) we just opened a stream to INBOX just above here
2014				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder('.__LINE__.'): NO need to switch folders: both $input_folder_arg and $current_folder_arg == INBOX<br />'); }
2015			}
2016			elseif ($input_folder_arg == $current_folder_arg)
2017			{
2018				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder('.__LINE__.'): NO need to switch folders: both $input_folder_arg == $current_folder_arg ['.htmlspecialchars($input_folder_arg).'] == ['.htmlspecialchars($current_folder_arg).'<br />'); }
2019			}
2020			else
2021			{
2022				// unless we missed something, we WILL SWITCH FOLDERS
2023				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder('.__LINE__.'): no "skip folder change" conditions match, so WE WILL CHANGE FOLDERS to $input_folder_arg ['.htmlspecialchars($input_folder_arg).'], $called_from: ['.$called_from.']<br />'); }
2024				$ctrl_info['do_reopen_to_folder'] = $input_folder_arg;
2025			}
2026
2027			// PROCEED ...
2028			if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder('.__LINE__.'): after logic, if $ctrl_info[do_reopen_to_folder] is filled we WILL REOPEN folder, it is ['.htmlspecialchars($ctrl_info['do_reopen_to_folder']).']<br />'); }
2029			if ($ctrl_info['do_reopen_to_folder'])
2030			{
2031				// class will get this data on its own to do the lookup in prep_folder_in anyway, so might as well get it for us here at the same time
2032				$mailsvr_namespace = $this->get_arg_value('mailsvr_namespace', $acctnum);
2033				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder('.__LINE__.'): $mailsvr_namespace: '.serialize($mailsvr_namespace).'<br />'); }
2034				$mailsvr_delimiter = $this->get_arg_value('mailsvr_delimiter', $acctnum);
2035				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder('.__LINE__.'): $mailsvr_delimiter: '.serialize($mailsvr_delimiter).'<br />'); }
2036				// do this now so we can check against it in the elseif block without having to call it several different times
2037				$preped_folder = $this->prep_folder_in($ctrl_info['do_reopen_to_folder']);
2038				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder('.__LINE__.'): $preped_folder: '.serialize($preped_folder).'<br />'); }
2039				// one last check (maybe redundant now)
2040				$preped_current_folder_arg = $this->prep_folder_in($current_folder_arg);
2041				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder('.__LINE__.'): $preped_current_folder_arg: '.serialize($preped_current_folder_arg).'<br />'); }
2042
2043				if (($current_folder_arg != '')
2044				&& ($preped_current_folder_arg == $preped_folder))
2045				{
2046					// the desired folder is already opened, note this could simply be INBOX
2047					// because we did set "folder" arg during the initial open just above
2048					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder('.__LINE__.'): NO need to switch folders: $preped_current_folder_arg ['.$processed_folder_arg.'] == $preped_folder ['.$preped_folder.']<br />'); }
2049				}
2050				else
2051				{
2052					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder('.__LINE__.'): need to switch folders (reopen) from $preped_current_folder_arg ['.$preped_current_folder_arg.'] to $preped_folder: '.$preped_folder.'<br />'); }
2053					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder('.__LINE__.'): about to issue: $GLOBALS[phpgw_dcom_'.$acctnum.']->dcom->reopen('.$mailsvr_stream.', '.$mailsvr_callstr.$preped_folder,', )'.'<br />'); }
2054					if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: ensure_stream_and_folder('.__LINE__.'): <font color="red">MAIL SERVER COMMAND</font>'.'<br />'); }
2055					$did_reopen = $GLOBALS['phpgw_dcom_'.$acctnum]->dcom->reopen($mailsvr_stream, $mailsvr_callstr.$preped_folder, '');
2056					if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder('.__LINE__.'): reopen returns: '.serialize($did_reopen).'<br />'); }
2057					if ($did_reopen == False)
2058					{
2059						if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: ensure_stream_and_folder('.__LINE__.'): LEAVING with re-open ERROR, closing stream, FAILED to reopen (change folders) $mailsvr_stream ['.$mailsvr_stream.'] $pre_opened_folder ['.$pre_opened_folder.'] to ['.$mailsvr_callstr.$processed_folder_arg.'<br />'); }
2060						$end_request_args = array();
2061						$end_request_args['acctnum'] = $acctnum;
2062						// only need to close this specific stream, leave other streams (if any) alone
2063						$this->end_request($end_request_args);
2064						return False;
2065					}
2066					else
2067					{
2068						if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder('.__LINE__.'): Successful switch folders (reopen) from (default initial folder) INBOX to ['.$preped_folder.']<br />'); }
2069						if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: ensure_stream_and_folder('.__LINE__.'): switched folders (via reopen), about to issue: $this->set_arg_value("folder", '.$preped_folder.')<br />'); }
2070						$this->set_arg_value('folder', $preped_folder, $acctnum);
2071					}
2072				}
2073			}
2074			$return_mailsvr_stream = $this->get_arg_value('mailsvr_stream', $acctnum);
2075			if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: ensure_stream_and_folder('.__LINE__.'): LEAVING, returning $this->get_arg_value(mailsvr_stream, '.$acctnum.') ['.$return_mailsvr_stream.']<br />'); }
2076			return $return_mailsvr_stream;
2077		}
2078
2079		/*!
2080		@function login_error
2081		@abstract  reports some details about a login failure, uses imap_last_error
2082		@author Angles
2083		@param $called_from (string) name of the function that you called this from, used to aid in debugging.
2084		@discussion ?
2085		*/
2086		function login_error($called_from='', $acctnum='')
2087		{
2088			if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: login_error('.__LINE__.'): ENTERING, $called_from ['.$called_from.'], $acctnum: ['.$acctnum.']<br />'); }
2089			// usually acctnum is only supplied by "ensure_stream_and_folder"
2090			// because it is there that streams other then the current account may be opened on demand
2091			if ((!isset($acctnum))
2092			|| ((string)$acctnum == ''))
2093			{
2094				$acctnum = $this->get_acctnum();
2095			}
2096
2097			if ($called_from == '')
2098			{
2099				$called_from = lang('this data not supplied.');
2100			}
2101			// NOTE THIS "imap_last_error" NEEDS TO BE WRAPPED
2102			//$imap_err = imap_last_error();
2103			// this will not work because we have no dcom object to talk to because there was an error, duhhh
2104			//$imap_err = $this->phpgw_server_last_error($acctnum);
2105			if (function_exists('imap_last_error'))
2106			{
2107				$imap_err = imap_last_error();
2108			}
2109			else
2110			{
2111				$imap_err = '';
2112			}
2113
2114			if ($imap_err == '')
2115			{
2116				$error_report = lang('No Error Returned From Server');
2117			}
2118			else
2119			{
2120				$error_report = $imap_err;
2121			}
2122			if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: login_error('.__LINE__.'): $error_report ['.$error_report.']<br />'); }
2123
2124			// ATTEMPT TO RECOVER FROM KNOWS PHP BUG even if "Certificate failure" is not obvious
2125			$always_try_recover = True;
2126
2127			if ($this->get_isset_arg('beenthere_loginerr_tryagain_buggy_cert', $acctnum))
2128			{
2129				if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: login_error('.__LINE__.'): ALREADY TRIED THIS: this arg is set: "beenthere_loginerr_tryagain_buggy_cert"<br />'); }
2130			}
2131			elseif ((stristr($imap_err,'Certificate failure'))
2132			|| ($always_try_recover == True))
2133			{
2134				$mail_server_type = $this->get_pref_value('mail_server_type', $acctnum);
2135				// onhy happens with non-ssl connections
2136				if (($mail_server_type == 'pop3')
2137				|| ($mail_server_type == 'imap'))
2138				{
2139					if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: login_error('.__LINE__.'): LEAVING, with call to: $this->loginerr_tryagain_buggy_cert('.$called_from.', '.$error_report.', '.$mail_server_type.', '.$acctnum.');<br />'); }
2140					$this->loginerr_tryagain_buggy_cert($called_from, $error_report, $mail_server_type, $acctnum);
2141					return;
2142				}
2143				// not recoverable, continue with error report
2144			}
2145			if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: login_error('.__LINE__.'): this is not an error related to RH Cert issue because server string is already (apparently) correct in that respect.<br />'); }
2146
2147			/*
2148			// this should be templated
2149			echo "<p><center><b>"
2150			  . lang("There was an error trying to connect to your mail server.<br />Please, check your username and password, or contact your admin.")."<br /> \r\n"
2151			  ."source: email class.mail_msg_base.inc.php"."<br /> \r\n"
2152			  ."called from: ".$called_from."<br /> \r\n"
2153			  ."imap_last_error: [".$error_report."]<br /> \r\n"
2154			  ."tried RH bug recovery?: [".$this->get_isset_arg('beenthere_loginerr_tryagain_buggy_cert', $acctnum)."] <br /> \r\n"
2155			  ."if there is no obvious error, then check your username and password first.<br /> \r\n"
2156			  . "</b></center></p>"
2157			  .'<p><center>'
2158			  .'<a href="'.$GLOBALS['phpgw']->link('/index.php').'">Click here to continue.</a>'
2159			  .'</center></p>'."<br /> \r\n";
2160			*/
2161			// we could just return this text
2162			$error_text_plain =
2163			  lang("There was an error trying to connect to your mail server.<br />Please, check your username and password, or contact your admin.")."\r\n"
2164			  ."source: email class.mail_msg_base.inc.php"."\r\n"
2165			  ."called from: ".$called_from."\r\n"
2166			  ."imap_last_error: [".$error_report."]\r\n"
2167			  ."tried RH bug recovery?: [".$this->get_isset_arg('beenthere_loginerr_tryagain_buggy_cert', $acctnum)."]\r\n"
2168			  .lang('if there is no obvious error, check your username and password first.')."\r\n";
2169			// or use this text in an html error report
2170			$error_text_formatted =
2171			  lang("There was an error trying to connect to your mail server.<br />Please, check your username and password, or contact your admin.")."<br /> \r\n"
2172			  ."<br /> \r\n"
2173			  ."<br /> \r\n"
2174			  ."source: email class.mail_msg_base.inc.php"."<br /> \r\n"
2175			  ."<br /> \r\n"
2176			  ."called from: ".$called_from."<br /> \r\n"
2177			  ."<br /> \r\n"
2178			  ."imap_last_error: [".$error_report."]<br /> \r\n"
2179			  ."tried RH bug recovery?: [".$this->get_isset_arg('beenthere_loginerr_tryagain_buggy_cert', $acctnum)."] <br /> \r\n"
2180			  ."<br /> \r\n"
2181			  ."<br /> \r\n"
2182			  .lang('if there is no obvious error, check your username and password first.')."<br /> \r\n";
2183			// HOW we were called determines HOW we display the error
2184			if (stristr($this->ref_SERVER['REQUEST_URI'] ,'index.php?menuaction=email'))
2185			{
2186				// we were called from within the email app itself
2187				// so show the error PAGE and then have it EXIT for us
2188				// use the error report page widget
2189				$widgets = CreateObject("email.html_widgets");
2190				$widgets->init_error_report_values();
2191				$widgets->prop_error_report_text($error_text_formatted);
2192
2193				if ((string)$acctnum == '0')
2194				{
2195					$go_somewhere_url = $GLOBALS['phpgw']->link('/index.php',array(
2196															'menuaction' => 'email.uipreferences.preferences',
2197															'show_help'  => '1'));
2198				}
2199				else
2200				{
2201					$go_somewhere_url = $GLOBALS['phpgw']->link('/index.php',array(
2202															'menuaction' => 'email.uipreferences.ex_accounts_edit',
2203															'ex_acctnum' => $acctnum,
2204															'show_help'  => '1'));
2205				}
2206				$go_somewhere_text = lang('click here to edit the settings for this email account.');
2207				$widgets->prop_go_somewhere_link($go_somewhere_url, $go_somewhere_text);
2208
2209				if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: login_error('.__LINE__.'): LEAVING, called from within the email app, so use out own error page and exit.<br />'); }
2210				// by putting anything (or TRUE) in the param of this function, it will shutdown the script for us.
2211				$widgets->display_error_report_page('do_exit');
2212				// we should not get here if the error widget exits for us
2213				//$GLOBALS['phpgw']->common->phpgw_exit();
2214			}
2215			else
2216			{
2217				// we were called by another app, maybe the home page, do not monopolize the page, but DO EXIT the script so we don't loop
2218				if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: login_error('.__LINE__.'): LEAVING, we were called by another app, the home page perhaps, so simple output the message and common EXIT (return causes a loop).<br />'); }
2219				//echo '<center><b>'.$error_text_plain.'</b></center>';
2220				echo '<center><b>'.$error_text_formatted.'</b></center>'."<br /> \r\n";
2221				$GLOBALS['phpgw']->common->phpgw_exit();
2222			}
2223			// we should not get here
2224			$GLOBALS['phpgw']->common->phpgw_exit(False);
2225		}
2226
2227		/*!
2228		@function loginerr_tryagain_buggy_cert
2229		@abstract try to recover from a known php bug and reattempt login
2230		@param $called_from
2231		@param $error_report
2232		@param $mail_server_type
2233		@param $acctnum
2234		@author Angles
2235		@discussion as of RedHat 7.3 there us a bug in php and UWash requiring unusual mailscr_callstr
2236		containing "novalidate-cert" even for NON-SSL connections. If possible, this function adjusts the
2237		mailsvr_callstr and continues execution of the script. As long as we "return" from this function,
2238		instead of exiting, we can continue the script from where the error occured, assuming we have
2239		fixed the error. This is a cool thing, the option to fix and continue just by using "return", or to
2240		exit with "phpgw_exit", which ends execution of the script.
2241		*/
2242		function loginerr_tryagain_buggy_cert($called_from='', $error_report='', $mail_server_type='', $acctnum='')
2243		{
2244			if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: loginerr_tryagain_buggy_cert('.__LINE__.'): ENTERING<br />'); }
2245			if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: loginerr_tryagain_buggy_cert('.__LINE__.'): $called_from ['.$called_from.'], $error_report: ['.$error_report.'], $mail_server_type: ['.$mail_server_type.'], $acctnum: ['.$acctnum.']<br />'); }
2246			if ((!isset($acctnum))
2247			|| ((string)$acctnum == ''))
2248			{
2249				$acctnum = $this->get_acctnum();
2250			}
2251			// avoid infinite RECURSION by setting this flag, says we've alreasy been here
2252			if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: loginerr_tryagain_buggy_cert('.__LINE__.'): setting flag "beenthere_loginerr_tryagain_buggy_cert" to "beenthere" so we know we have been here.<br />'); }
2253			$this->set_arg_value('beenthere_loginerr_tryagain_buggy_cert', 'beenthere', $acctnum);
2254			/*!
2255			@capability "show_recover_msg" during loginerr_tryagain_buggy_cert
2256			@abstract whether ot not to show the user a "show_recover_msg"
2257			@discussion set this "show_recover_msg" var to True to show the user an error
2258			message during the recovery from the initial error a RH73+ php imap module can cause.
2259			Or set "show_recover_msg" to False to not show such a message. NOTE the error generated
2260			by php itself is controlled by your ini file settings, show_errors and log_errors.
2261			*/
2262			//$show_recover_msg = True;
2263			$show_recover_msg = False;
2264			if ($show_recover_msg == True)
2265			{
2266				// this should be templated
2267				//echo "<br /><center><b>"
2268				//  .'You have encountered a <u>KNOWN PHP - UWASH bug</u>, called the "<u>non-ssl no validate cert bug</u>"'
2269				//  .' attempting to recover ...'.'<br />'."\r\n"
2270				//  . "</b></center><br />";
2271				echo '<small><i>'
2272						.'Please ignore this message, you will only see this once on login'
2273					.'</i></small><br />'."\r\n";
2274			}
2275
2276			// MAKE A NEW MAILSVR_CALLSTR with the "novalidate-cert"
2277			// UPDATE: using "notls" because user did not specifically request encryption
2278			$old_mailsvr_callstr = $this->get_mailsvr_callstr($acctnum);
2279			// NOTE: now that we set flag "beenthere_loginerr_tryagain_buggy_cert" we NEVER GET HERE a 2nd time any more
2280			// SO this text below will NEVER get a chance to be shown to the user.
2281			if (($mail_server_type != 'pop3')
2282			&& ($mail_server_type != 'imap'))
2283			{
2284				echo "<p><center><b>"
2285				  .'detected that this is a different situation, unable to recover'.'<br />'."\r\n"
2286				  .'exiting...'
2287				  . "</b></center></p>";
2288				if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: loginerr_tryagain_buggy_cert('.__LINE__.'): LEAVING, calling $this->login_error because it will show the error msg to the user.<br />'); }
2289				//$GLOBALS['phpgw']->common->phpgw_exit(False);
2290				$this->login_error('mail_msg: loginerr_tryagain_buggy_cert(LINE '.__LINE__.'), called_from: '.$called_from, $acctnum);
2291			}
2292			//elseif(stristr($old_mailsvr_callstr,'novalidate-cert'))
2293			elseif(stristr($old_mailsvr_callstr,'notls'))
2294			{
2295				echo "<p><center><b>"
2296				  .'detected that there has already been an attempting to recover that failed'.'<br />'."\r\n"
2297				  .'exiting...'
2298				  . "</b></center></p>";
2299				if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: loginerr_tryagain_buggy_cert('.__LINE__.'): LEAVING, calling $this->login_error because it will show the error msg to the user.<br />'); }
2300				//$GLOBALS['phpgw']->common->phpgw_exit(False);
2301				$this->login_error('mail_msg: loginerr_tryagain_buggy_cert(LINE '.__LINE__.'), called_from: '.$called_from, $acctnum);
2302			}
2303			else
2304			{
2305				// MAKE A NEW MAILSVR_CALLSTR with the "novalidate-cert"
2306				// later changed to "notls" because non-ssl sessions are not encrypted unless asked for.
2307				$mail_port = $this->get_pref_value('mail_port', $acctnum);
2308				if ($mail_server_type == 'pop3')
2309				{
2310					//$new_mailsvr_callstr = str_replace($mail_port.'/pop3}',$mail_port.'/pop3/novalidate-cert}',$old_mailsvr_callstr);
2311					$new_mailsvr_callstr = str_replace($mail_port.'/pop3}',$mail_port.'/pop3/notls}',$old_mailsvr_callstr);
2312				}
2313				else
2314				{
2315					//$new_mailsvr_callstr = str_replace($mail_port.'}',$mail_port.'/imap/novalidate-cert}',$old_mailsvr_callstr);
2316					$new_mailsvr_callstr = str_replace($mail_port.'}',$mail_port.'/imap/notls}',$old_mailsvr_callstr);
2317				}
2318				// cache the result in L1 cache
2319				// we are not certain yet this will work, we need to set this in L1 cache so that "" will use this $new_mailsvr_callstr instead of the old one
2320				// if "ensure_stream_and_folder" returns NON-False then we can cache the new_mailsvr_callstr to the appsession cache
2321				$this->set_arg_value('mailsvr_callstr', $new_mailsvr_callstr, $acctnum);
2322				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: loginerr_tryagain_buggy_cert('.__LINE__.'): set "level 1 cache, class var" arg $this->set_arg_value(mailsvr_callstr, '.htmlspecialchars($new_mailsvr_callstr).', '.$acctnum.']) <br />'); }
2323
2324				//echo "<p><center><b>"
2325				//  .'ADJUSTED mailsvr_callstr: '.$new_mailsvr_callstr.'<br />'."\r\n"
2326				//  .'now attempting recovery...'
2327				//  . "</b></center></p>";
2328
2329				// attempt recovery
2330				$fldball = array();
2331				if ($this->get_isset_arg('fldball', $acctnum))
2332				{
2333					$fldball = $this->get_arg_value('fldball', $acctnum);
2334				}
2335				elseif ($this->get_isset_arg('msgball', $acctnum))
2336				{
2337					$fldball = $this->get_arg_value('msgball', $acctnum);
2338				}
2339				else
2340				{
2341					// during the recursive attempt fcalled directly rom "ensure_stream_and_folder"
2342					// which we will call again (recursively) below,  fldball may STILL have nothing
2343					$fldball['acctnum'] = $acctnum;
2344					if ($this->get_isset_arg('folder', $acctnum))
2345					{
2346						$fldball['folder'] = $this->get_arg_value('folder', $acctnum);
2347					}
2348					else
2349					{
2350						$fldball['folder'] = 'INBOX';
2351					}
2352				}
2353
2354				//$fldball['folder'] = $this->get_arg_value('folder', $acctnum);
2355				//$fldball['acctnum'] = $acctnum;
2356				//  function ensure_stream_and_folder($fldball='', $called_from='')
2357				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: loginerr_tryagain_buggy_cert('.__LINE__.'): try RECOVERY ATTEMPT: calling $this->ensure_stream_and_folder('.htmlspecialchars(serialize($fldball)).']; NOTE that under certain circumstances we just in fact EXITED this function here, because we do not alsays return.<br />'); }
2358				$did_recover = $this->ensure_stream_and_folder($fldball, 'from mail_msg_base: loginerr_tryagain_buggy_cert FOR '.$called_from);
2359				if ($this->debug_logins > 1) { $this->dbug->out('mail_msg: loginerr_tryagain_buggy_cert('.__LINE__.'): just returned from call to $this->ensure_stream_and_folder, $did_recover ['.serialize($did_recover).']<br />'); }
2360				if ((is_bool($did_recover)) && ($did_recover == False))
2361				{
2362					echo 'mail_msg: loginerr_tryagain_buggy_cert: UNABLE to recover from this bug, exiting ...';
2363					if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: loginerr_tryagain_buggy_cert('.__LINE__.'): LEAVING<br />'); }
2364					$GLOBALS['phpgw']->common->phpgw_exit(False);
2365				}
2366				else
2367				{
2368					// we only put it in appsession cache AFTER "ensure_stream_and_folder" returns NON-False
2369					// -----------
2370					// SAVE DATA TO APPSESSION CACHE
2371					// -----------
2372					// save "mailsvr_callstr" to appsession data store
2373					if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: loginerr_tryagain_buggy_cert: set appsession cache $this->save_session_cache_item(mailsvr_callstr, '.$new_mailsvr_callstr.', '.$acctnum.']) <br />'); }
2374					$this->save_session_cache_item('mailsvr_callstr', $new_mailsvr_callstr, $acctnum);
2375					// back to index page
2376					// NOTE: by NOT calling "phpgw_exit" we can simply fix the problem and continue...
2377					if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: loginerr_tryagain_buggy_cert('.__LINE__.'): LEAVING quietly, we succeeded in fixing the RH Cert problem, by NOT calling "phpgw_exit" we can simply fix the problem and continue.<br />'); }
2378					return;
2379				}
2380			}
2381			echo 'mail_msg_base: loginerr_tryagain_buggy_cert: unhandled if .. then situation, returning to script';
2382			if ($this->debug_logins > 0) { $this->dbug->out('mail_msg: loginerr_tryagain_buggy_cert('.__LINE__.'): LEAVING, we should not get here, UNHANDLED if .. then scenario.<br />'); }
2383			return;
2384
2385		}
2386
2387		// ----  Various Functions Used To Support Email   -----
2388		/*!
2389		@function prep_folder_in
2390		@abstract  make sure the folder name has the correct namespace and delimiter, if not supplied they will be added.
2391		@author Angles
2392		@param $feed_folder (string) the folder name to work on.
2393		@param $acctnum (int) which account the folder belongs to, defaults to 0, the default account number.
2394		@result Folder long name as obtained from the folder lookup which uses server supplied folder names.
2395		@discussion Mail servers ecpect the foldername to be in a particluar form. This function makes sure of this.
2396		If a foldername without a namespace is provided, this function will preform a lookup of all available folders
2397		for the given acount and get the closest match. The lookup may be nevessary because the namespace and
2398		delimiter can differ from server to server, although most typically the name space is "INBOX" and the
2399		delimiter is a period. NOTE this DOES A LOOKUP and returns what was found there that
2400		reasonable matches param $feed_folder, the return is in FOLDER LONG form as directly supplied
2401		by the server itself to the lookup list.
2402		*/
2403		function prep_folder_in($feed_folder, $acctnum='')
2404		{
2405			if ($this->debug_args_input_flow > 0) { $this->dbug->out('mail_msg: prep_folder_in: ENTERING , feed $feed_folder: ['.htmlspecialchars($feed_folder).'], feed (optional) $acctnum: ['.$acctnum.']<br />'); }
2406			// ----  Ensure a Folder Variable exists, if not, set to INBOX (typical practice)   -----
2407			if (!$feed_folder)
2408			{
2409				return 'INBOX';
2410				// note: return auto-exits this function
2411			}
2412
2413			if ((!isset($acctnum))
2414			|| ((string)$acctnum == ''))
2415			{
2416				$acctnum = $this->get_acctnum();
2417			}
2418
2419			// FILESYSTEM imap server "dot_slash" or "bare slash" CHECK
2420			if ( ((strstr(urldecode($feed_folder), './'))
2421			|| (trim(urldecode($feed_folder)) == '/' ) )
2422			&& 	((($this->get_pref_value('imap_server_type', $acctnum) == 'UW-Maildir')
2423				|| ($this->get_pref_value('imap_server_type', $acctnum) == 'UWash'))) )
2424			{
2425				// UWash and UW-Maildir IMAP servers are filesystem based,
2426				// so anything like "./" or "../" *might* make the server list files and directories
2427				// somewhere in the parent directory of the users mail directory
2428				// this could be undesirable a
2429				// (a) IMAP servers really should not do this unless specifically enabled and/or told to do so, and
2430				// (b) many would consider this a security risk to display filesystem data outside the users directory
2431				// same with a bare slash "/" as an erronious folder name might display undesirable information.
2432				return 'INBOX';
2433				// note: return auto-exits this function
2434			}
2435
2436			// an incoming folder name has generally been urlencoded before it gets here
2437			// particularly if the folder has spaces and is included in the URI, then a + will be where the speces are
2438			$feed_folder = urldecode($feed_folder);
2439			$prepped_folder_in = $this->folder_lookup('', $feed_folder, $acctnum);
2440			if ($this->debug_args_input_flow > 0) { $this->dbug->out('mail_msg: prep_folder_in: LEAVING , returning $prepped_folder_in: ['.$prepped_folder_in.'] again with htmlspecialchars(): ['.htmlspecialchars($prepped_folder_in).']<br />'); }
2441			return $prepped_folder_in;
2442		}
2443
2444		/*!
2445		@function prep_folder_out
2446		@abstract  Used to prepare folder names for use in a GPC request, currently just urlencodes the foldername.
2447		@author Angles
2448		@param $feed_folder (string) OPTIONAL the folder name to prepare.
2449		@access Private
2450		@discussion Folder if not passed will be obtained from the class which keeps track od the folder currently selected,
2451		this allows us to call this with no args and the current folder is "prep-ed". Foldnames with spaces and other
2452		URL unfriendly chars are encoded here must be decoded on the next input (script session) to undo what we do here.
2453		*/
2454		function prep_folder_out($feed_folder='')
2455		{
2456			if ($feed_folder == '')
2457			{
2458				// this allows us to call this with no args and the current folder is "prep'ed"
2459				// foldnames with spaces and other URL unfriendly chars are encoded here
2460				// must be decoded on the next input (script session) to undo what we do here
2461				$feed_folder = $this->get_arg_value('folder');
2462			}
2463			//echo 'prep_folder_out: param $feed_folder ['.$feed_folder.'], :: ';
2464			$preped_folder = $this->ensure_one_urlencoding($feed_folder);
2465			$preped_folder = str_replace('&', '%26', $preped_folder);
2466			//echo ' $preped_folder ['.$preped_folder.']<br />';
2467			return $preped_folder;
2468		}
2469
2470		/*!
2471		@function ensure_no_brackets
2472		@abstract  used for removing the bracketed server call string from a full IMAP folder name string.
2473		@author Angles
2474		@param $feed_str (string) the folder name to work on.
2475		@access Private
2476		@syntax ** note this has chars that will not show up in the inline doc parser **
2477		ensure_no_brackets('{mail.yourserver.com:143}INBOX') = 'INBOX'
2478		@example ** same as syntax but can be viewed in the inline doc parser **
2479		ensure_no_brackets(&#039;&#123;mail.yourserver.com&#058;143&#125;INBOX&#039;) results in &#039;INBOX&#039;
2480		*/
2481		function ensure_no_brackets($feed_str='')
2482		{
2483			if ((strstr($feed_str,'{') == False) && (strstr($feed_str,'}') == False))
2484			{
2485				// no brackets to remove
2486				$no_brackets = $feed_str;
2487			}
2488			else
2489			{
2490				// get everything to the right of the bracket "}", INCLUDES the bracket itself
2491				$no_brackets = strstr($feed_str,'}');
2492				// get rid of that 'needle' "}"
2493				$no_brackets = substr($no_brackets, 1);
2494			}
2495			return $no_brackets;
2496		}
2497
2498		/*!
2499		@function  get_mailsvr_callstr
2500		@abstract will generate the appropriate string to access a mail server of type pop3, pop3s, imap, imaps
2501		@result the returned string is the server call string from beginning bracker "{" to ending bracket "}"
2502		the returned string is the server call string from beginning bracker "&#123;" to ending bracket "&#125;"
2503		@discussion After updating to RH73, a new bug popped up where PHP was checking the validity
2504		of the mail server certificate even for NON-SSL sessions, under certain circumstances. This has been
2505		handles in the login_error routine where this particular error is detected and fixes by adding
2506		"novalidate-cert" to the non-ssl imap or pop mailsvr_callstr, note this breaks good servers, so it's
2507		handled only after an error pops up and is determined to be a result of this bug.
2508		CACHE NOTE: this item is saved in the appsession cache, AND is bese64_encoded there,
2509		which encoding and decoding is handled in "save_session_cache_item" and
2510		"read_session_cache_item", respectively, where this dataname "mailsvr_namespace" has
2511		a special handler for this purpose.
2512		because this data has "database unfriendly" chars in it.
2513		@syntax  {mail.yourserver.com:143}
2514		@example &#123;mail.yourserver.com:143&#125;
2515		@access PRIVATE - public access is object->get_arg_value("mailsvr_namespace")
2516		*/
2517		function get_mailsvr_callstr($acctnum='')
2518		{
2519			if (stristr($this->skip_args_special_handlers, 'get_mailsvr_callstr'))
2520			{
2521				$fake_return = '{brick.earthlink.net:143}';
2522				if ($this->debug_args_special_handlers > 0) { $this->dbug->out('mail_msg: get_mailsvr_callstr: debug SKIP, $fake_return: '.serialize($fake_return).' <br />'); }
2523				return $fake_return;
2524			}
2525
2526			if ($this->debug_args_special_handlers > 0) { $this->dbug->out('mail_msg: get_mailsvr_callstr: ENTERING , feed $acctnum: ['.$acctnum.']<br />'); }
2527
2528			if ((!isset($acctnum))
2529			|| ((string)$acctnum == ''))
2530			{
2531				$acctnum = $this->get_acctnum();
2532			}
2533			if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_mailsvr_callstr: after testing feed arg, using $acctnum: ['.$acctnum.']<br />'); }
2534
2535			// do we have "level one cache" class var data that we can use?
2536			$class_cached_mailsvr_callstr = $this->_direct_access_arg_value('mailsvr_callstr', $acctnum);
2537			if ($class_cached_mailsvr_callstr != '')
2538			{
2539				// return the "level one cache" class var data
2540				if ($this->debug_args_special_handlers > 0) { $this->dbug->out('mail_msg: get_mailsvr_callstr: LEAVING, returned class var cached data: '.serialize($class_cached_mailsvr_callstr).'<br />'); }
2541				return $class_cached_mailsvr_callstr;
2542			}
2543
2544			// -----------
2545			// TRY CACHED DATA FROM APPSESSION
2546			// -----------
2547			// try to restore "mailsvr_callstr" from saved appsession data store
2548			$appsession_cached_mailsvr_callstr = $this->read_session_cache_item('mailsvr_callstr', $acctnum);
2549			if ($this->debug_args_special_handlers > 2) { $this->dbug->out('mail_msg: get_mailsvr_callstr: $appsession_cached_mailsvr_callstr is  ['.serialize($appsession_cached_mailsvr_callstr).']<br />'); }
2550			if ($appsession_cached_mailsvr_callstr)
2551			{
2552				// cache the result in "level one cache" class var holder
2553				if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_mailsvr_callstr: recovered "mailsvr_callstr" data from appsession <br />'); }
2554				if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_mailsvr_callstr: put appsession retored data into "level 1 cache, class var" arg $this->set_arg_value(mailsvr_namespace, '.$appsession_cached_mailsvr_callstr['mailsvr_callstr'].', '.$acctnum.']) <br />'); }
2555				$this->set_arg_value('mailsvr_callstr', $appsession_cached_mailsvr_callstr, $acctnum);
2556
2557				if ($this->debug_args_special_handlers > 0) { $this->dbug->out('mail_msg: get_mailsvr_callstr: LEAVING, returned appsession cached data: '.serialize($appsession_cached_mailsvr_callstr['mailsvr_callstr']).'<br />'); }
2558				return $appsession_cached_mailsvr_callstr;
2559			}
2560
2561			// no cached data of any kind we can use ...
2562
2563			// what's the name or IP of the mail server
2564			$mail_server = $this->get_pref_value('mail_server', $acctnum);
2565
2566			// what's the port we should use
2567			// mail port is not yet *really* a user settable pref, SO WE WILL PUT LOGIC HERE TO FIGURE OUT WHAT IT SHOULD BE
2568			//$pref_mail_port = $this->get_pref_value('mail_port', $acctnum)
2569			/*!
2570			@concept pref_mail_port
2571			@discussion The preferences for email were designed to *someday* let the user specify a
2572			non standard port to use for the mail server. This preference never came to be. The user
2573			still can not actually set a port number to use for the mail server. However, in the preferences
2574			api there is still code initializing a preference item called "mail_port", with a note that some day it
2575			would be user settable, but since it is not currently, the api preferences class has a function to
2576			determine the port number based on what kind of server we are connecting to. THIS DOES NOT
2577			REALLY BELONG IN PREFS, it never because a user preference, so the api preferences should
2578			not be concerned with mail_port. THEREFOR we will SET IT HERE. For backwards compatibility
2579			we still set a pref value for mail_port after we are done here, until we replace any code that asks
2580			prefs for a port number. Then, port number should be exclusively treated as an "arg" accessable through
2581			the OOP style ->get_arg_value('mail_port')  function. We do not have a seperate functin for this,
2582			we do it here, because the "mail_port" and the "mail_server_type" and the "mailsvr_callstr" are
2583			inextricibly linked, they exist only as a related group if data. CONCLUSION: we determine
2584			"mail_port" in this function "get_mailsvr_callstr" which is a private function anyway.
2585			*/
2586
2587			// determine the Mail Server Call String AND PORT
2588			// construct the email server call string from the opening bracket "{"  to the closing bracket  "}"
2589			$mail_server_type = $this->get_pref_value('mail_server_type', $acctnum);
2590			if ($mail_server_type == 'imaps')
2591			{
2592				// IMAP over SSL
2593				$callstr_extra = '/imap/ssl/novalidate-cert';
2594				$mail_port = 993;
2595			}
2596			elseif ($mail_server_type == 'pop3s')
2597			{
2598				// POP3 over SSL
2599				$callstr_extra = '/pop3/ssl/novalidate-cert';
2600				$mail_port = 995;
2601			}
2602			elseif ($mail_server_type == 'pop3')
2603			{
2604				// POP3 normal connection, No SSL
2605				$callstr_extra = '/pop3/notls';
2606				$mail_port = 110;
2607			}
2608			elseif ($mail_server_type == 'imap')
2609			{
2610				// IMAP normal connection, No SSL
2611				$callstr_extra = '/imap/notls';
2612				$mail_port = 143;
2613			}
2614			elseif ($mail_server_type == 'nntp')
2615			{
2616				// NOT CURRENTLY USED
2617				// NNTP news server port
2618				$callstr_extra = '/nntp';
2619				$port_number = 119;
2620			}
2621			else
2622			{
2623				// UNKNOW SERVER type
2624				// assume IMAP normal connection, No SSL
2625				// return a default value that is likely to work
2626				// probably should raise some kind of error here
2627				$callstr_extra = '';
2628				$mail_port = 143;
2629			}
2630
2631			$mail_port = (string)$mail_port;
2632			$mailsvr_callstr = '{'.$mail_server.':'.$mail_port.$callstr_extra .'}';
2633
2634			// cache the result in "level one cache" class var holder
2635			if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_mailsvr_callstr: set "level 1 cache, class var" arg $this->set_arg_value(mailsvr_callstr, '.htmlspecialchars($mailsvr_callstr).', '.$acctnum.']) <br />'); }
2636			$this->set_arg_value('mailsvr_callstr', $mailsvr_callstr, $acctnum);
2637
2638			// -----------
2639			// SAVE DATA TO APPSESSION CACHE
2640			// -----------
2641			// save "mailsvr_callstr" to appsession data store
2642			if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_mailsvr_callstr: set appsession cache (will base64_encode) $this->save_session_cache_item(mailsvr_callstr, '.$mailsvr_callstr.', '.$acctnum.']) <br />'); }
2643			$this->save_session_cache_item('mailsvr_callstr', $mailsvr_callstr, $acctnum);
2644
2645			if ($this->debug_args_special_handlers > 0) { $this->dbug->out('mail_msg: get_mailsvr_callstr: LEAVING, returning $mailsvr_callstr: '.serialize($mailsvr_callstr).' for $acctnum ['.$acctnum.']<br />'); }
2646			return $mailsvr_callstr;
2647		}
2648
2649		/*!
2650		@function  get_mailsvr_namespace
2651		@abstract will generate the appropriate namespace (aka filter) string to access an imap mail server
2652		@param $acctnum of the server in question.
2653		@syntax {mail.servyou.com:143}INBOX    where INBOX is returned as the namespace
2654		@example get_mailsvr_namespace(&#123;mail.servyou.com:143&#125;INBOX) returns INBOX
2655		@discussion for more info see: see http://www.rfc-editor.org/rfc/rfc2342.txt
2656		CACHE NOTE: this item is saved in the appsession cache.
2657		@access PRIVATE - public access is object->get_arg_value("mailsvr_namespace")
2658		*/
2659		function get_mailsvr_namespace($acctnum='')
2660		{
2661			if (stristr($this->skip_args_special_handlers, 'get_mailsvr_namespace'))
2662			{
2663				$fake_return = '';
2664				if ($this->debug_args_special_handlers > 0) { $this->dbug->out('mail_msg: get_mailsvr_namespace: debug SKIP, $fake_return: '.serialize($fake_return).' <br />'); }
2665				return $fake_return;
2666			}
2667
2668			if ($this->debug_args_special_handlers > 0) { $this->dbug->out('mail_msg: get_mailsvr_namespace: ENTERING , feed $acctnum: ['.$acctnum.']<br />'); }
2669
2670			if ((!isset($acctnum))
2671			|| ((string)$acctnum == ''))
2672			{
2673				$acctnum = $this->get_acctnum();
2674			}
2675			if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_mailsvr_namespace: after testing feed arg, using $acctnum: ['.$acctnum.']<br />'); }
2676
2677			// UWash patched for Maildir style: $Maildir.Junque ?????
2678			// Cyrus and Courier style =" INBOX"
2679			// UWash style: "mail"
2680
2681			// do we have cached data that we can use?
2682			$class_cached_mailsvr_namespace = $this->_direct_access_arg_value('mailsvr_namespace', $acctnum);
2683			if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_mailsvr_namespace: check for L1 class var cached data: $this->_direct_access_arg_value(mailsvr_namespace, '.$acctnum.'); returns: '.serialize($class_cached_mailsvr_namespace).'<br />'); }
2684			if ($class_cached_mailsvr_namespace != '')
2685			{
2686				// return the cached data
2687				if ($this->debug_args_special_handlers > 0) { $this->dbug->out('mail_msg: get_mailsvr_namespace: LEAVING, returned class var cached data: '.serialize($class_cached_mailsvr_namespace).'<br />'); }
2688				return $class_cached_mailsvr_namespace;
2689			}
2690
2691			// -----------
2692			// TRY CACHED DATA FROM APPSESSION
2693			// -----------
2694			// try to restore "mailsvr_namespace" from saved appsession data store
2695			$appsession_cached_mailsvr_namespace = $this->read_session_cache_item('mailsvr_namespace', $acctnum);
2696			if ($appsession_cached_mailsvr_namespace)
2697			{
2698				// cache the result in "level one cache" class var holder
2699				if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_mailsvr_namespace: put appsession retored data into "level 1 cache, class var" arg $this->set_arg_value(mailsvr_namespace, '.$appsession_cached_mailsvr_namespace['mailsvr_namespace'].', '.$acctnum.']) <br />'); }
2700				$this->set_arg_value('mailsvr_namespace', $appsession_cached_mailsvr_namespace, $acctnum);
2701
2702				if ($this->debug_args_special_handlers > 0) { $this->dbug->out('mail_msg: get_mailsvr_namespace: LEAVING, returned appsession cached data: '.serialize($appsession_cached_mailsvr_namespace['mailsvr_namespace']).'<br />'); }
2703				return $appsession_cached_mailsvr_namespace;
2704			}
2705
2706
2707			// no cached data of any kind we can use ...
2708
2709			// we *may* need this data later
2710			$mailsvr_stream = $this->get_arg_value('mailsvr_stream', $acctnum);
2711			$mailsvr_callstr = $this->get_arg_value('mailsvr_callstr', $acctnum);
2712			if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_mailsvr_namespace: got these for later use: $mailsvr_stream: ['.$mailsvr_stream.'] ; $mailsvr_callstr: ['.$mailsvr_callstr.']<br />'); }
2713
2714			if (($this->get_pref_value('imap_server_type', $acctnum) == 'UW-Maildir')
2715			|| ($this->get_pref_value('imap_server_type', $acctnum) == 'UWash'))
2716			{
2717				if (($this->get_isset_pref('mail_folder', $acctnum))
2718				&& (trim($this->get_pref_value('mail_folder', $acctnum)) != ''))
2719				{
2720					// if the user fills this option correctly, this should yield an unqualified foldername which
2721					// UWash should qualify (juat like any unix command line "cd" command) with the
2722					// appropriate $HOME variable (I THINK) ...
2723					// DO I NEED to add the "~" here too?
2724					$name_space = trim($this->get_pref_value('mail_folder', $acctnum));
2725					if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_mailsvr_namespace: user supplied UWash namespace is $name_space ['.$name_space.'] ; needs testing!<br />'); }
2726					$test_result = $this->uwash_string_ok($name_space);
2727					if (!$test_result)
2728					{
2729						if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_mailsvr_namespace: user supplied UWash namespace returns $test_result ['.serialize($test_result).'] FAILS OK TEST, use "~" instead<br />'); }
2730						$name_space = '~';
2731					}
2732					else
2733					{
2734						if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_mailsvr_namespace: user supplied UWash namespace returns $test_result ['.serialize($test_result).'] passed OK test, we use that retuen<br />'); }
2735						$name_space = $test_result;
2736					}
2737				}
2738				else
2739				{
2740					// in this case, the namespace is blank, indicating the user's $HOME is where the MBOX files are
2741					// or in the case of UW-Maildir, where the maildir files are
2742					// thus we can not have <blank><slash> preceeding a folder name
2743					// note that we *may* have <tilde><slash> preceeding a folder name, SO:
2744					// default value for this UWash server, $HOME = tilde (~)
2745					$name_space = '~';
2746				}
2747			}
2748			/*
2749			elseif ($this->get_pref_value('imap_server_type') == 'Cyrus')
2750			// ALSO works for Courier IMAP
2751			{
2752				$name_space = 'INBOX';
2753				if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_mailsvr_namespace: Assume,GUESSING: $name_space = INBOX <br />'); }
2754			}
2755			// TEMP DO NOT USE THIS, MAY BE MORE TROUBLE THAN IT'S WORTH
2756			// JUST ASSUME INBOX, the below code is "by the book" but may be causeing problems with window based installs
2757			// ------- Dynamically Discover User's Private Namespace ---------
2758			// existing "$this->get_arg_value('mailsvr_stream')" means we are logged in and can querey the server
2759			elseif ((isset($mailsvr_stream) == True)
2760			&& ($mailsvr_stream != ''))
2761			{
2762				// a LIST querey with "%" returns the namespace of the current reference
2763				// in format {SERVER_NAME:PORT}NAMESPACE
2764				// also, it MAY (needs testing) return all available namespaces
2765				// however this is less useful if the IMAP server makes available shared folders and/or usenet groups
2766				// in addition to the users private mailboxes
2767				// see http://www.faqs.org/rfcs/rfc2060.html  section 6.3.8 (which is not entirely clear on this)
2768				//if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_mailsvr_namespace: issuing: $GLOBALS[phpgw_dcom_'.$acctnum.']->dcom->listmailbox('.$mailsvr_stream.', '.$mailsvr_callstr.', %)'.'<br />'); }
2769				if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_mailsvr_namespace: issuing: $this->phpgw_listmailbox('.$mailsvr_callstr.', \'%\', '.$acctnum.')<br />'); }
2770
2771				//$name_space = $GLOBALS['phpgw_dcom_'.$acctnum]->dcom->listmailbox($mailsvr_stream, $mailsvr_callstr, '%');
2772				$name_space = $this->phpgw_listmailbox($mailsvr_callstr, '%', $acctnum);
2773
2774				if ($this->debug_args_special_handlers > 2) { $this->dbug->out('mail_msg: get_mailsvr_namespace: raw $name_space dump<pre>'; print_r($name_space); echo '</pre>'); }
2775
2776				if (!$name_space)
2777				{
2778					// if the server returns nothing, just use the most common namespace, "INBOX"
2779					// note: "INBOX" is NOT case sensitive according to rfc2060
2780					$name_space = 'INBOX';
2781				}
2782				elseif (is_array($name_space))
2783				{
2784					// if the server returns an array of namespaces, the first one is usually the users personal namespace
2785					// tyically "INBOX", there can be any number of other, unpredictable, namespaces also
2786					// used for the shared folders and/or nntp access (like #ftp), but we want the users "personal"
2787					// namespace used for their mailbox folders here
2788					// most likely that the first element of the array is the users primary personal namespace
2789					// I'm not sure but I think it's possible to have more than one personal (i.e. not public) namespace
2790					// note: do not use php function "is_array()" because php3 does not have it
2791					// later note: i think php3 does have "is_array()"
2792					$processed_name_space = $this->ensure_no_brackets($name_space[0]);
2793					if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_mailsvr_namespace: ($name_space is_array) $processed_name_space = $this->ensure_no_brackets($name_space[0]) [that arg='.$name_space[0].'] returns '.serialize($processed_name_space).'<br />'); }
2794					// put that back in name_space var
2795					$name_space = $processed_name_space;
2796				}
2797				elseif (is_string($name_space))
2798				{
2799					// if the server returns a string (not likely) just get rid of the brackets
2800					// note: do not use is_string() because php3 does not have it ???
2801					$processed_name_space = $this->ensure_no_brackets($name_space);
2802					if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_mailsvr_namespace: ($name_space is string) $processed_name_space = $this->ensure_no_brackets($name_space) [that arg='.$name_space.'] returns '.serialize($processed_name_space).'<br />'); }
2803					// put that back in name_space var
2804					$name_space = $processed_name_space;
2805				}
2806				else
2807				{
2808					// something really screwed up, EDUCATED GUESS
2809					// note: "INBOX" is NOT case sensitive according to rfc2060
2810					$name_space = 'INBOX';
2811					if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_mailsvr_namespace: ($name_space is NOT string nor array) GUESSING: $name_space = '.serialize($name_space).'<br />'); }
2812				}
2813			}
2814			*/
2815			else
2816			{
2817				// GENERIC IMAP NAMESPACE
2818				// imap servers usually use INBOX as their namespace
2819				// this is supposed to be discoverablewith the NAMESPACE command
2820				// see http://www.rfc-editor.org/rfc/rfc2342.txt
2821				// however as of PHP 4.0 this is not implemented, and some IMAP servers do not cooperate with it anyway
2822				$name_space = 'INBOX';
2823				if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_mailsvr_namespace:  GUESSING: $name_space = '.serialize($name_space).'<br />'); }
2824			}
2825
2826			// cache the result in "level one cache" class var holder
2827			if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_mailsvr_namespace: set "level 1 cache, class var" arg $this->set_arg_value(mailsvr_namespace, '.$name_space.', '.$acctnum.']) <br />'); }
2828			$this->set_arg_value('mailsvr_namespace', $name_space, $acctnum);
2829
2830			// -----------
2831			// SAVE DATA TO APPSESSION CACHE
2832			// -----------
2833			// save "mailsvr_namespace" to appsession data store
2834			if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_mailsvr_namespace: set appsession cache $this->save_session_cache_item(mailsvr_namespace, '.$name_space.', '.$acctnum.']) <br />'); }
2835			$this->save_session_cache_item('mailsvr_namespace', $name_space, $acctnum);
2836
2837			if ($this->debug_args_special_handlers > 0) { $this->dbug->out('mail_msg: get_mailsvr_namespace: LEAVING, returning $name_space: '.serialize($name_space).'<br />'); }
2838			return $name_space;
2839		}
2840
2841		/*!
2842		@function  uwash_string_ok
2843		@abstract make sure we do not use bad char sequence for uwash filesystem namespace
2844		@result (string) namespace if OK, or boolean False if namespace is NOT OK
2845		@param $namespace (string) the UWash namespace to test
2846		@discussion This can "validate" a UWash namespace AND ALSO ANY UWash
2847		folder string, checking for chars that should not be used in a filesystem based folder
2848		name string, such as a bare forward slash, or dot dot slash, etc.
2849		@access private or public
2850		*/
2851		function uwash_string_ok($namespace='')
2852		{
2853			// it os OK unless we find bad stuff
2854			$is_ok = True;
2855			$ns = trim($namespace);
2856			if ($ns == '')
2857			{
2858				$is_ok = False;
2859			}
2860			elseif (($ns == '/')
2861			|| ($ns == SEP))
2862			{
2863				$is_ok = False;
2864			}
2865			elseif ((stristr($ns,'..'))
2866			|| (stristr($ns,'./'))
2867			|| (stristr($ns,'/.')))
2868			{
2869				$is_ok = False;
2870			}
2871			// return False if bad, else return the ns we verified as OK
2872			if ($is_ok == True)
2873			{
2874				return $ns;
2875			}
2876			else
2877			{
2878				return False;
2879			}
2880		}
2881
2882		/*!
2883		@function  get_mailsvr_delimiter
2884		@abstract will generate the appropriate token that goes between the namespace and the inferior folders (subfolders)
2885		@example (a) typical imap  "INBOX.Sent"  returns the "." as the delimiter,
2886		(b) UWash imap (stock mbox)  "email/Sent"  returns the "/" as the delimiter
2887		@access PRIVATE - public access is object->get_arg_value("mailsvr_delimiter")
2888		*/
2889		function get_mailsvr_delimiter($acctnum='')
2890		{
2891			if (stristr($this->skip_args_special_handlers, 'get_mailsvr_delimiter'))
2892			{
2893				$fake_return = '/';
2894				if ($this->debug_args_special_handlers > 0) { $this->dbug->out('mail_msg: get_mailsvr_delimiter: debug SKIP, $fake_return: '.serialize($fake_return).' <br />'); }
2895				return $fake_return;
2896			}
2897
2898			if ($this->debug_args_special_handlers > 0) { $this->dbug->out('mail_msg: get_mailsvr_delimiter: ENTERING , feed $acctnum: ['.$acctnum.']<br />'); }
2899
2900			if ((!isset($acctnum))
2901			|| ((string)$acctnum == ''))
2902			{
2903				$acctnum = $this->get_acctnum();
2904			}
2905			if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_mailsvr_delimiter: after testing feed arg, using $acctnum: ['.$acctnum.']<br />'); }
2906
2907			// UWash style: "/"
2908			// all other imap servers *should* be "."
2909
2910			// do we have cached data that we can use?
2911			$class_cached_mailsvr_delimiter = $this->_direct_access_arg_value('mailsvr_delimiter', $acctnum);
2912			if ($class_cached_mailsvr_delimiter != '')
2913			{
2914				// return the cached data
2915				if ($this->debug_args_special_handlers > 0) { $this->dbug->out('mail_msg: get_mailsvr_delimiter: LEAVING, returned class var cached data: '.serialize($class_cached_mailsvr_delimiter).'<br />'); }
2916				return $class_cached_mailsvr_delimiter;
2917			}
2918
2919			//if ($this->debug_args_special_handlers > 0) { $this->dbug->out('mail_msg: get_mailsvr_delimiter: $this->get_pref_value(imap_server_type, '.$acctnum.') returns: ['.$this->get_pref_value('imap_server_type', $acctnum).'] ; api var SEP: ['.serialize(SEP).']<br />'); }
2920			if ($this->get_pref_value('imap_server_type', $acctnum) == 'UWash')
2921			{
2922				//$delimiter = '/';
2923				//$delimiter = SEP;
2924
2925				// UWASH is a filesystem based thing, so the delimiter is whatever the system SEP is
2926				// unix = /  and win = \ (win maybe even "\\" because the backslash needs escaping???
2927				// currently the filesystem seterator is provided by phpgw api as constant "SEP"
2928				if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_mailsvr_delimiter: imap_server_type is UWash<br />'); }
2929				if (!SEP)
2930				{
2931					$delimiter = '/';
2932				}
2933				else
2934				{
2935					$delimiter = SEP;
2936				}
2937			}
2938			else
2939			{
2940				// GENERIC IMAP DELIMITER
2941				// imap servers usually use a "." as their delimiter
2942				// this is supposed to be discoverable with the NAMESPACE command
2943				// see http://www.rfc-editor.org/rfc/rfc2342.txt
2944				// however as of PHP 4.0 this is not implemented
2945				if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_mailsvr_delimiter: imap_server_type is OTHER than UWash<br />'); }
2946				$delimiter = '.';
2947			}
2948			// cache the result to "level 1 cache" class arg holder var
2949			if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_mailsvr_delimiter: set "level 1 cache, class var" arg $this->set_arg_value(mailsvr_delimiter, '.$delimiter.', '.$acctnum.']) <br />'); }
2950			$this->set_arg_value('mailsvr_delimiter', $delimiter, $acctnum);
2951
2952			if ($this->debug_args_special_handlers > 0) { $this->dbug->out('mail_msg: get_mailsvr_delimiter: LEAVING, returning: '.serialize($delimiter).' for $acctnum ['.$acctnum.']<br />'); }
2953			return $delimiter;
2954		}
2955
2956		/*!
2957		@function  get_mailsvr_supports_folders
2958		@abstract imap and nntp servers have folders, pop has only inbox, this is a utility function.
2959		@result boolean
2960		@access private
2961		*/
2962		function get_mailsvr_supports_folders($acctnum='')
2963		{
2964			if ((!isset($acctnum))
2965			|| ((string)$acctnum == ''))
2966			{
2967				$acctnum = $this->get_acctnum();
2968			}
2969
2970			// Does This Mailbox Support Folders (i.e. more than just INBOX)?
2971
2972			//if (($this->get_pref_value('mail_server_type',$acctnum ) == 'imap')
2973			//|| ($this->get_pref_value('mail_server_type', $acctnum) == 'imaps')
2974			//|| ($this->newsmode))
2975			$mail_server_type = $this->get_pref_value('mail_server_type', $acctnum);
2976			if (stristr($mail_server_type, 'imap'))
2977			{
2978				return True;
2979			}
2980			else
2981			{
2982				return False;
2983			}
2984		}
2985
2986		/*!
2987		@function get_folder_long
2988		@abstract  will generate the long name of an imap folder name, contains NAMESPACE_DELIMITER_FOLDER string
2989		but NOT the {serverName:port} part, (inline docparser repeat): ... but NOT the &#123;serverName:port&#125; part
2990		@param $feed_folder (string) optional, defaults to inbox
2991		@result string the long name of an imap folder name, contains NAMESPACE_DELIMITER_FOLDER string
2992		@discussion  Note that syntax "{serverName:port}NAMESPACE_DELIMITER_FOLDER" is called a "fully qualified"
2993		folder name here. The param $feed_folder will be compared to the folder list supplied by the server to insure
2994		an accurate folder name is returned because a param $feed_folder LACKING a namespace or delimiter MUST
2995		have them added in order to become a "long" folder name, and just guessing is not good enough to ensure accuracy.
2996		Works with supported imap servers: UW-Maildir, Cyrus, Courier, UWash
2997		Example (Cyrus or Courier):  INBOX.Templates
2998		Example (if subfolders a.k.a. "inferior folders" are enabled):  INBOX.drafts.rfc
2999		????   Example (UW-Maildir only): /home/James.Drafts   ????
3000		The above examle would suggext that UW-Maildir takes "~" as namespace and "/" as its pre-folder name delimiter,
3001		which as somewhat nonstandard because it appears the rest of the folder name uses "." as the delimiter.
3002		@access Public
3003		*/
3004		function get_folder_long($feed_folder='INBOX')
3005		{
3006			$feed_folder = urldecode($feed_folder);
3007			$folder = $this->ensure_no_brackets($feed_folder);
3008			if ($folder == 'INBOX')
3009			{
3010				// INBOX is (always?) a special reserved word with nothing preceeding it in long or short form
3011				$folder_long = 'INBOX';
3012			}
3013			else
3014			{
3015				$name_space = $this->get_arg_value('mailsvr_namespace');
3016				$delimiter = $this->get_arg_value('mailsvr_delimiter');
3017				//if (strstr($folder,"$name_space" ."$delimiter") == False)
3018				// "INBOX" as namespace is NOT supposed to be case sensitive
3019				if (stristr($folder,"$name_space" ."$delimiter") == False)
3020				{
3021					// the [namespace][delimiter] string was not present
3022					// CONTROVERSIAL: add the [namespace][delimiter] string
3023					// this will incorrectly change a shared folder name, whose name may not
3024					// supposed to have the [namespace][delimiter] string
3025					$folder_long = "$name_space" ."$delimiter" ."$folder";
3026				}
3027				else
3028				{
3029					// this folder is already in "long" format (it's namespace and delimiter already there)
3030					$folder_long = $folder;
3031				}
3032			}
3033			//echo 'get_folder_long('.$folder.')='.$folder_long.'<br />';
3034			return trim($folder_long);
3035		}
3036
3037		/*!
3038		@function get_folder_short
3039		@abstract  will generate the SHORT name of an imap folder name, i.e. strip off {SERVER}NAMESPACE_DELIMITER
3040		(inline docparser repeat): ... strip off &#123;SERVER&#125;NAMESPACE_DELIMITER
3041		@param $feed_folder string
3042		@result string the "shortened" name of a given imap folder name
3043		@discussion  Simply, this is the folder name without the {serverName:port}  nor the NAMESPACE  nor the DELIMITER
3044		preceeding it. Inline docparser repeat: ... without the &#123;serverName:port&#125; nor the NAMESPACE
3045		nor the DELIMITER  preceeding it.
3046		Works with supported imap servers UWash, UW-Maildir, Cyrus, Courier
3047		(old) Example (Cyrus or Courier):  Templates
3048		(old) Example (Cyrus only):  drafts.rfc
3049		*/
3050		function get_folder_short($feed_folder='INBOX', $acctnum='')
3051		{
3052			// Example: "Sent"
3053			// Cyrus may support  "Sent.Today"
3054			// note: we need $acctnum to obtain the right namespace and delimiter, so we can strip them
3055			if ((!isset($acctnum))
3056			|| ((string)$acctnum == ''))
3057			{
3058				$acctnum = $this->get_acctnum();
3059			}
3060			$feed_folder = urldecode($feed_folder);
3061			$folder = $this->ensure_no_brackets($feed_folder);
3062			if ($folder == 'INBOX')
3063			{
3064				// INBOX is (always?) a special reserved word with nothing preceeding it in long or short form
3065				$folder_short = 'INBOX';
3066			}
3067			else
3068			{
3069				$name_space = $this->get_arg_value('mailsvr_namespace', $acctnum);
3070				$delimiter = $this->get_arg_value('mailsvr_delimiter', $acctnum);
3071				//if (strstr($folder,"$name_space" ."$delimiter") == False)
3072				// "INBOX" as namespace is NOT supposed to be case sensitive
3073				if (stristr($folder,"$name_space" ."$delimiter") == False)
3074				{
3075					$folder_short = $folder;
3076				}
3077				else
3078				{
3079					//$folder_short = strstr($folder,$delimiter);
3080					$folder_short = stristr($folder,$delimiter);
3081					// get rid of that delimiter (it's included from the stristr above)
3082					$folder_short = substr($folder_short, 1);
3083				}
3084			}
3085			return $folder_short;
3086		}
3087
3088		/*!
3089		@function folder_list_change_callback
3090		@abstract dcom class callback to alert when dcom class has made a change to the folder list
3091		@param $acctnum  OPTIONAL
3092		discussion CACHE NOTE: this item is saved in the appsession cache, the folder_list, so altering
3093		that list requires wiping that saved info because it has become stale.
3094		@author Angles
3095		*/
3096		function folder_list_change_callback($acctnum='')
3097		{
3098			if ($this->debug_args_special_handlers > 0) { $this->dbug->out('mail_msg: folder_list_change_callback('.__LINE__.'): ENTERING, param $acctnum ['.$acctnum.']<br />'); }
3099			// what acctnum is operative here, we can only get a folder list for one account at a time (obviously)
3100			if ((!isset($acctnum))
3101			|| ((string)$acctnum == ''))
3102			{
3103				$acctnum = $this->get_acctnum();
3104			}
3105			if ($this->debug_args_special_handlers > 0) { $this->dbug->out('mail_msg: folder_list_change_callback('.__LINE__.'): willo use $acctnum ['.$acctnum.']<br />'); }
3106			// class dcom recorded a change in the folder list
3107			// supposed to happen when create or delete or rename mailbox is called
3108			// dcom class will callback to this function to handle cleanup of stale folder_list data
3109			// expire cached data
3110			if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: folder_list_change_callback('.__LINE__.'): calling $this->expire_session_cache_item(folder_list, '.$acctnum.') <br />'); }
3111			$sucess = $this->expire_session_cache_item('folder_list', $acctnum);
3112
3113			if ($this->debug_args_special_handlers > 0) { $this->dbug->out('mail_msg: folder_list_change_callback('.__LINE__.'): LEAVING, returning $sucess ['.$sucess.']<br />'); }
3114			return $sucess;
3115		}
3116
3117		/*!
3118		@function get_folder_list
3119		@abstract  list of folders in a numbered array, each element has 2 properties, "folder_long" and "folder_short"
3120		@param $acctnum (int) OPTIONAL
3121		@param $force_refresh   boolean, will cause any cached folder data to expire, and "fresh" data is retrieved from the mailserver
3122		@return   array   numbered, with each numbered element having array keys  "folder_long" and "folder_short"
3123		@discussion  returns a numbered array, each element has 2 properties, "folder_long" and "folder_short"
3124		so every available folder is in the structure in both long form [namespace][delimiter][foldername]
3125		and short form (does not have the [namespace][delimiter] prefix to the folder name)
3126		This function can cache data in 2 ways
3127		(1) caching as server data in the prefs DB cache department, and
3128		(2) in the class var $this->get_arg_value("folder_list")
3129		Data will be grabbed from cache when available and when allowed.
3130		CACHE NOTE: this item is saved in the appsession cache.
3131		@access private  - public access is object->get_arg_value("folder_list") but may be
3132		called directly if you need to manually force_refresh any cached data, although this is still for
3133		private use as well.
3134		*/
3135		function get_folder_list($acctnum='', $force_refresh=False)
3136		{
3137			if ($this->debug_args_special_handlers > 0) { $this->dbug->out('mail_msg: get_folder_list: ENTERING<br />'); }
3138			// what acctnum is operative here, we can only get a folder list for one account at a time (obviously)
3139			if ((!isset($acctnum))
3140			|| ((string)$acctnum == ''))
3141			{
3142				$acctnum = $this->get_acctnum();
3143			}
3144			if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_folder_list: for the rest of this function we will use $acctnum: ['.$acctnum.'] <br />'); }
3145			// hardcore debug
3146			if (stristr($this->skip_args_special_handlers, 'get_folder_list'))
3147			{
3148				$fake_return = array();
3149				$fake_return[0] = array();
3150				$fake_return[0]['folder_long'] = 'INBOX';
3151				$fake_return[0]['folder_short'] = 'INBOX';
3152				$fake_return[0]['acctnum'] = $acctnum;
3153				if ($this->debug_args_special_handlers > 0) { $this->dbug->out('mail_msg: get_folder_list: LEAVING, debug SKIP, $fake_return: '.serialize($fake_return).' <br />'); }
3154				return $fake_return;
3155			}
3156
3157			//if ($this->debug_args_special_handlers > 2) { $this->dbug->out('mail_msg: get_folder_list: $this->_direct_access_arg_value(folder_list, '.$acctnum.') dump:<pre>'; print_r($this->_direct_access_arg_value('folder_list', $acctnum)); echo '</pre>'); }
3158
3159			// check if class dcom reports that the folder list has changed
3160			// is this accounts dcom object has not been created yet, then obviously we did not just change its folder list
3161			// NOTE THIS IS OBSOLETED - THE DCOM CLASS NOW USES CALLBACK FUNCTION "folder_list_change_callback"
3162			if ((is_object($GLOBALS['phpgw_dcom_'.$acctnum]->dcom))
3163			&& ($GLOBALS['phpgw_dcom_'.$acctnum]->dcom->folder_list_changed == True))
3164			{
3165				// class dcom recorded a change in the folder list
3166				// supposed to happen when create or delete mailbox is called
3167				// reset the changed flag
3168				$GLOBALS['phpgw_dcom_'.$acctnum]->dcom->folder_list_changed = False;
3169				// set up for a force_refresh
3170				$force_refresh = True;
3171				if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_folder_list: class dcom report folder list changed<br />'); }
3172				if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_folder_list: make sure folder data is removed from cache <br />'); }
3173				// expire appsession cache
3174				$this->expire_session_cache_item('folder_list', $acctnum);
3175			}
3176
3177			// see if we have object class var cached data that we can use
3178			$class_cached_folder_list = $this->_direct_access_arg_value('folder_list', $acctnum);
3179			if ($this->debug_args_special_handlers > 2) { $this->dbug->out('mail_msg: get_folder_list: $this->_direct_access_arg_value(folder_list, '.$acctnum.') DUMP:', $this->_direct_access_arg_value('folder_list', $acctnum)); }
3180			if ((count($class_cached_folder_list) > 0)
3181			&& ($force_refresh == False))
3182			{
3183				// use the cached data
3184				if ($this->debug_args_special_handlers > 2) { $this->dbug->out(' * * $class_cached_folder_list DUMP:', $class_cached_folder_list); }
3185				if ($this->debug_args_special_handlers > 0) { $this->dbug->out('mail_msg: get_folder_list: LEAVING,  using object cached folder list data<br />'); }
3186				return $class_cached_folder_list;
3187			}
3188			elseif (($this->get_pref_value('mail_server_type', $acctnum) == 'pop3')
3189			|| ($this->get_pref_value('mail_server_type', $acctnum) == 'pop3s'))
3190			{
3191				// normalize the folder_list property
3192				$my_folder_list = array();
3193				// POP3 servers have 1 folder: INBOX
3194				$my_folder_list[0] = array();
3195				$my_folder_list[0]['folder_long'] = 'INBOX';
3196				$my_folder_list[0]['folder_short'] = 'INBOX';
3197				$my_folder_list[0]['acctnum'] = $acctnum;
3198				// save result to "Level 1 cache" class arg holder var
3199				$this->set_arg_value('folder_list', $my_folder_list, $acctnum);
3200				if ($this->debug_args_special_handlers > 0) { $this->dbug->out('mail_msg: get_folder_list: LEAVING,  pop3 servers only have one folder: INBOX<br />'); }
3201				return $my_folder_list;
3202			}
3203			elseif ($force_refresh == False)
3204			{
3205				// -----------
3206				// TRY CACHED DATA FROM APPSESSION
3207				// -----------
3208				// try to restore "folder_list" from saved appsession data store
3209				$appsession_cached_folder_list = $this->read_session_cache_item('folder_list', $acctnum);
3210				if ($appsession_cached_folder_list)
3211				{
3212					if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_folder_list: got appsession cached data<br />'); }
3213					$cached_data = $appsession_cached_folder_list;
3214					if ($this->debug_args_special_handlers > 2) { $this->dbug->out('mail_msg: get_folder_list: appsession cached data DUMP:', $cached_data); }
3215					// we no longer need this var
3216					$appsession_cached_folder_list = '';
3217					unset($appsession_cached_folder_list);
3218				}
3219				else
3220				{
3221					if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_folder_list: NO appsession cached data was available<br />'); }
3222					$cached_data = False;
3223				}
3224
3225				// if there's no data we'll get back a FALSE
3226				if ($cached_data)
3227				{
3228					//if ($this->debug_args_special_handlers > 1) { echo 'mail_msg: get_folder_list: using *Prefs DB* cached folder list data<br />';}
3229					if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_folder_list: using appsession cached folder list data<br />'); }
3230					if (!isset($cached_data[0]['folder_short']))
3231					{
3232						// OLD cached folder list does NOT contain "folder_short" data
3233						// that cuts cached data in 1/2, no need to cache something this easy to deduce
3234						// therefor... add FOLDER SHORT element to cached_data array structure
3235						if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_folder_list: (L1) adding [folder_short] element to $cached_data array<br />'); }
3236						for ($i=0; $i<count($cached_data);$i++)
3237						{
3238							$my_folder_long = $cached_data[$i]['folder_long'];
3239							$my_folder_acctnum = $cached_data[$i]['acctnum'];
3240							$my_folder_short = $this->get_folder_short($my_folder_long, $my_folder_acctnum);
3241							if ($this->debug_args_special_handlers > 1) { $this->dbug->out('* * mail_msg: get_folder_list: add folder_short loop (L1) ['.$i.']: $my_folder_long ['.$my_folder_long.'] ; $my_folder_acctnum ['.$my_folder_acctnum.'] ; $my_folder_short ['.$my_folder_short.']<br />'); }
3242							$cached_data[$i]['folder_short'] = $my_folder_short;
3243							//$cached_data[$i]['folder_short'] = $this->get_folder_short($cached_data[$i]['folder_long']);
3244							if ($this->debug_args_special_handlers > 2) { $this->dbug->out(' * * $cached_data['.$i.'][folder_long]='.htmlspecialchars($cached_data[$i]['folder_long']).' ; $cached_data['.$i.'][folder_short]='.htmlspecialchars($cached_data[$i]['folder_short']).'<br />'); }
3245						}
3246						if ($this->debug_args_special_handlers > 2) { $this->dbug->out('mail_msg: get_folder_list: $cached_data *after* adding "folder_short" data DUMP:', $cached_data); }
3247						// -----------
3248						// SAVE DATA TO APPSESSION DB CACHE (WITH the [folder_short] data)
3249						// -----------
3250						// save "folder_list" (WITH ADDED  folder short data) to appsession data store
3251						// new style folder_list is stored FULL, has all elements
3252						if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_folder_list: added folder short, now resave in DB as new style, complete folder list, set appsession cache $this->save_session_cache_item(folder_list, $cached_data, '.$acctnum.']) <br />'); }
3253						$this->save_session_cache_item('folder_list', $cached_data, $acctnum);
3254					}
3255					// cache the result in "Level 1 cache" class object var
3256					if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_folder_list: put folder_list into Level 1 class var "cache" $this->set_arg_value(folder_list, $cached_data, '.$acctnum.');<br />'); }
3257					$this->set_arg_value('folder_list', $cached_data, $acctnum);
3258					if ($this->debug_args_special_handlers > 0) { $this->dbug->out('mail_msg: get_folder_list: LEAVING, got data from cache<br />'); }
3259					return $cached_data;
3260				}
3261				else
3262				{
3263					if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_folder_list: NO cached folder list data, fallback to get data from mailserver<br />'); }
3264				}
3265			}
3266
3267			// if we get here we must actually get the data from the mailsvr
3268			// otherwise we would have return/broke out of this function
3269			// only IF statement above that allows code to reach here is if we are allowed to use
3270			// cached data, BUT none exists
3271			if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_folder_list: need to get data from mailserver<br />'); }
3272
3273			// Establish Email Server Connectivity Information
3274			$mailsvr_stream = $this->get_arg_value('mailsvr_stream', $acctnum);
3275			$mailsvr_callstr = $this->get_arg_value('mailsvr_callstr', $acctnum);
3276			$name_space = $this->get_arg_value('mailsvr_namespace', $acctnum);
3277			$delimiter = $this->get_arg_value('mailsvr_delimiter', $acctnum);
3278
3279			// get a list of available folders from the server
3280			if ($this->get_pref_value('imap_server_type', $acctnum) == 'UWash')
3281			{
3282				if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_folder_list: mailserver is of type UWash<br />'); }
3283				/*!
3284				@concept UWash IMAP Namespace
3285				@discussion uwash is file system based, so it requires a filesystem slash after the namespace.
3286				Note that with uwash the delimiter is in fact the file system slash.
3287				example: requesting list for "mail/*"
3288				(NOTE  this <slash><star> request will NOT yield a list the INBOX folder included in the returned folder list).
3289				However, we have no choice since without the <slash> filesystem delimiter, requesting "email*" returns NOTHING.
3290				Example queries: (a) "~/"
3291				OR (b) if the user specifies specific mbox folder, then: "~/emails/*"
3292				OR (c) "emails/*" give the same result, much like a unix "ls" command.
3293				At this time we use "unqualified" a.k.a. "relative" directory names if the user provides a namespace.
3294				UWash will consider it relative to the mailuser's $HOME property as with "emails/*" (DOES THIS WORK ON ALL PLATFORMS??).
3295				BUT we use <tilde><slash> "~/" if no namespace is given.
3296				@returns
3297				UWASH IMAP returns information in this format (same as any other IMAP server format):
3298				{SERVER_NAME:PORT}FOLDERNAME
3299				(inline docparser repeat): &#123;SERVER_NAME:PORT&#125;FOLDERNAME
3300				For Example:
3301				{some.server.com:143}Trash AND
3302				{some.server.com:143}Archives/Letters
3303				(inline docparser repeat):
3304				&#123;some.server.com:143&#125;Trash AND
3305				&#123;some.server.com:143&#125;Archives/Letters
3306				*/
3307				$mailboxes = $this->phpgw_listmailbox($mailsvr_callstr, "$name_space" ."$delimiter" ."*", $acctnum);
3308				// UWASH IMAP returns information in this format:
3309				// {SERVER_NAME:PORT}FOLDERNAME
3310				// example:
3311				// {some.server.com:143}Trash
3312				// {some.server.com:143}Archives/Letters
3313			}
3314			else
3315			{
3316				if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_folder_list: mailserver is other than UWash type<br />'); }
3317				/*!
3318				@concept non-UWash IMAP Server Namespace
3319				@discussion when handling handle non-UWash IMAP servers,
3320				i.e. typical IMAP servers that do not use a filesystem slash as the "delimiter",
3321				the last arg is typically "INBOX*" (no dot) which DOES include the inbox in the list of folders.
3322				Wheres adding the delimiter "INBOX.*" (has dot) will NOT include the INBOX in the list of folders.
3323				So - it's theoretically safe to include the delimiter here, but the INBOX will not be included in the list,
3324				this is typically the ONLY TIME you would ever *not* use the delimiter between the namespace and what comes after it.
3325				HOWEVER to get *shared* folders included in the return, better NOT include the "." delimiter.
3326				For example: Cyrus does not like anything but a "*" as the pattern IF you want shared folders returned.
3327				Return data is a list suck as this:
3328				{some.server.com:143}INBOX
3329				{some.server.com:143}INBOX.Trash
3330				(inline docparser repeat):
3331				&#123;some.server.com:143&#125;INBOX AND
3332				&#123;some.server.com:143&#125;INBOX.Trash
3333				*/
3334				$mailboxes = $this->phpgw_listmailbox($mailsvr_callstr, "*", $acctnum);
3335				// returns information in this format:
3336				// {SERVER_NAME:PORT} NAMESPACE DELIMITER FOLDERNAME
3337				// example:
3338				// {some.server.com:143}INBOX
3339				// {some.server.com:143}INBOX.Trash
3340			}
3341			if ($this->debug_args_special_handlers > 2) { $this->dbug->out('mail_msg: get_folder_list: server returned $mailboxes DUMP:', $mailboxes); }
3342			//echo 'raw mailbox list:<br />'.htmlspecialchars(serialize($mailboxes)).'<br />';
3343
3344			// ERROR DETECTION
3345			if (!$mailboxes)
3346			{
3347				// we got no information back, clear the folder_list property
3348				// normalize the folder_list property
3349				$my_folder_list = array();
3350				// *assume* (i.e. pretend)  we have a server with only one box: INBOX
3351				$my_folder_list[0] = array();
3352				$my_folder_list[0]['folder_long'] = 'INBOX';
3353				$my_folder_list[0]['folder_short'] = 'INBOX';
3354				$my_folder_list[0]['acctnum'] = $acctnum;
3355				// save result to "Level 1 cache" class arg holder var
3356				$this->set_arg_value('folder_list', $my_folder_list, $acctnum);
3357				if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_folder_list: error, no mailboxes returned from server, fallback to "INBOX" as only folder, $this->set_arg_value(folder_list, $my_folder_list) to hold that value<br />'); }
3358				if ($this->debug_args_special_handlers > 0) { $this->dbug->out('mail_msg: get_folder_list: LEAVING, with error, no mailboxes returned from server, return list with only INBOX<br />'); }
3359				return $my_folder_list;
3360			}
3361
3362			// was INBOX included in the list? Some servers (uwash) do not return it
3363			$has_inbox = False;
3364			for ($i=0; $i<count($mailboxes);$i++)
3365			{
3366				$this_folder = $this->get_folder_short($mailboxes[$i]);
3367				//if ($this_folder == 'INBOX')
3368				// rfc2060 says "INBOX" as a namespace can not be case sensitive
3369				if ((stristr($this_folder, 'INBOX'))
3370				&& (strlen($this_folder) == strlen('INBOX')))
3371				{
3372					$has_inbox = True;
3373					break;
3374				}
3375			}
3376			// ADD INBOX if necessary
3377			if ($has_inbox == False)
3378			{
3379				if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_folder_list: adding INBOX to mailboxes data<br />'); }
3380				// use the same "fully qualified" folder name format that "phpgw_listmailbox" returns, includes the {serverName:port}
3381				$add_inbox = $mailsvr_callstr.'INBOX';
3382				$next_available = count($mailboxes);
3383				// add it to the $mailboxes array
3384				$mailboxes[$next_available] = $add_inbox;
3385			}
3386
3387			// sort folder names
3388			// note: php3 DOES have is_array(), ok to use it here
3389			if (is_array($mailboxes))
3390			{
3391				// mainly to avoid warnings
3392				sort($mailboxes);
3393			}
3394
3395			// normalize the folder_list property, we will transfer raw data in $mailboxes array to processed data in $my_folder_list
3396			$my_folder_list = array();
3397
3398			// make a $my_folder_list array structure with ONLY FOLDER LONG data
3399			// save that to cache, that cuts cached data in 1/2
3400			// (LATER - we will add the "folder_short" data)
3401			for ($i=0; $i<count($mailboxes);$i++)
3402			{
3403				// "is_imap_folder" really just a check on what UWASH imap returns, may be files that are not MBOX's
3404				if ($this->is_imap_folder($mailboxes[$i]))
3405				{
3406					//$this->a[$acctnum]['folder_list'][$i]['folder_long'] = $this->get_folder_long($mailboxes[$i]);
3407					// what we (well, me, Angles) calls a "folder long" is the raw data returned from the server (fully qualified name)
3408					// MINUS the bracketed server, so we are calling "folder long" a NAMESPACE_DELIMITER_FOLDER string
3409					// WITHOUT the {serverName:port} part, if that part is included we (Angles) call this "fully qualified"
3410					$next_idx = count($my_folder_list);
3411					$my_folder_list[$next_idx]['folder_long'] = $this->ensure_no_brackets($mailboxes[$i]);
3412					// AS SOON as possible, add data indicating WHICH ACCOUNT this folder list came from
3413					// while it is still somewhat easy to determine this
3414					$my_folder_list[$next_idx]['acctnum'] = $acctnum;
3415				}
3416			}
3417			if ($this->debug_args_special_handlers > 2) { $this->dbug->out('mail_msg: get_folder_list: my_folder_list with only "folder_long" DUMP:', $my_folder_list); }
3418			// NEW just save the complete list, leaving out the folder short does not reduce size my a material amount
3419			//// -----------
3420			//// SAVE DATA TO APPSESSION DB CACHE (without the [folder_short] data)
3421			//// -----------
3422			//// save "folder_list" (without folder short data) to appsession data store
3423			//if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_folder_list: set appsession cache $this->save_session_cache_item(folder_list, $my_folder_list, '.$acctnum.']) <br />'); }
3424			//$this->save_session_cache_item('folder_list', $my_folder_list, $acctnum);
3425
3426			// add FOLDER SHORT element to folder_list array structure
3427			// that cuts cached data in 1/2, no need to cache something this easy to deduce
3428			// NEW: forget about it, just add folder short THEN SAVE it, additional data is not that much more
3429			for ($i=0; $i<count($my_folder_list);$i++)
3430			{
3431				$my_folder_long = $my_folder_list[$i]['folder_long'];
3432				$my_folder_acctnum = $my_folder_list[$i]['acctnum'];
3433				$my_folder_short = $this->get_folder_short($my_folder_long, $my_folder_acctnum);
3434				if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_folder_list: add folder_short loop['.$i.']: $my_folder_long ['.$my_folder_long.'] ; $my_folder_acctnum ['.$my_folder_acctnum.'] ; $my_folder_short ['.$my_folder_short.']<br />'); }
3435				$my_folder_list[$i]['folder_short'] = $my_folder_short;
3436			}
3437
3438			// -----------
3439			// SAVE DATA TO APPSESSION DB CACHE (WITH the [folder_short] data)
3440			// -----------
3441			// save "folder_list" (WITH ADDED  folder short data) to appsession data store
3442			// new style folder_list is stored FULL, has all elements
3443			if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_folder_list: set appsession cache $this->save_session_cache_item(folder_list, $my_folder_list, '.$acctnum.']) <br />'); }
3444			$this->save_session_cache_item('folder_list', $my_folder_list, $acctnum);
3445
3446			// cache the result to "level 1 cache" class arg holder var
3447			if ($this->debug_args_special_handlers > 1) { $this->dbug->out('mail_msg: get_folder_list: set Level 1 class var "cache" $this->set_arg_value(folder_list, $my_folder_list, '.$acctnum.') <br />'); }
3448			$this->set_arg_value('folder_list', $my_folder_list, $acctnum);
3449
3450			// finished, return the folder_list array atructure
3451			if ($this->debug_args_special_handlers > 2) { $this->dbug->out('mail_msg: get_folder_list: finished, $my_folder_list DUMP:', $my_folder_list); }
3452			if ($this->debug_args_special_handlers > 0) { $this->dbug->out('mail_msg: get_folder_list: LEAVING, got folder data from server<br />'); }
3453			return $my_folder_list;
3454		}
3455
3456
3457		/*!
3458		@function folder_lookup
3459		@abstract searches thru the list of available folders to determine if a given folder already exists
3460		@param $mailsvr_stream - DEPRECIATED to not use, class provides this.
3461		@param $folder_needle string ?
3462		@param $acctnum int ?
3463		@discussion uses "folder_list[folder_long]" as the "haystack" because it is the most unaltered folder
3464		information returned from the server that we have
3465		if TRUE, then the "official" folder_long name is returned - the one supplied by the server itself
3466		during the get_folder_list routine - "folder_list[folder_long]"
3467		if False, an empty string is returned.
3468		*/
3469		function folder_lookup($mailsvr_stream, $folder_needle='INBOX', $acctnum='')
3470		{
3471			if ($this->debug_args_input_flow > 0) { $this->dbug->out('mail_msg: folder_lookup: ENTERING , feed $folder_needle: ['.htmlspecialchars($folder_needle).'], feed (optional) $acctnum: ['.$acctnum.']<br />'); }
3472			if ((!isset($acctnum))
3473			|| ((string)$acctnum == ''))
3474			{
3475				$acctnum = $this->get_acctnum();
3476			}
3477
3478			//$folder_list = $this->get_folder_list($acctnum);
3479			$folder_list = $this->get_arg_value('folder_list', $acctnum);
3480			//$folder_list =& $this->get_arg_value_ref('folder_list', $acctnum);
3481
3482			if ($this->debug_args_input_flow > 1)
3483			{
3484				$debug_folder_lookup = True;
3485			}
3486			else
3487			{
3488				$debug_folder_lookup = False;
3489			}
3490
3491			// retuen an empty string if the lookup fails
3492			$needle_official_long = '';
3493			for ($i=0; $i<count($folder_list);$i++)
3494			{
3495				// folder_haystack is the official folder long name returned from the server during "get_folder_list"
3496				$folder_haystack = $folder_list[$i]['folder_long'];
3497				  if ($debug_folder_lookup) { $this->dbug->out(' _ ['.$i.'] [folder_needle] '.$folder_needle.' len='.strlen($folder_needle).' [folder_haystack] '.$folder_haystack.' len='.strlen($folder_haystack).'<br />' ); }
3498
3499				// first try to match the whole name, i.e. needle is already a folder long type name
3500				// the NAMESPACE should NOT be case sensitive
3501				// mostly, this means "INBOX" must not be matched case sensitively
3502				if (stristr($folder_haystack, $folder_needle))
3503				{
3504					if ($debug_folder_lookup) { $this->dbug->out(' _ entered stristr statement<br />'); }
3505					if (strlen($folder_haystack) == strlen($folder_needle))
3506					{
3507						// exact match - needle is already a fully legit folder_long name
3508						  if ($debug_folder_lookup) { $this->dbug->out(' _ folder exists, exact match, already legit long name: '.$needle_official_long.'<br />'); }
3509						$needle_official_long = $folder_haystack;
3510						break;
3511					}
3512					  if ($debug_folder_lookup) { $this->dbug->out(' _ exact match failed<br />'); }
3513					// if the needle is smaller than the haystack, then it is possible that the
3514					// needle is a partial folder name that will match a portion of the haystack
3515					// look for pattern [delimiter][folder_needle] in the last portion of string haystack
3516					// because we do NOT want to match a partial word, folder_needle should be a whole folder name
3517					//tried this: if (preg_match('/.*([\]|[.]|[\\/]){1}'.$folder_needle.'$/i', $folder_haystack))
3518					// problem: unescaped forward slashes will be in UWASH folder names needles
3519					// and unescaped dots will be in other folder names needles
3520					// so use non-regex comparing
3521					// haystack must be larger then needle+1 (needle + a delimiter) for this to work
3522					if (strlen($folder_haystack) > strlen($folder_needle))
3523					{
3524						if ($debug_folder_lookup) { $this->dbug->out(' _ entered partial match logic<br />'); }
3525						// at least the needle is somewhere in the haystack
3526						// 1) get the length of the needle
3527						$needle_len = strlen($folder_needle);
3528						// get a negative value for use in substr
3529						$needle_len_negative = ($needle_len * (-1));
3530						// go back one more char in haystack to get the delimiter
3531						$needle_len_negative = $needle_len_negative - 1;
3532						  if ($debug_folder_lookup) { $this->dbug->out(' _ needle_len: '.$needle_len.' and needle_len_negative-1: '.$needle_len_negative.'<br />' ); }
3533						// get the last part of haystack that is that length
3534						$haystack_end = substr($folder_haystack, $needle_len_negative);
3535						// look for pattern [delimiter][folder_needle]
3536						// because we do NOT want to match a partial word, folder_needle should be a whole folder name
3537						  if ($debug_folder_lookup) { $this->dbug->out(' _ haystack_end: '.$haystack_end.'<br />' ); }
3538						if ((stristr('/'.$folder_needle, $haystack_end))
3539						|| (stristr('.'.$folder_needle, $haystack_end))
3540						|| (stristr('\\'.$folder_needle, $haystack_end)))
3541						{
3542							$needle_official_long = $folder_haystack;
3543							  if ($debug_folder_lookup) { $this->dbug->out(' _ folder exists, lookup found partial match, official long name: '.$needle_official_long.'<br />'); }
3544							break;
3545						}
3546						 if ($debug_folder_lookup) { $this->dbug->out(' _ partial match failed<br />'); }
3547					}
3548				}
3549			}
3550			if ($this->debug_args_input_flow > 0) { $this->dbug->out('mail_msg: folder_lookup: LEAVING, returning $needle_official_long: ['.htmlspecialchars($needle_official_long).']<br />'); }
3551			return $needle_official_long;
3552		}
3553
3554		/*!
3555		@function is_imap_folder
3556		@abstract Used with UWash servers folder list to filter out names that are most like not folders.
3557		@param $folder string ?
3558		@result boolean
3559		@discussion returns True if the given foldername is probable an IMAP folder. Uses some general
3560		criteria such as anything with a DOT_SLASH is probably not the name of an IMAP folder.
3561		*/
3562		function is_imap_folder($folder)
3563		{
3564			// UWash is the only (?) imap server where there is any question whether a folder is legit or not
3565			if ($this->get_pref_value('imap_server_type') != 'UWash')
3566			{
3567				//echo 'is_imap_folder TRUE 1<br />';
3568				return True;
3569			}
3570
3571			$folder_long = $this->get_folder_long($folder);
3572
3573			// INBOX is ALWAYS a valid folder, and is ALWAYS called INBOX because it's a special reserved word
3574			// although it is NOT case sensitive
3575			//if ($folder_long == 'INBOX')
3576			if ((stristr($folder_long, 'INBOX'))
3577			&& (strlen($folder_long) == strlen('INBOX')))
3578			{
3579				//echo 'is_imap_folder TRUE 2<br />';
3580				return True;
3581			}
3582
3583			// UWash IMAP server looks for MBOX files, which it considers to be email "folders"
3584			// and will return any file, whether it's an actual IMAP folder or not
3585			if (strstr($folder_long,"/."))
3586			{
3587				// any pattern matching "/." for UWash is NOT an MBOX
3588				// because the delimiter for UWash is "/" and the immediately following "." indicates a hidden file
3589				// not an MBOX file, at least on Linux type system
3590				//echo 'is_imap_folder FALSE 3<br />';
3591				return False;
3592			}
3593
3594			// if user specifies namespace like "mail" then MBOX files are in $HOME/mail
3595			// so this server knows to put MBOX files in a special place
3596			// BUT if the namespace used is associated with $HOME, such as ~
3597			// then how many folders deep do you want to go? UWash is recursive, it will go as deep as possible into $HOME
3598
3599			// is this a $HOME type of namespace
3600			$the_namespace = $this->get_arg_value('mailsvr_namespace');
3601			if ($the_namespace == '~')
3602			{
3603				$home_type_namespace = True;
3604			}
3605			else
3606			{
3607				$home_type_namespace = False;
3608			}
3609
3610			// DECISION: no more than 4 DIRECTORIES DEEP of recursion
3611			$num_slashes = $this->substr_count_ex($folder_long, "/");
3612			if (($home_type_namespace)
3613			&& ($num_slashes >= 4))
3614			{
3615				// this folder name indicates we are too deeply recursed, we don't care beyond here
3616				//echo 'is_imap_folder FALSE 4<br />';
3617				return False;
3618			}
3619
3620			// if you get all the way to here then this must be a valid folder name
3621			//echo 'is_imap_folder TRUE 5<br />';
3622			return True;
3623		}
3624
3625		/*!
3626		@function care_about_unseen DEPRECIATED
3627		@abstract When reporting the number of unseen, or new, messages in a folder, we may skip
3628		folders such as Trash and Sent because we do not care what is unseen in those folders.
3629		@param $folder string ?
3630		@result boolean
3631		@discussion DEPRECIATED function was used when the number of unseen messages in a
3632		folder was included in the dropdown folder list combobox HTML select widget.
3633		*/
3634		function care_about_unseen($folder)
3635		{
3636			$folder = $this->get_folder_short($folder);
3637			// we ALWAYS care about new messages in the INBOX
3638			if ((stristr($folder_long, 'INBOX'))
3639			&& (strlen($folder_long) == strlen('INBOX')))
3640			{
3641				return True;
3642			}
3643
3644			$we_care = True; // initialize
3645			$ignore_these_folders = Array();
3646			// DO NOT CHECK UNSEEN for these folders
3647			$ignore_these_folders[0] = "sent";
3648			$ignore_these_folders[1] = "trash";
3649			$ignore_these_folders[2] = "templates";
3650			for ($i=0; $i<count($ignore_these_folders); $i++)
3651			{
3652				$match_this = $ignore_these_folders[$i];
3653				if (eregi("^.*$match_this$",$folder))
3654				{
3655					$we_care = False;
3656					break;
3657				}
3658			}
3659			return $we_care;
3660		}
3661
3662
3663
3664
3665	// ----  Password Crypto Workaround broken common->en/decrypt  -----
3666		/*!
3667		@function encrypt_email_passwd
3668		@abstract passes directly to crypto class
3669		@param $data data string to be encrypted
3670		@discussion  if mcrypt is not enabled, then the password data should be unmolested thru the
3671		crypto functions, i.e. do not alter the string if mcrypt will not be preformed on that string.
3672		*/
3673		function encrypt_email_passwd($data)
3674		{
3675			if(!is_object($this->crypto))
3676			{
3677				$cryptovars[0] = md5($GLOBALS['phpgw_info']['server']['encryptkey']);
3678				$cryptovars[1] = $GLOBALS['phpgw_info']['server']['mcrypt_iv'];
3679				$this->crypto = CreateObject('phpgwapi.crypto',$cryptovars);
3680			}
3681			return $this->crypto->encrypt($data);
3682		}
3683
3684		/*!
3685		@function decrypt_email_pass
3686		@abstract decrypt $data
3687		@param $data data to be decrypted
3688		@discussion  if mcrypt is not enabled, then the password data should be unmolested thru the
3689		crypto functions, i.e. do not alter the string if mcrypt will not be preformed on that string.
3690		*/
3691		function decrypt_email_passwd($data)
3692		{
3693			if(!is_object($this->crypto))
3694			{
3695				$cryptovars[0] = md5($GLOBALS['phpgw_info']['server']['encryptkey']);
3696				$cryptovars[1] = $GLOBALS['phpgw_info']['server']['mcrypt_iv'];
3697				$this->crypto = CreateObject('phpgwapi.crypto',$cryptovars);
3698			}
3699			$unencrypted = $this->crypto->decrypt($data);
3700			$encrypted = $this->crypto->encrypt($unencrypted);
3701			if($data <> $encrypted)
3702			{
3703				$unencrypted = $GLOBALS['phpgw']->crypto->decrypt($data);
3704				$encrypted = $GLOBALS['phpgw']->crypto->encrypt($unencrypted);
3705			}
3706			return $unencrypted;
3707		}
3708		/*
3709		// THIS CODE is depreciated, it was needed before the crypto in the API was fixed
3710		// However, the issue of screwed up, multiply serialized passwords from pre 0.9.12 is
3711		// perhaps still an issue, maybe this code will need to be used on rare occasions
3712		function decrypt_email_passwd($data)
3713		{
3714			if ($GLOBALS['phpgw_info']['server']['mcrypt_enabled'] && extension_loaded('mcrypt'))
3715			{
3716				$passwd = $data;
3717				// this will return a string that has:
3718				// (1) been decrypted with mcrypt (assuming mcrypt is enabled and working)
3719				// (2) had stripslashes applied and
3720				// (3) *MAY HAVE* been unserialized (ambiguous... see next comment)
3721				// correction Dec 14, 2001, (3) and definately was unserialized
3722				$cryptovars[0] = md5($GLOBALS['phpgw_info']['server']['encryptkey']);
3723				$cryptovars[1] = $GLOBALS['phpgw_info']['server']['mcrypt_iv'];
3724				$crypto = CreateObject('phpgwapi.crypto', $cryptovars);
3725				//$passwd = $crypto->decrypt($passwd);
3726				$passwd = $crypto->decrypt_mail_pass($passwd);
3727			}
3728			else
3729			{
3730				$passwd = $data;
3731				// ASSUMING set_magic_quotes_runtime(0) has been specified, then
3732				// there should be NO escape slashes coming from the database
3733				//if ($this->is_serialized($passwd))
3734				if ($this->is_serialized_str($passwd))
3735				{
3736					$passwd = unserialize($passwd);
3737				}
3738
3739
3740				// #### (begin) Upgrade Routine for 0.9.12 and earlier versions ####
3741				/*!
3742				@capability  Upgrade Routine for 0.9.12 and earlier Custom Passwords DEPRECIATED
3743				@discussion  the phpgw versions prior to and including 0.9.12 *may* have double or even tripple serialized
3744				passwd strings stored in their preferences table. SO:
3745				(1) check for this
3746				(2) unserialize to the real string
3747				(3) feed the unserialized / fixed passwd in the prefs class and save the "upgraded" passwd
3748				*/
3749
3750				/*
3751				// (1) check for this
3752				//$multi_serialized = $this->is_serialized($passwd);
3753				$multi_serialized = $this->is_serialized_str($passwd);
3754				if ($multi_serialized)
3755				{
3756					$pre_upgrade_passwd = $passwd;
3757					// (2) unserialize to the real string
3758					$failure = 10;
3759					$loop_num = 0;
3760					do
3761					{
3762						$loop_num++;
3763						if ($loop_num == $failure)
3764						{
3765							break;
3766						}
3767						$passwd = unserialize($passwd);
3768					}
3769					//while ($this->is_serialized($passwd));
3770					while ($this->is_serialized_str($passwd));
3771
3772					// 10 loops is too much, something is wrong
3773					if ($loop_num == $failure)
3774					{
3775						// screw it and continue as normal, user will need to reenter password
3776						echo 'ERROR: decrypt_email_passwd: custom pass upgrade procedure failed to restore passwd to useable state<br />';
3777						$passwd = $pre_upgrade_passwd;
3778					}
3779					else
3780					{
3781						// (3) SAVE THE FIXED / UPGRADED PASSWD TO PREFS
3782						// feed the unserialized / fixed passwd in the prefs class
3783						$GLOBALS['phpgw']->preferences->delete('email','passwd');
3784						// make any html quote entities back to real form (i.e. ' or ")
3785						$encrypted_passwd = $this->html_quotes_decode($passwd);
3786						// encrypt it as it would be as if the user had just submitted the preferences page (no need to strip slashes, no POST occured)
3787						$encrypted_passwd = $this->encrypt_email_passwd($passwd);
3788						// store in preferences so this does not happen again
3789						$GLOBALS['phpgw']->preferences->add('email','passwd',$encrypted_passwd);
3790						$GLOBALS['phpgw']->preferences->save_repository();
3791					}
3792				}
3793				// #### (end) Upgrade Routine for 0.9.12 and earlier versions ####
3794
3795				$passwd = $this->html_quotes_decode($passwd);
3796				//echo 'decrypt_email_passwd result: '.$passwd;
3797			}
3798			return $passwd;
3799		}
3800		*/
3801
3802		// ----  Make Address accoring to RFC2822 Standards  -----
3803		/*!
3804		@function  make_rfc2822_address
3805		@abstract Make Address accoring to RFC2822 Standards
3806		@param $addy_data object from php email address structure from IMAP_HEADER
3807		@param $html_encode boolean used to encode HTML offensive chars for display in a browser.
3808		@result string
3809		@author Angles
3810		@discussion will produce a string containing email addresses for (a) display a browser such as
3811		on the compose form, or (b) for use in an email mesage header, hence the standards
3812		compliance necessity, message headers must follow strict form.
3813		*/
3814		function make_rfc2822_address($addy_data, $html_encode=True)
3815		{
3816			//echo '<br />'.$this->htmlspecialchars_encode(serialize($addy_data)).'<br />'.'<br />';
3817
3818			if ((!isset($addy_data->mailbox)) && (!$addy_data->mailbox)
3819			&& (!isset($addy_data->host)) && (!$addy_data->host))
3820			{
3821				// fallback value, we do not want to sent a string like this "@" if no data if available
3822				return '';
3823			}
3824			// now we can continue, 1st make a simple, plain address
3825			// RFC2822 allows this simple form if not using "personal" info
3826			$rfc_addy = $addy_data->mailbox.'@'.$addy_data->host;
3827			// add "personal" data if it exists
3828			if (isset($addy_data->personal) && ($addy_data->personal))
3829			{
3830				// why DECODE when we are just going to feed it right back into a header?
3831				// answer - it looks crappy to have rfc2047 encoded personal info in the to: box
3832				$personal = $this->decode_header_string($addy_data->personal);
3833				// need to format according to RFC2822 spec for non-plain email address
3834				$rfc_addy = '"'.$personal.'" <'.$rfc_addy.'>';
3835				// if using this addy in an html page, we need to encode the ' " < > chars
3836				if ($html_encode)
3837				{
3838					$rfc_addy = $this->htmlspecialchars_encode($rfc_addy);
3839					//NOTE: in rfc_comma_sep we will decode any html entities back into these chars
3840				}
3841			}
3842			return $rfc_addy;
3843		}
3844
3845
3846		// ----  Make a To: string of comma seperated email addresses into an array structure  -----
3847		/*!
3848		@function make_rfc_addy_array
3849		@abstract  Make a typical string of "To " adresses into an array structure which easily manages all seperate elements of the addresses
3850		@param $data (string) should be a comma seperated string of email addresses (or just one email address) such as
3851		@discussion  To adress(es) in a string as we would get from a submitted compose form hold alot of useful information that
3852		can be extracted by sperating out the individual elements of the email address and, if many addresses are present, making an
3853		array of this data. This is especially useful during the send procedure, when we feed each individual address to the MTA,
3854		because the MTA expects a SIMPLE email address in the RCPT TO: command arg, not an address that contains any of the
3855		other things that an email address can contain, such as a users name (personal part) which itself can contain chars outside
3856		of the ASCII range  (whether ISO encoded or not) that the MTA does not need and does not want to see in the RCPT TO: commands.
3857		@example * * using html special chars so the inline doc parser will show the brackets and stuff * *
3858		-&gt;make_rfc_addy_array&#040;&#039;john&#064;doe.com,&quot;Php Group&quot;&lt;info&#064;phpgroupware.org&gt;,jerry&#064;example.com,&quot;joe john&quot; &lt;jj&#064;example.com&gt;&#039;&#041
3859		 which will be decomposed into an array of individual email addresses
3860		 where each numbered item will be like this this:
3861		 	array[x][&#039;personal&#039;]
3862		 	array[x][&#039;plain&#039;]
3863		 the above example would return this structure:
3864		 	array[0][&#039;personal&#039;] = &quot;&quot;
3865		 	array[0][&#039;plain&#039;] = &quot;john&#064;doe.com&quot;
3866		 	array[1][&#039;personal&#039;] = &quot;Php Group&quot;
3867		 	array[1][&#039;plain&#039;] = &quot;info&#064;phpgroupware.org&quot;
3868		 	array[2][&#039;personal&#039;] = &quot;&quot;
3869		 	array[2][&#039;plain&#039;] = &quot;jerry&#064;example.com&quot;
3870		 	array[3][&#039;personal&#039;] = &quot;joe john&quot;
3871		 	array[3][&#039;plain&#039;] = &quot;jj&#064;example.com&quot;
3872		@syntax ASCII example, inline docs will not show correctly
3873		 john@doe.com,"Php Group" <info@phpgroupware.org>,jerry@example.com,"joe john" <jj@example.com>  <br />
3874		 which will be decomposed into an array of individual email addresses <br />
3875		 where each numbered item will be like this this: <br />
3876		 	array[x]['personal']
3877		 	array[x]['plain']
3878		 the above example would return this structure:
3879		 	array[0]['personal'] = ""
3880		 	array[0]['plain'] = "john@doe.com"
3881		 	array[1]['personal'] = "Php Group"
3882		 	array[1]['plain'] = "info@phpgroupware.org"
3883		 	array[2]['personal'] = ""
3884		 	array[2]['plain'] = "jerry@example.com"
3885		 	array[3]['personal'] = "joe john"
3886		 	array[3]['plain'] = "jj@example.com"
3887		@author Angles
3888		*/
3889		function make_rfc_addy_array($data)
3890		{
3891			// if we are fed a null value, return nothing (i.e. a null value)
3892			if (isset($data))
3893			{
3894				$data = trim($data);
3895				// if we are fed a whitespace only string, return a blank string
3896				if ($data == '')
3897				{
3898					return $data;
3899					// return performs an implicit break, so we are outta here
3900				}
3901				// in some cases the data may be in html entity form
3902				// i.e. the compose page uses html entities when filling the To: box with a predefined value
3903				$data = $this->htmlspecialchars_decode($data);
3904				//reduce all multiple spaces to just one space
3905				//$data = ereg_replace("[' ']{2,20}", ' ', $data);
3906				$this_space = " ";
3907				$data = ereg_replace("$this_space{2,20}", " ", $data);
3908				// explode into an array of email addys
3909				//$data = explode(",", $data);
3910
3911
3912				// WORKAROUND - comma inside the "personal" part will incorrectly explode
3913				//$debug_explode = True;
3914				$debug_explode = False;
3915
3916				/*// === ATTEMPT 1 ====
3917				// replace any comma(s) INSIDE the "personal" part with this:  "C-O-M-M-A"
3918				echo 'PRE replace: '.$this->htmlspecialchars_encode($data).'<br />';
3919				$comma_replacement = "C_O_M_M_A";
3920				do
3921				{
3922					//$data = preg_replace('/(".*?)[,](.*?")/',"$1"."C_O_M_M_A"."$2", $data);
3923					//$data = preg_replace('/("[/001-/063,/065-/255]*?)[,]([/001-/063,/065-/255]*?")/',"$1"."$comma_replacement"."$2", $data);
3924					$data = preg_replace('/("(.(?!@))*?)[,]((.(?!@))*?")/',"$1"."$comma_replacement"."$3", $data);
3925				}
3926				while (preg_match('/("(.(?!@))*?)[,]((.(?!@))*?")/',$data));
3927				echo 'POST replace: '.$this->htmlspecialchars_encode($data).'<br />';
3928				//DEBUG
3929				return " ";
3930				// explode into an array of email addys
3931				//$data = explode(",", $data);
3932				*/
3933
3934				// === Explode Prep: STEP 1 ====
3935				// little is known about an email address at this point
3936				// what is known is that the following pattern should be present in ALL non-simple addy's
3937				// " <  (doublequote_space_lessThan)
3938				// so replace that with a known temp string
3939
3940				if ($debug_explode) { $this->dbug->out('[known sep] PRE replace: '.$this->htmlspecialchars_encode($data).'<br />'.'<br />'); }
3941				//$known_sep_item = "_SEP_COMPLEX_SEP_";
3942				// introduce some randomness to make accidental replacements less likely
3943				$sep_rand = $GLOBALS['phpgw']->common->randomstring(3);
3944				$known_sep_item = "_SEP_COMPLEX_".$sep_rand."_SEP_";
3945				$data = str_replace('" <',$known_sep_item,$data);
3946				if ($debug_explode) { $this->dbug->out('[known sep] POST replace: '.$this->htmlspecialchars_encode($data).'<br />'.'<br />'); }
3947
3948				// === Explode Prep: STEP 2 ====
3949				// now we know more
3950				// the area BETWEEN a " (doubleQuote) and the $known_sep_item is the "personal" part of the addy
3951				// replace any comma(s) in there with another known temp string
3952				if ($debug_explode) { $this->dbug->out('PRE replace: '.$this->htmlspecialchars_encode($data).'<br />'.'<br />'); }
3953				//$comma_replacement = "_C_O_M_M_A_";
3954				// introduce some randomness to make accidental replacements less likely
3955				$comma_rand = $GLOBALS['phpgw']->common->randomstring(3);
3956				$comma_replacement = "_C_O_M_".$comma_rand."_M_A_";
3957				//$data = preg_replace('/(".*?)[,](.*?'.$known_sep_item.')/',"$1"."$comma_replacement "."$2", $data);
3958				//$data = preg_replace('/(".*?)(?<!>)[,](.*?'.$known_sep_item.')/',"$1"."$comma_replacement"."$2", $data);
3959				do
3960				{
3961					$data = preg_replace('/("(.(?<!'.$known_sep_item.'))*?)[,](.*?'.$known_sep_item.')/',"$1"."$comma_replacement"."$3", $data);
3962				}
3963				while (preg_match('/("(.(?<!'.$known_sep_item.'))*?)[,](.*?'.$known_sep_item.')/',$data));
3964				if ($debug_explode) { $this->dbug->out('POST replace: '.$this->htmlspecialchars_encode($data).'<br />'.'<br />'); }
3965
3966				// Regex Pattern Explanation:
3967				//	openQuote_anythingExcept$known_sep_item_repeated0+times_NOT GREEDY
3968				//	_aComma_anything_repeated0+times_NOT GREEDY_$known_sep_item
3969				// syntax: "*?" is 0+ repetion symbol with the immediately following '?' being the Not Greedy modifier
3970				// NotGreedy: match as little as possible that still makes the pattern match
3971				// syntax: "?<!" is a "lookbehind negative assertion"
3972				// indicating that the ". *" can not contain anything EXCEPT the $known_sep_item string
3973				// lookbehind is necessary because this assertion applies to something BEFORE the thing (comma) we are trying to capture with the regex
3974				// Methodology:
3975				// (1) We need to specify NO $known_sep_item before the comma or else the regex will match
3976				// commas OUTSIDE of the intended "personal" part of the email addy, which are the
3977				// special commas that seperate email addresses in a comma seperated string
3978				// these special commas MUST NOT be altered
3979				// (2) this preg_replace will only replace ONE comma in the designated "personal" part
3980				// therefor we need a do ... while loop to keep running the preg_replace until all matches are replaced
3981				// the while statement is the SAME regex expression used in a preg_match function
3982
3983				// === Explode Prep: STEP 3 ====
3984				// UNDO the str_replace from STEP 1
3985				$data = str_replace($known_sep_item, '" <', $data);
3986				if ($debug_explode) { $this->dbug->out('UNDO Step 1: '.$this->htmlspecialchars_encode($data).'<br />'.'<br />'); }
3987
3988				// === ACTUAL EXPLODE ====
3989				// now the only comma(s) (if any) existing in $data *should* be the
3990				// special commas that seperate email addresses in a comma seperated string
3991				// with this as a (hopefully) KNOWN FACTOR - we can now EXPLODE by comma
3992				// thus: Explode into an array of email addys
3993				$data = explode(",", $data);
3994				if ($debug_explode) { $this->dbug->out('EXPLODED: '.$this->htmlspecialchars_encode(serialize($data)).'<br />'.'<br />'); }
3995
3996				// === POST EXPLODE  CLEANING====
3997				// explode occasionally produces empty elements in the resulting array, so
3998				// (1) eliminate any empty array elements
3999				// (2) UNDO the preg_replace from STEP 2 (add back the actual comma(s) in the "personal" part)
4000				$data_clean = Array();
4001				for ($i=0;$i<count($data);$i++)
4002				{
4003					// is there actual data in this array element
4004					if ((isset($data[$i])) && ($data[$i] != ''))
4005					{
4006						// OK, now undo the preg_replace from step 2 above
4007						$data[$i] = str_replace($comma_replacement, ',', $data[$i]);
4008						// add this to our $data_clean array
4009						$next_empty = count($data_clean);
4010						$data_clean[$next_empty] = $data[$i];
4011					}
4012				}
4013				if ($debug_explode) { $this->dbug->out('Cleaned Exploded Data: '.$this->htmlspecialchars_encode(serialize($data_clean)).'<br />'.'<br />'); }
4014
4015
4016				// --- Create Compund Array Structure To Hold Decomposed Addresses -----
4017				// addy_array is a simple numbered array, each element is a addr_spec_array
4018				$addy_array = Array();
4019				// $addr_spec_array has this structure:
4020				//  addr_spec_array['plain']
4021				//  addr_spec_array['personal']
4022
4023				// decompose addy's into that array, and format according to rfc specs
4024				for ($i=0;$i<count($data_clean);$i++)
4025				{
4026					// trim off leading and trailing whitespaces and \r and \n
4027					$data_clean[$i] = trim($data_clean[$i]);
4028					// is this a rfc 2822 compound address (not a simple one)
4029					if (strstr($data_clean[$i], '" <'))
4030					{
4031						// SEPERATE "personal" part from the <x@x.com> part
4032						$addr_spec_parts = explode('" <', $data_clean[$i]);
4033						// that got rid of the closing " in personal, now get rig of the first "
4034						$addy_array[$i]['personal'] = substr($addr_spec_parts[0], 1);
4035						//  the "<" was already removed, , NOW remove the closing ">"
4036						$grab_to = strlen($addr_spec_parts[1]) - 1;
4037						$addy_array[$i]['plain'] = substr($addr_spec_parts[1], 0, $grab_to);
4038
4039						// QPRINT NON US-ASCII CHARS in "personal" string, as per RFC2047
4040						// the actual "plain" address may NOT have any other than US-ASCII chars, as per rfc2822
4041						$addy_array[$i]['personal'] = $this->encode_header($addy_array[$i]['personal']);
4042
4043						// REVISION: rfc2047 says the following escaping technique is not much help
4044						// use the encoding above instead
4045						/*
4046						// ESCAPE SPECIALS:  rfc2822 requires the "personal" comment string to escape "specials" inside the quotes
4047						// the non-simple (i.e. "personal" info is included) need special escaping
4048						// escape these:  ' " ( )
4049						$addy_array[$i]['personal'] = ereg_replace('\'', "\\'", $addy_array[$i]['personal']);
4050						$addy_array[$i]['personal'] = str_replace('"', '\"', $addy_array[$i]['personal']);
4051						$addy_array[$i]['personal'] = str_replace("(", "\(", $addy_array[$i]['personal']);
4052						$addy_array[$i]['personal'] = str_replace(")", "\)", $addy_array[$i]['personal']);
4053						*/
4054					}
4055					else
4056					{
4057						// this is an old style simple address
4058						$addy_array[$i]['personal'] = '';
4059						$addy_array[$i]['plain'] = $data_clean[$i];
4060					}
4061
4062					//echo 'addy_array['.$i.'][personal]: '.$this->htmlspecialchars_encode($addy_array[$i]['personal']).'<br />';
4063					//echo 'addy_array['.$i.'][plain]: '.$this->htmlspecialchars_encode($addy_array[$i]['plain']).'<br />';
4064				}
4065				if ($debug_explode) { $this->dbug->out('FINAL processed addy_array:<br />'.$this->htmlspecialchars_encode(serialize($addy_array)).'<br />'.'<br />'); }
4066				return $addy_array;
4067			}
4068		}
4069
4070		// takes an array generated by "make_rfc_addy_array()" and makes it into a string
4071		// ytpically used to make to and from headers, etc...
4072		/*!
4073		@function addy_array_to_str
4074		@param $data array of email addresses from the function make_rfc_addy_array
4075		@param $include_personal boolean whether to return simple email address or to also include the personal part
4076		@result string
4077		@author Angles
4078		@discussion this is the opposite of the function make_rfc_addy_array
4079		*/
4080		function addy_array_to_str($data, $include_personal=True)
4081		{
4082			$addy_string = '';
4083
4084			// reconstruct data in the correct email address format
4085			//if (count($data) == 0)
4086			//{
4087			//	$addy_string = '';
4088			//}
4089			if (count($data) == 1)
4090			{
4091				if (($include_personal == False) || (strlen(trim($data[0]['personal'])) < 1))
4092				{
4093					$addy_string = trim($data[0]['plain']);
4094				}
4095				else
4096				{
4097					$addy_string = '"'.trim($data[0]['personal']).'" <'.trim($data[0]['plain']).'>';
4098				}
4099			}
4100			elseif ($include_personal == False)
4101			{
4102				// CLASS SEND CAN NOT HANDLE FOLDED HEADERS OR PERSONAL ADDRESSES
4103				// (UPDATE - now it can)
4104				// this snippit just assembles the headers
4105				for ($i=0;$i<count($data);$i++)
4106				{
4107					// addresses should be seperated by one comma with NO SPACES AT ALL
4108					$addy_string = $addy_string .trim($data[$i]['plain']) .',';
4109				}
4110				// catch any situations where a blank string was included, resulting in two commas with nothing inbetween
4111				$addy_string = ereg_replace("[,]{2}", ',', $addy_string);
4112				// trim again, strlen needs to be accurate without trailing spaces included
4113				$addy_string = trim($addy_string);
4114				// eliminate that final comma
4115				$grab_to = strlen($addy_string) - 1;
4116				$addy_string = substr($addy_string, 0, $grab_to);
4117			}
4118			else
4119			{
4120				// if folding headers - use SEND_2822  instead of class.send
4121				// FRC2822 recommended max header line length, excluding the required CRLF
4122				$rfc_max_length = 78;
4123
4124				// establish an arrays in case we need a multiline header string
4125				$header_lines = Array();
4126				$line_num = 0;
4127				$header_lines[$line_num] = '';
4128				// loop thru the addresses, construct the header string
4129				for ($z=0;$z<count($data);$z++)
4130				{
4131					// make a string for this individual address
4132					if (trim($data[$z]['personal']) != '')
4133					{
4134						$this_address = '"'.trim($data[$z]['personal']).'" <'.trim($data[$z]['plain']).'>';
4135					}
4136					else
4137					{
4138						$this_address = trim($data[$z]['plain']);
4139					}
4140					// see how long this line would be if this address were added
4141					//if ($z == 0)
4142					$cur_len = strlen($header_lines[$line_num]);
4143					if ($cur_len < 1)
4144					{
4145						$would_be_str = $this_address;
4146					}
4147					else
4148					{
4149						$would_be_str = $header_lines[$line_num] .','.$this_address;
4150					}
4151					//echo 'would_be_str: '.$this->htmlspecialchars_encode($would_be_str).'<br />';
4152					//echo 'strlen(would_be_str): '.strlen($would_be_str).'<br />';
4153					if ((strlen($would_be_str) > $rfc_max_length)
4154					&& ($cur_len > 1))
4155					{
4156						// Fold Header: RFC2822 "fold" = CRLF followed by a "whitespace" (#9 or #32)
4157						// preferable to "fold" after the comma, and DO NOT TRIM that white space, preserve it
4158						//$whitespace = " ";
4159						$whitespace = chr(9);
4160						$header_lines[$line_num] = $header_lines[$line_num].','."\r\n";
4161						// advance to the next line
4162						$line_num++;
4163						// now start the new line with the "folding whitespace" then the address
4164						$header_lines[$line_num] = $whitespace .$this_address;
4165					}
4166					else
4167					{
4168						// simply comma sep the items (as we did when making "would_be_str")
4169						$header_lines[$line_num] = $would_be_str;
4170					}
4171				}
4172				// assemble $header_lines array into a single string
4173				$addy_string = '';
4174				for ($x=0;$x<count($header_lines);$x++)
4175				{
4176					$addy_string = $addy_string .$header_lines[$x];
4177				}
4178				$addy_string = trim($addy_string);
4179			}
4180			// data leaves here with NO FINAL (trailing) CRLF - will add that later
4181			return $addy_string;
4182		}
4183
4184		// ----  Ensure CR and LF are always together, RFCs prefer the CRLF combo  -----
4185		/*!
4186		@function normalize_crlf
4187		@abstract Ensure CR and LF are always together, RFCs prefer the CRLF combo
4188		@param $data string
4189		@result string
4190		@author Angles
4191		@discussion ?
4192		*/
4193		function normalize_crlf($data)
4194		{
4195			// this is to catch all plain \n instances and replace them with \r\n.
4196			$data = ereg_replace("\r\n", "\n", $data);
4197			$data = ereg_replace("\r", "\n", $data);
4198			$data = ereg_replace("\n", "\r\n", $data);
4199
4200			//$data = preg_replace("/(?<!\r)\n/m", "\r\n", $data);
4201			//$data = preg_replace("/\r(?!\n)/m", "\r\n", $data);
4202			return $data;
4203		}
4204
4205		// ----  Explode by Linebreak, ANY kind of line break  -----
4206		/*!
4207		@function explode_linebreaks
4208		@abstract Explode by Linebreak, ANY kind of line break
4209		@param $data string
4210		@result string
4211		@author Angles
4212		@discussion ?
4213		*/
4214		function explode_linebreaks($data)
4215		{
4216			$data = preg_split("/\r\n|\r(?!\n)|(?<!\r)\n/m",$data);
4217			// match \r\n, OR \r with no \n after it , OR /n with no /r before it
4218			// modifier m = multiline
4219			return $data;
4220		}
4221
4222		// ----  Create a Unique Mime Boundary  -----
4223		/*!
4224		@function make_boundary
4225		@abstract Create a Unique Mime Boundary
4226		@param $part_length int ?
4227		@result string
4228		@author Angles
4229		@discussion Users some randomization and RFC standards to make a MIME part seperator.
4230		*/
4231		function make_boundary($part_length=4)
4232		{
4233			$part_length = (int)$part_length;
4234
4235			$rand_stuff = Array();
4236			$rand_stuff[0]['length'] = $part_length;
4237			$rand_stuff[0]['string'] = $GLOBALS['phpgw']->common->randomstring($rand_stuff[0]['length']);
4238			$rand_stuff[0]['rand_numbers'] = '';
4239			for ($i = 0; $i < $rand_stuff[0]['length']; $i++)
4240			{
4241				if ((ord($rand_stuff[0]['string'][$i]) > 47)
4242				&& (ord($rand_stuff[0]['string'][$i]) < 58))
4243				{
4244					// this char is already a digit
4245					$rand_stuff[0]['rand_numbers'] .= $rand_stuff[0]['string'][$i];
4246				}
4247				else
4248				{
4249					// turn this into number form, based on this char's ASCII value
4250					$rand_stuff[0]['rand_numbers'] .= ord($rand_stuff[0]['string'][$i]);
4251				}
4252			}
4253			$rand_stuff[1]['length'] = $part_length;
4254			$rand_stuff[1]['string'] = $GLOBALS['phpgw']->common->randomstring($rand_stuff[1]['length']);
4255			$rand_stuff[1]['rand_numbers'] = '';
4256			for ($i = 0; $i < $rand_stuff[1]['length']; $i++)
4257			{
4258				if ((ord($rand_stuff[1]['string'][$i]) > 47)
4259				&& (ord($rand_stuff[1]['string'][$i]) < 58))
4260				{
4261					// this char is already a digit
4262					$rand_stuff[1]['rand_numbers'] .= $rand_stuff[1]['string'][$i];
4263				}
4264				else
4265				{
4266					// turn this into number form, based on this char's ASCII value
4267					$rand_stuff[1]['rand_numbers'] .= ord($rand_stuff[1]['string'][$i]);
4268				}
4269			}
4270			$unique_boundary = '---=_Next_Part_'.$rand_stuff[0]['rand_numbers'].'_'.$GLOBALS['phpgw']->common->randomstring($part_length)
4271				.'_'.$GLOBALS['phpgw']->common->randomstring($part_length).'_'.$rand_stuff[1]['rand_numbers'];
4272
4273			return $unique_boundary;
4274		}
4275
4276		// ----  Create a Unique RFC2822 Message ID  -----
4277		/*!
4278		@function make_message_id
4279		@abstract Create a Unique RFC2822 Message ID
4280		@result string
4281		@author Angles
4282		@discussion Users some randomization and some datetime elements to produce an RFC compliant MessageID header string.
4283		*/
4284		function make_message_id()
4285		{
4286			if ($GLOBALS['phpgw_info']['server']['hostname'] != '')
4287			{
4288				$id_suffix = $GLOBALS['phpgw_info']['server']['hostname'];
4289			}
4290			else
4291			{
4292				$id_suffix = $GLOBALS['phpgw']->common->randomstring(3).'local';
4293			}
4294			// gives you timezone dot microseconds space datetime
4295			$stamp = microtime();
4296			$stamp = explode(" ",$stamp);
4297			// get rid of tomezone info
4298			$grab_from = strpos($stamp[0], ".") + 1;
4299			$stamp[0] = substr($stamp[0], $grab_from);
4300			// formay the datetime into YYYYMMDD
4301			$stamp[1] = date('Ymd', $stamp[1]);
4302			// a small random string for the middle
4303			$rand_middle = $GLOBALS['phpgw']->common->randomstring(3);
4304
4305			$mess_id = '<'.$stamp[1].'.'.$rand_middle.'.'.$stamp[0].'@'.$id_suffix.'>';
4306			return $mess_id;
4307		}
4308
4309		/*!
4310		@function make_flags_str
4311		@abstract ConvertPHP type message header IMAP flag data into string data that the IMAP server understands.
4312		@result string
4313		@author Angles
4314		@discussion for use in an IMAP_APPEND command, and anytime you need to give the IMAP server Flag information
4315		ina format that it understands, as opposed to the PHP object which is not understandable to an  IMAP server. ALSO
4316		can be used in string matching functions to verify if a message has a certain flag.
4317		@example example of verifing a message has been replied to
4318		if (strisr('Answered'), $flags_str) then (show replied flag)
4319		*/
4320		function make_flags_str($hdr_envelope='')
4321		{
4322			/*
4323			// --- Message Flags ---
4324			var $Recent = '';		//  'R' if recent and seen, 'N' if recent and not seen, ' ' if not recent
4325			var $Unseen = '';		//  'U' if not seen AND not recent, ' ' if seen OR not seen and recent
4326			var $Answered = '';	//  'A' if answered, ' ' if unanswered
4327			var $Deleted = '';		//  'D' if deleted, ' ' if not deleted
4328			var $Draft = '';		//  'X' if draft, ' ' if not draft
4329			var $Flagged = '';		//  'F' if flagged, ' ' if not flagged
4330			*/
4331			/*  == RFC2060: ==
4332			\Seen				Message has been read
4333			\Answered		Message has been answered
4334			\Flagged			Message is "flagged" for urgent/special attention
4335			\Deleted			Message is "deleted" for removal by later EXPUNGE
4336			\Draft				Message has not completed composition (marked as a draft).
4337			\Recent			Message is "recently" arrived in this mailbox. (long story...
4338			Note: The \Recent system flag is a special case of a
4339			session flag.  \Recent can not be used as an argument in a
4340			STORE command, and thus can not be changed at all.
4341			*/
4342			if ($hdr_envelope == '')
4343			{
4344				return 'ERROR';
4345			}
4346			$flags_str = '';
4347			$flags_array = array();
4348			if (($hdr_envelope->Recent != 'N') && ($hdr_envelope->Unseen != 'U'))
4349			{
4350				$flags_str .= "\\Seen ";
4351			}
4352			if ((isset($hdr_envelope->Answered))
4353			&& ($hdr_envelope->Answered == 'A'))
4354			{
4355				$flags_str .= "\\Answered ";
4356			}
4357			if ((isset($hdr_envelope->Flagged))
4358			&& ($hdr_envelope->Flagged == 'F'))
4359			{
4360				$flags_str .= "\\Flagged ";
4361			}
4362			if ((isset($hdr_envelope->Deleted))
4363			&& ($hdr_envelope->Deleted == 'D'))
4364			{
4365				$flags_str .= "\\Deleted ";
4366			}
4367			if ((isset($hdr_envelope->Draft))
4368			&& ($hdr_envelope->Draft == 'X'))
4369			{
4370				$flags_str .= "\\Draft ";
4371			}
4372			// Recent is not settable in an append, so forge about it
4373			return trim($flags_str);
4374		}
4375
4376	  // ----  HTML - Related Utility Functions   -----
4377		/*!
4378		@function qprint
4379		@abstract Decode quoted-printable encoded text to ASCII
4380		@result string
4381		@discussion This function originally did 2 extra things
4382		before using the php "quoted_printable_decode" command.  First, it would
4383		change any underscores "_" to a space, (NOW commented out) and
4384		second, it would change the sequence "=CRLF" to nothing,
4385		in other words erasing that, BECAUSE php "quoted_printable_decode" does not
4386		correctly handle the "qprint line folding" whereby lines of length longer than
4387		76 chars are terminated with a "=CRLF" and continue on the next line,
4388		BUT the php function "imap_qprint" DOES HANDLE IT correctly.
4389		Note that function "imap_qprint" is part of the IMAP module , these 2 php
4390		functions should do the same thing except that "quoted_printable_decode" does
4391		not require the IMAP module compiled into php BUT does require replacing
4392		=CRLF with empty string to work.
4393		@author previous authors, Angles
4394		*/
4395		function qprint($string)
4396		{
4397			if (function_exists('imap_qprint'))
4398			{
4399				return imap_qprint($string);
4400			}
4401			else
4402			{
4403				////$string = str_replace("_", " ", $string);
4404				$string = str_replace("=\r\n","",$string);
4405				return quoted_printable_decode($string);
4406			}
4407		}
4408
4409
4410		// ----  RFC Header Decoding  -----
4411		/*!
4412		@function decode_rfc_header_glob
4413		@abstract feed "decode_rfc_header" one line at a time.
4414		@author Angles
4415		@discussion split multi line data into single line strings for processing by "decode_rfc_header" one line at a time.
4416		Currently supports array data or a large string with CRLF pairs that can be exploded into an array.
4417		Not handled is an array with glob data as its elements. Feed this function reasonable simple data structures.
4418		*/
4419		function decode_rfc_header_glob($data)
4420		{
4421			//$debug_me = 2;
4422			$debug_me = 0;
4423
4424			if ($debug_me > 0) { $this->dbug->out('mail_msg_base: decode_rfc_header_glob: ENTERING <br />'); }
4425			if ($debug_me > 2) { $this->dbug->out('mail_msg_base: decode_rfc_header_glob: ENTERING $data DUMP:', $data); }
4426			// multiline glob needs to be an array
4427			if (!is_array($data))
4428			{
4429				if ($debug_me > 1) { $this->dbug->out('mail_msg_base: decode_rfc_header_glob: $data is NOT an array, strlen = ['.strlen($data).'] <br />'); }
4430				$data_was_array = False;
4431				if (stristr($data, "\r\n"))
4432				{
4433					$array_data = array();
4434					$array_data = $this->explode_linebreaks($data);
4435				}
4436				else
4437				{
4438					// maybe a single line slipped in here
4439					$array_data = array();
4440					$array_data[0] = $data;
4441				}
4442			}
4443			else
4444			{
4445				if ($debug_me > 1) { $this->dbug->out('mail_msg_base: decode_rfc_header_glob: $data is array, count = ['.count($data).'] <br />'); }
4446				$data_was_array = True;
4447			}
4448
4449			// so now we KNOW we have an array, right?
4450			// decode its elements
4451			$return_data = array();
4452			$loops = count($array_data);
4453			for ($i = 0; $i < $loops; $i++)
4454			{
4455				$return_data[$i] = $this->decode_rfc_header($array_data[$i]);
4456			}
4457
4458			// put data back into its original form and return it
4459			if ($data_was_array == True)
4460			{
4461				if ($debug_me > 2) { $this->dbug->out('mail_msg_base: decode_rfc_header_glob: $return_data DUMP:', $return_data); }
4462				if ($debug_me > 0) { $this->dbug->out('mail_msg_base: decode_rfc_header_glob: LEAVING, $data_was_array was ['.serialize($data_was_array).'] <br />'); }
4463				return $return_data;
4464			}
4465			else
4466			{
4467				$my_glob = '';
4468				$my_glob = implode("\r\n", $return_data);
4469				if ($debug_me > 2) { $this->dbug->out('mail_msg_base: decode_rfc_header_glob: $my_glob DUMP:', $my_glob); }
4470				if ($debug_me > 0) { $this->dbug->out('mail_msg_base: decode_rfc_header_glob: LEAVING, $data_was_array was ['.serialize($data_was_array).'] <br />'); }
4471				return $my_glob;
4472			}
4473		}
4474
4475		/*!
4476		@function decode_rfc_header
4477		@abstract Email header must have chars within US-ASCII limits, any other chars must be encoded, this function DECODES said text.
4478		@result string
4479		@author Angles
4480		@discussion Email header must have chars within US-ASCII limits, any other chars must
4481		be encoded according to RFC2822 spec, this function DECODES said chars, usually one word is encoded individually.
4482		Uses regex to handle either base64 or quoted-printable encoded email headers,
4483		does not care about the specified charset, just decodes based on Q or B encoding key.
4484		@syntax Non-us-ascii chars in email headers MUST be encoded using the special format
4485		=?charset?Q?word?=
4486		=?charset?B?word?=
4487		currently only qprint and base64 encoding is specified by RFCs, represented by the Q or B,
4488		which can be upper OR lower case.
4489		*/
4490		function decode_rfc_header($data)
4491		{
4492			// SAME FUNCTIONALITY as decode_header_string()  (but Faster, hopefully)
4493			// non-us-ascii chars in email headers MUST be encoded using the special format:
4494			//  =?charset?Q?word?=
4495			// currently only qprint and base64 encoding is specified by RFCs
4496			if (ereg("=\?.*\?(Q|q)\?.*\?=", $data))
4497			{
4498				$data = ereg_replace("=\?.*\?(Q|q)\?", '', $data);
4499				$data = ereg_replace("\?=", '', $data);
4500				$data = $this->qprint(str_replace("_"," ",$data));
4501			}
4502			if (ereg("=\?.*\?(B|b)\?.*\?=", $data))
4503			{
4504				$data = ereg_replace("=\?.*\?(B|b)\?", '', $data);
4505				$data = ereg_replace("\?=", '', $data);
4506				$data = urldecode(base64_decode($data));
4507			}
4508			return $data;
4509		}
4510
4511
4512		// non-us-ascii chars in email headers MUST be encoded using the special format:
4513		//  =?charset?Q?word?=
4514		// commonly:
4515		// =?iso-8859-1?Q?encoded_word?=
4516		// currently only qprint and base64 encoding is specified by RFCs
4517		/*!
4518		@function decode_header_string
4519		@abstract Email header must have chars within US-ASCII limits, any other chars must be encoded, this function DECODES said text.
4520		@result string
4521		@author previous authors
4522		@discussion same capability as function decode_rfc_header but does not use any regex, but
4523		also needs to be tweaked to not be b0rked by the various charset encoding strings which increasingly
4524		do not follow the predictable pattern this function is used to seeing.
4525		@syntax This finction is tuned for this encoding
4526		=?iso-8859-1?Q?encoded_word?=
4527		=?iso-8859-1?B?encoded_word?=
4528		it needs some tweaking to handle the different lengths of various modern encoding type descriptors, like
4529		=?UTF-8?Q?encoded_word?=
4530		=?iso-8859-15?Q?encoded_word?=
4531		=?GB2312?Q?encoded_word?=
4532		=?big5?Q?encoded_word?=
4533		NOTE that Q and B are both supported, it is the length of the charset descriptor that is an issue.
4534		*/
4535		function decode_header_string($string)
4536		{
4537			//return $this->decode_rfc_header($string);
4538			return $this->decode_header_string_orig($string);
4539		}
4540		function decode_header_string_orig($string)
4541		{
4542			if($string)
4543			{
4544				$pos = strpos($string,"=?");
4545				if(!is_int($pos))
4546				{
4547					return $string;
4548				}
4549				// save any preceding text
4550				$preceding = substr($string,0,$pos);
4551				$end = strlen($string);
4552				// the mime header spec says this is the longest a single encoded word can be
4553				$search = substr($string,$pos+2,$end - $pos - 2 );
4554				$d1 = strpos($search,"?");
4555				if(!is_int($d1))
4556				{
4557					return $string;
4558				}
4559				$charset = strtolower(substr($string,$pos+2,$d1));
4560				$search = substr($search,$d1+1);
4561				$d2 = strpos($search,"?");
4562				if(!is_int($d2))
4563				{
4564					return $string;
4565				}
4566				$encoding = substr($search,0,$d2);
4567				$search = substr($search,$d2+1);
4568				$end = strpos($search,"?=");
4569				if(!is_int($end))
4570				{
4571					return $string;
4572				}
4573				$encoded_text = substr($search,0,$end);
4574				$rest = substr($string,(strlen($preceding.$charset.$encoding.$encoded_text)+6));
4575				if(strtoupper($encoding) == "Q")
4576				{
4577					$decoded = $this->qprint(str_replace("_"," ",$encoded_text));
4578				}
4579				if (strtoupper($encoding) == "B")
4580				{
4581					$decoded = urldecode(base64_decode($encoded_text));
4582				}
4583				return $preceding . $decoded . $this->decode_header_string($rest);
4584			}
4585			else
4586			{
4587				return $string;
4588			}
4589		}
4590
4591		/*!
4592		@function encode_iso88591_word
4593		@abstract Private utility function used by encode_header to encode non US-ASCII text in email headers
4594		@param $string
4595		@author Angles
4596		@discussion SUB-FUNCTION - do not call directly, used by function encode_header() SPEED NOTE
4597		this function does not use preg yet does a similar test as the only function that directly calls this
4598		function, perhaps this could be used to reduce the preg matching in function "encode_header" that calls this.
4599		@access private
4600		*/
4601		function encode_iso88591_word($string)
4602		{
4603			$qprint_prefix = '=?iso-8859-1?Q?';
4604			$qprint_suffix = '?=';
4605			$new_str = '';
4606			$did_encode = False;
4607
4608			for( $i = 0 ; $i < strlen($string) ; $i++ )
4609			{
4610				$val = ord($string[$i]);
4611				// my interpetation of what to encode from RFC2045 and RFC2822
4612				if ( (($val >= 1) && ($val <= 31))
4613				|| (($val >= 33) && ($val <= 47))
4614				|| ($val == 61)
4615				|| ($val == 62)
4616				|| ($val == 64)
4617				|| (($val >= 91) && ($val <= 94))
4618				|| ($val == 96)
4619				|| ($val >= 123))
4620				{
4621					$did_encode = True;
4622					//echo 'val needs encode: '.$val.'<br />';
4623					$val = dechex($val);
4624					// rfc2045 requires quote printable HEX letters to be uppercase
4625					$val = strtoupper($val);
4626					//echo 'val AFTER encode: '.$val.'<br />';
4627					//$text .= '='.$val;
4628					$new_str = $new_str .'='.$val;
4629				}
4630				else
4631				{
4632					$new_str = $new_str . $string[$i];
4633				}
4634			}
4635			if ($did_encode)
4636			{
4637				$new_str =  $qprint_prefix .$new_str .$qprint_suffix;
4638			}
4639			return $new_str;
4640		}
4641
4642		// encode email headers as per rfc2047, non US-ASCII chars in email headers
4643		// basic idea is to qprint any word with "header-unfriendly" chars in it
4644		// then surround that qprinted word with the stuff specified in rfc2047
4645		// Example:
4646		//  "my //name\\ {iS} L@@T" <leet@email.com>
4647		// that email address has "header-unfriendly" chars in it
4648		// this function would encode it suitable for email transport
4649		/*!
4650		@function encode_header
4651		@abstract Encode non US-ASCII text in email headers, takes a line of text at a time
4652		@param $string (string) one like on email headers
4653		@author Angles
4654		@result string only encoded if needed, else returns the same string given as the $string param.
4655		@discussion encode email headers as per rfc2047, non US-ASCII chars in email headers.
4656		Basic idea is to qprint any word with "header-unfriendly" chars in it, but note that base64 encoding
4657		is n alternative way to do the same thing. This function uses quoted-printable method for simplicity.
4658		The encoded word must be surrouned with the specific syntax outlined in rfc2047.
4659		NOTE text is only encoded if necessary, otherwise this function simply returns the same
4660		text it was givin as the $string param, if no encoding is needed.
4661		NOTE this encoding is typically found in the subject, from, to, or cc headers, other email
4662		headers should have no need to use text outside US-ASCII, since email headers are highly
4663		typed strings strictly defined in relevant RFCs.
4664		NOTE the chars encoded by this function are my (Angles) interpetation of what to encode
4665		from RFC2045, RFC2047, and RFC2822 because all these chars seem to cause trouble,
4666		so they are encoded here, BUT I have seen email headers with some of these chars
4667		not encoded so it is possible that this function encoded a broader range of chars than
4668		is actually necessary. NOTE also that any email client that can decode email headers,
4669		and they all must, can decoded ANY encoded text as long as the encoding syntax is correct.
4670		So even IF this function encoded more chars than necessary, there is no problem with that.
4671		Email clients will still decode the headers into the intended text.
4672		SPEED NOTE notice that there is a loop thru every letter of a string, where every letter
4673		is subject to a complicated preg test, could the preg test be simplified, or is this even a
4674		speed issue at all?
4675		@syntax This email address has "header-unfriendly" chars in it,
4676		"my //name\\ {iS} L@@T" <leet@email.com>
4677		same example for the inline doc parser
4678		&quot;my &#047;&#047;name&#092;&#092; &#123;iS&#125; L@@T&quot; &lt;leet@email.com&gt;
4679		this function would encode it suitable for email transport
4680		*/
4681		function encode_header($data)
4682		{
4683			// explode string into an array or words
4684			$words = explode(' ', $data);
4685
4686			for($i=0; $i<count($words); $i++)
4687			{
4688				//echo 'words['.$i.'] in loop: '.$words[$i].'<br />';
4689
4690				// my interpetation of what to encode from RFC2045, RFC2047, and RFC2822
4691				// all these chars seem to cause trouble, so encode them
4692				if (preg_match('/'
4693					. '['.chr(1).'-'.chr(31).']'
4694					. '['.chr(33).'-'.chr(38).']'
4695					.'|[\\'.chr(39).']'
4696					.'|['.chr(40).'-'.chr(46).']'
4697					.'|[\\'.chr(47).']'
4698					.'|['.chr(61).'-'.chr(62).']'
4699					.'|['.chr(64).']'
4700					.'|['.chr(91).'-'.chr(94).']'
4701					.'|['.chr(96).']'
4702					.'|['.chr(123).'-'.chr(255).']'
4703					.'/', $words[$i]))
4704				{
4705					/*
4706					// qprint this word, and add rfc2047 header special words
4707					$len_before = strlen($words[$i]);
4708					echo 'words['.$i.'] needs encode: '.$words[$i].'<br />';
4709					$words[$i] = imap_8bit($words[$i]);
4710					echo 'words['.$i.'] AFTER encode: '.$words[$i].'<br />';
4711					// php may not encode everything that I expect, so check to see if encoding happened
4712					$len_after = strlen($words[$i]);
4713					if ($len_before != $len_after)
4714					{
4715						// indeed, encoding did happen, add rfc2047 header special words
4716						$words[$i] = $qprint_prefix .$words[$i] .$qprint_suffix;
4717					}
4718					*/
4719
4720					// qprint this word, and add rfc2047 header special words
4721					//echo 'words['.$i.'] needs encode: '.$words[$i].'<br />';
4722					$words[$i] = $this->encode_iso88591_word($words[$i]);
4723					//echo 'words['.$i.'] AFTER encode: '.$words[$i].'<br />';
4724				}
4725			}
4726
4727			// reassemble the string
4728			$encoded_str = implode(' ',$words);
4729			return $encoded_str;
4730		}
4731
4732		/*!
4733		@function needs_utf7_encoding TESTING
4734		@abstract if string has char 38, ampersand, or char including and greater than 127, then returns true.
4735		@param $string
4736		@author Angles
4737		@discussion This can be used to test if a foldername may need utf7 encoding, IT DOES NOT DO
4738		ANY ENCODING, it just tests if such encoding would be necessary under the rules of RFC2060
4739		sect 5.1.3 concerning "modified UTF7" mailbox encodings.  DELETE THIS FUNCTION IF THIS PROVES USELESS.
4740		*/
4741		function needs_utf7_encoding($string)
4742		{
4743			for( $i = 0 ; $i < strlen($string) ; $i++ )
4744			{
4745				$val = ord($string[$i]);
4746				if (($val == 37)
4747				|| ($val >= 127))
4748				{
4749					return True;
4750				}
4751			}
4752		}
4753
4754		/*!
4755		@function needs_utf7_decoding TESTING
4756		@abstract if string has a pattern associated with rfc2060 utf7 encodings, we guess it would need decoding
4757		@param $string
4758		@author Angles
4759		@discussion This can be used to test if a foldername COMING FROM A MAILSERVER may need utf7
4760		decoding, as the mailserver will store the foldername in encoded form and the MUA (us) must decode if
4761		necessary. This function DOES NOT DO ANY DECODING, it just tests if such decoding would be necessary
4762		under the rules of RFC2060 sect 5.1.3 concerning "modified UTF7" mailbox encodings. The pattern we look for
4763		is a string which is a foldername (string inside any delimiter slash or dot) and which begins with an
4764		ampersand and ends with a dash. Note that the delimiter slash or dot can break up such patterns. Such
4765		as NAMESPACE_peter_DELIMITER_mail_DELIMITER_&ZeVnLIqe-_DELIMITER_&U,BTFw- where
4766		the _DELIMITER_ would typically be a slash or a dot. Since we want to use this function without having
4767		to know or care about what the delimiter is, we use preg match an make an educated guess, because the
4768		ampersand need not be at the beginning at the name nor does the dash have to be at the end of the name.
4769		DELETE THIS FUNCTION IF THIS PROVES USELESS.
4770		*/
4771		function needs_utf7_decoding($string)
4772		{
4773			// ~peter/mail/&ZeVnLIqe-/&U,BTFw-
4774			// mail/Re&5w-ur&6Q-p&4A-ce
4775			// mail/Pe&3w-e&9g-n
4776			preg_match('/&.*[-]/',$string);
4777		}
4778
4779		// PHP "htmpspecialchars" is unreliable sometimes, and does not encode single quotes (unless told to)
4780		// this is a somewhat more reliable version of that PHP function
4781		// with a corresponding 'decode' function below it
4782		/*!
4783		@function htmlspecialchars_encode
4784		@abstract a more reliable version of php function htmpspecialchars
4785		@param $str (string) of plain text
4786		@author Angles
4787		@result string where the most html sensitive chars have been converted to htmlspecialchars.
4788		@discussion PHP "htmpspecialchars" is unreliable sometimes, and does not encode
4789		single quotes unless specifically told to, this is a somewhat more reliable version of
4790		that PHP function with a corresponding decode function also provided. These are
4791		chars that are common in html markup that, when not used as markup, should be
4792		encoded into something else so as not to be interpeted as markup.
4793		@syntax Currently the these chars will be encoded
4794		& , " , ' , > , < ,
4795		repeat for the inline doc parser
4796		&amp; , &quot; , &#039; , &lt; , &gt;
4797		*/
4798		function htmlspecialchars_encode($str)
4799		{
4800			/*// replace  '  and  "  with htmlspecialchars */
4801			$str = ereg_replace('&', '&amp;', $str);
4802			// any ampersand & that ia already in a "&amp;" should NOT be encoded
4803			//$str = preg_replace("/&(?![:alnum:]*;)/", "&amp;", $str);
4804			$str = ereg_replace('"', '&quot;', $str);
4805			$str = ereg_replace('\'', '&#039;', $str);
4806			$str = ereg_replace('<', '&lt;', $str);
4807			$str = ereg_replace('>', '&gt;', $str);
4808			// these {  and  }  must be html encoded or else they conflict with the template system
4809			$str = str_replace("{", '&#123;', $str);
4810			$str = str_replace("}", '&#125;', $str);
4811			return $str;
4812		}
4813
4814		// reverse of the above encode function
4815		/*!
4816		@function htmlspecialchars_decode
4817		@abstract reverse of the htmlspecialchars_encode function
4818		@param $str (string) of text which may have chars in html token form.
4819		@author Angles
4820		@result string where certain htmlspecialchars encoded chars have been converted to plain ASCII.
4821		@discussion reverse of the htmlspecialchars_encode function
4822		@syntax Currently the these chars will be decoded
4823		&amp; , &quot; , &#039; , &lt; , &gt;
4824		*/
4825		function htmlspecialchars_decode($str)
4826		{
4827			/*// reverse of htmlspecialchars */
4828			$str = str_replace('&#125;', "}", $str);
4829			$str = str_replace('&#123;', "{", $str);
4830
4831			$str = ereg_replace('&gt;', '>', $str);
4832			$str = ereg_replace('&lt;', '<', $str);
4833			$str = ereg_replace('&#039;', '\'', $str);
4834			$str = ereg_replace('&quot;', '"', $str);
4835			$str = ereg_replace('&amp;', '&', $str);
4836			return $str;
4837		}
4838
4839		// ==  "poor-man's" database compatibility ==
4840		/*!
4841		@function db_defang_encode
4842		@abstract alias to function html_quotes_encode
4843		@author Angles
4844		*/
4845		function db_defang_encode($str)
4846		{
4847			return $this->html_quotes_encode($str);
4848		}
4849		/*!
4850		@function html_quotes_encode
4851		@abstract &quot;poor-mans&quot; database compatibility, encode database unfriendly chars as html entities
4852		@author Angles
4853		@discussion phpGroupWare supports a variets of databases and to date still needs certain database
4854		unfriendly chars to be encoded into something else so as not to corrupt data. Such database unfriendly
4855		chars are the double quote, single quote, comma, forward slash, and back slash. Adding to this need is
4856		the fact that some of phpGroupWare data is stored in the database in the form of php serialized items,
4857		which are sensitive to the same chars. Various databases may escape or otherwise alter the data
4858		as their native handling of these unencoded chars, which data altering may completely destroy the
4859		integrity of a php serialized item, i.e. the ability of the serialized item to be converted back into its
4860		native unserialized format. phpGroupWare preference database is the primary use for this encoding.
4861		NOTE this function uses html entities as replacements for the database unfriendly chars, but any
4862		encoding technique that does not itself use those chars could have been used.
4863		*/
4864		function html_quotes_encode($str)
4865		{
4866			// ==  "poor-man's" database compatibility ==
4867			// encode database unfriendly chars as html entities
4868			// it'll work for now, and it can be easily un-done later when real DB classes take care of this issue
4869			// replace  '  and  "  with htmlspecialchars
4870			$str = ereg_replace('"', '&quot;', $str);
4871			$str = ereg_replace('\'', '&#039;', $str);
4872			// replace  , (comma)
4873			$str = ereg_replace(',', '&#044;', $str);
4874			// replace /  (forward slash)
4875			$str = ereg_replace('/', '&#047;', $str);
4876			// replace \  (back slash)
4877			$str = ereg_replace("\\\\", '&#092;', $str);
4878			return $str;
4879		}
4880
4881		// ==  "poor-man's" database compatibility ==
4882		/*!
4883		@function db_defang_decode
4884		@abstract alias to function html_quotes_decode
4885		@author Angles
4886		*/
4887		function db_defang_decode($str)
4888		{
4889			return $this->html_quotes_decode($str);
4890		}
4891		/*!
4892		@function html_quotes_decode
4893		@abstract &quot;poor-mans&quot; database compatibility, decode chars encoded using html_quotes_encode or its alias.
4894		@author Angles
4895		@discussion reverse of function html_quotes_encode, html tokens are converted into ASCII. This
4896		is used for database unfriendly chars which have been encoded with function html_quotes_encode for data
4897		to be stored in a database, and when retrieved from the database this function should be immediately applied.
4898		NOTE this is a quick substitute to REAL database handling of these chars, but it works now, and works
4899		across databases. The name of these two functions originated in the first chars handled by these functions
4900		were simple the doible quote and the single quote. Later these functions were augmented to also handle the
4901		backslash, forward slash, and comma.
4902		*/
4903		function html_quotes_decode($str)
4904		{
4905			// ==  "poor-man's" database compatibility ==
4906			// reverse of html_quotes_encode - html specialchar convert to actual ascii char
4907			// backslash \
4908			$str = ereg_replace('&#092;', "\\", $str);
4909			// forward slash /
4910			$str = ereg_replace('&#047;', '/', $str);
4911			// comma ,
4912			$str = ereg_replace('&#044;', ',', $str);
4913			// single quote '
4914			$str = ereg_replace('&#039;', '\'', $str);
4915			// double quote "
4916			$str = ereg_replace('&quot;', '"', $str);
4917			return $str;
4918		}
4919
4920		// base64 decoding
4921		/*!
4922		@function de_base64
4923		@abstract ?
4924		*/
4925		function de_base64($text)
4926		{
4927			//return $this->a[$this->acctnum]['dcom']->base64($text);
4928			//return imap_base64($text);
4929			return base64_decode($text);
4930		}
4931
4932		/*!
4933		@function ensure_one_urlencoding
4934		@abstract TESTING - make sure folder arg is urlencoded ONCE only
4935		@param $str (string)
4936		@author Angles
4937		*/
4938		function ensure_one_urlencoding($str='')
4939		{
4940			// check for things we know should not exist in a URLENCODED string
4941			if ( (ereg('"', $str))
4942			|| (ereg('\'', $str))
4943			// check for   , (comma)
4944			|| (ereg(',', $str))
4945			// check for  /  (forward slash)
4946			|| (ereg('/', $str))
4947			// check for  \  (back slash)
4948			|| (ereg("\\\\", $str))
4949			|| (ereg('~', $str))
4950			|| (ereg(' ', $str))
4951			)
4952			{
4953				return urlencode($str);
4954			}
4955			else
4956			{
4957				return $str;
4958			}
4959		}
4960
4961		/*!
4962		@function body_hard_wrap
4963		@abstract Wrap test calls either the php4 wordwrap OR optionally sucky native code
4964		@author Angles
4965		@discussion when php3 compat was necessary I made a sucky hand made body wrap,
4966		but now php4 is expected so this function should call the php4 function wordwrap instead.
4967		*/
4968		function body_hard_wrap($in='', $size=78)
4969		{
4970			// use sucky hand made function
4971			//return $this->body_hard_wrap_ex($in, $size);
4972			// use the php4 builting function
4973			return wordwrap($in, $size, "\r\n");
4974
4975		}
4976		// my implementation of a PHP4 only function
4977		/*!
4978		@function body_hard_wrap_ex
4979		@abstract my implementation of a PHP4 only function which keeps lines of text under a certain length.
4980		@author Angles
4981		@discussion Keeps lines of text under a certain length, adding linebreaks to break up lines if
4982		necessary. Used to keep email message body line lengths inline with whatever rules you need.
4983		Recent RFCs greatly expanded the maximum line length allowed in an email message body,
4984		but for backwards compatibility it is generally common practice to keep line lengths in
4985		line with the older standard, which was 78 chars plus CRLF which is 80 chars max.
4986		I believe the modern maximum line length is 998 chars plus CRLF which is 1000 chars.
4987		NEED TO VERIFY THIS.
4988		NOTE immediately that I probably did not do the best job emulating the
4989		php4 function which does the same, but at the time of this functions origination it
4990		was necessary to implement all non php3 functions with a compatibility function.
4991		*/
4992		function body_hard_wrap_ex($in, $size=80)
4993		{
4994			// this function formats lines according to the defined
4995			// linesize. Linebrakes (\n\n) are added when neccessary,
4996			// but only between words.
4997
4998			$out='';
4999			$exploded = explode ("\r\n",$in);
5000
5001			for ($i = 0; $i < count($exploded); $i++)
5002			{
5003				$this_line = $exploded[$i];
5004				$this_line_len = strlen($this_line);
5005				if ($this_line_len > $size)
5006				{
5007					$temptext='';
5008					$temparray = explode (' ',$this_line);
5009					$z = 0;
5010					while ($z <= count($temparray))
5011					{
5012						while ((strlen($temptext.' '.$temparray[$z]) < $size) && ($z <= count($temparray)))
5013						{
5014							$temptext = $temptext.' '.$temparray[$z];
5015							$z++;
5016						}
5017						$out = $out."\r\n".$temptext;
5018						$temptext = $temparray[$z];
5019						$z++;
5020					}
5021				}
5022				else
5023				{
5024					//$out = trim($out);
5025					// get the rest of the line now
5026					$out = $out . $this_line . "\r\n";
5027				}
5028				//$out = trim($out);
5029				//$out = $out . "\r\n";
5030			}
5031			// one last trimming
5032			$temparray = explode("\r\n",$out);
5033			for ($i = 0; $i < count($temparray); $i++)
5034			{
5035				//$temparray[$i] = trim($temparray[$i]);
5036				// NOTE: I see NO reason to trim the LEFT part of the string, use RTRIM instead
5037				$temparray[$i] = rtrim($temparray[$i]);
5038			}
5039			$out = implode("\r\n",$temparray);
5040
5041			return $out;
5042		}
5043
5044		/*!
5045		@function recall_desired_action
5046		@abstract used to preserve if this originated as a reply, replyall, forward, or new mail
5047		@author Angles
5048		@discussion Used in both bocompose and bosend so we put it here for general access.
5049		Line lengths will differ for new mail and forwarded orig body, vs. reply mail that has longer
5050		lines. So this preserves this info for later use. Particularly we like to preserve this thru the spelling pass also.
5051		We look for GPC args "action" or "orig_action", as keys, and their
5052		values are limited to "reply", "replyall", "forward", and "new", with "new" being deduced on the
5053		initial compose page call and put into "orig_action" for later use, while the others possible "action"
5054		values simply get stored in "orig_action" no deduction is required, it is specified.
5055		If new future actions are added, adjust this function accordingly.
5056		@access public
5057		*/
5058		function recall_desired_action()
5059		{
5060			// what action are we dealing with here, reply(all), forward, or newmail
5061			// we care because new and forward get different line length then reply mail that has ">"
5062			$orig_action = 'unknown';
5063			if (($this->get_isset_arg('action'))
5064			&& (
5065				($this->get_arg_value('action') == 'forward')
5066				|| ($this->get_arg_value('action') == 'reply')
5067				|| ($this->get_arg_value('action') == 'replyall')
5068				)
5069			)
5070			{
5071				$orig_action = $this->get_arg_value('action');
5072			}
5073			elseif (($this->get_isset_arg('orig_action'))
5074			&& (
5075				($this->get_arg_value('orig_action') == 'forward')
5076				|| ($this->get_arg_value('orig_action') == 'reply')
5077				|| ($this->get_arg_value('orig_action') == 'replyall')
5078				|| ($this->get_arg_value('orig_action') == 'new')
5079				)
5080			)
5081			{
5082				$orig_action = $this->get_arg_value('orig_action');
5083			}
5084			else
5085			{
5086				// if not reply, replyall, nor forward "action", then we have NEW message
5087				// if this is set now then the above "orig_action" should preserve it
5088				$orig_action = 'new';
5089			}
5090			return $orig_action;
5091		}
5092
5093
5094		/**************************************************************************\
5095		*
5096		* Functions PHP Should Have OR Functions From PHP4+ Backported to PHP3 *
5097		*
5098		\**************************************************************************/
5099
5100		// magic_quotes_gpc  PHP MANUAL:
5101		/*!
5102		@function  stripslashes_gpc
5103		@abstract strip GPC magic quotes from incoming data ONLY IF magic quotes is indeed being used.
5104		@author various sources
5105		@discussion THIS is what MAGIC QUOTES are, quoted from the php online manual.
5106		BEGIN QUOTE Sets the magic_quotes state for GPC (Get/Post/Cookie) operations. When magic_quotes are on,
5107		all ' (single-quote), " (double quote), \ (backslash) and NULs are escaped with a backslash automatically.
5108		GPC means GET/POST/COOKIE which is actually EGPCS these days (Environment, GET, POST, Cookie, Server).
5109		(UPDATE this has changed again, but the idea is the same). This cannot be turned off in
5110		your script because it operates on the data before your script is called. You can check if it is on
5111		using that function and treat the data accordingly. (by Rasmus Lerdorf) END QUOTE.
5112		So ths function will get rid of the escape \ that magic_quotes HTTP POST will add, " becomes \"
5113		and  '  becomes  \'  but ONLY if magic_quotes is on, less likely to strip user intended slashes this way.
5114		*/
5115		function stripslashes_gpc($data)
5116		{	/* get rid of the escape \ that magic_quotes HTTP POST will add, " becomes \" and  '  becomes  \'
5117			  but ONLY if magic_quotes is on, less likely to strip user intended slashes this way */
5118			if (get_magic_quotes_gpc()==1)
5119			{
5120				return stripslashes($data);
5121			}
5122			else
5123			{
5124				return $data;
5125			}
5126		}
5127
5128		/*!
5129		@function  addslashes_gpc
5130		@abstract reverse of function stripslashes_gpc BUT THIS IS NEVER USED.
5131		@discussion Magic quotes, if they are turned on, are added BY PHP before the
5132		script is called. Therefor this function HAS NO USE. It is the decoding that is important
5133		to the coder.
5134		*/
5135		function addslashes_gpc($data)
5136		{	/* add the escape \ that magic_quotes HTTP POST would add, " becomes \" and  '  becomes  \'
5137			  but ONLY if magic_quotes is OFF, else we may *double* add slashes */
5138			if (get_magic_quotes_gpc()==1)
5139			{
5140				return $data;
5141			}
5142			else
5143			{
5144				return addslashes($data);
5145			}
5146		}
5147
5148		/*!
5149		@function is_serialized
5150		@abstract find out if something is already serialized
5151		@param $data could be almost anything
5152		*/
5153		function is_serialized($data)
5154		{
5155			/* not totally complete: currently works with strings, arrays, and booleans (update this if more is added) */
5156
5157			 /* FUTURE: detect a serialized data that had addslashes appplied AFTER it was serialized
5158			 you can NOT unserialize that data until those post-serialization slashes are REMOVED */
5159
5160			//echo 'is_serialized initial input [' .$data .']<br />';
5161			//echo 'is_serialized unserialized input [' .unserialize($data) .']<br />';
5162
5163			if (is_array($data))
5164			{
5165				// arrays types are of course not serialized (at least not at the top level)
5166				// BUT there  may be serialization INSIDE in a sub part
5167				return False;
5168			}
5169			elseif ($this->is_bool_ex($data))
5170			{
5171				// a boolean type is of course not serialized
5172				return False;
5173			}
5174			elseif ((is_string($data))
5175			&& (($data == 'b:0;') || ($data == 'b:1;')) )
5176			{
5177				// check for easily identifiable serialized boolean values
5178				return True;
5179			}
5180			elseif ((is_string($data))
5181			&& (unserialize($data) == False))
5182			{
5183				// when you unserialize a normal (not-serialized) string, you get False
5184				return False;
5185			}
5186			elseif ((is_string($data))
5187			&& (ereg('^s:[0-9]+:"',$data) == True))
5188			{
5189				// identify pattern of a serialized string (that did NOT have slashes added AFTER serialization )
5190				return True;
5191			}
5192			elseif ((is_string($data))
5193			&& (is_array(unserialize($data))))
5194			{
5195				// if unserialization produces an array out of a string, it was serialized
5196				//(ereg('^a:[0-9]+:\{',$data) == True))  also could work
5197				return True;
5198			}
5199			//Best Guess - UNKNOWN / ERROR / NOY YET SUPPORTED TYPE
5200			elseif (is_string($data))
5201			{
5202				return True;
5203			}
5204			else
5205			{
5206				return False;
5207			}
5208		}
5209
5210		/*!
5211		@function is_serialized_str
5212		@abstract find out if a string is already serialized, speed increases since string is known type
5213		@param $string_data SHOULD be a string, or else call "is_serialized()" instead
5214		*/
5215		function is_serialized_str($string_data)
5216		{
5217			if ((is_string($string_data))
5218			&& (unserialize($string_data) == False))
5219			{
5220				// when you unserialize a normal (not-serialized) string, you get False
5221				return False;
5222			}
5223			else
5224			{
5225				return True;
5226			}
5227		}
5228
5229		/*!
5230		@function is_serialized_smarter
5231		@abstract find out if a string is already serialized, BUT NOT FOOLED BY SLASH problems on unserizalize.
5232		@param $string_data SHOULD be a string
5233		*/
5234		function is_serialized_smarter($string_data)
5235		{
5236			if ((is_string($string_data))
5237			&& (unserialize($string_data) == False))
5238			{
5239				// when you unserialize a normal (not-serialized) string, you get False
5240				// HOWEVER slashes may b0rk unserialize, do not be fooled, it is still serialized
5241				// so use a second test here, piss poor test, but it helps
5242				if (stristr($string_data, ':"stdClass":'))
5243				{
5244					// unserialize failed but the source str appears to look like a serialized thing
5245					return True;
5246				}
5247				else
5248				{
5249					// second test still says this is not  serialized str
5250					return False;
5251				}
5252			}
5253			else
5254			{
5255				return True;
5256			}
5257		}
5258
5259		// PHP3 SAFE Version of "substr_count"
5260		/*!
5261		@function substr_count_ex
5262		@abstract returns the number of times the "needle" substring occurs in the "haystack" string
5263		@param $haystack  string
5264		@param $needle  string
5265		*/
5266		function substr_count_ex($haystack='', $needle='')
5267		{
5268			if (floor(phpversion()) == 3)
5269			{
5270				if (($haystack == '') || ($needle == ''))
5271				{
5272					return 0;
5273				}
5274
5275				$crtl_struct = Array();
5276				// how long is needle
5277				$crtl_struct['needle_len'] = strlen($needle);
5278				// how long is haystack before the replacement
5279				$crtl_struct['haystack_orig_len'] = strlen($haystack);
5280
5281				// we will replace needle with a BLANK STRING
5282				$crtl_struct['haystack_new'] = str_replace("$needle",'',$haystack);
5283				// how long is the new haystack string
5284				$crtl_struct['haystack_new_len'] = strlen($crtl_struct['haystack_new']);
5285				// the diff in length between orig haystack and haystack_new diveded by len of needle = the number of occurances of needle
5286				$crtl_struct['substr_count'] = ($crtl_struct['haystack_orig_len'] - $crtl_struct['haystack_new_len']) / $crtl_struct['needle_len'];
5287
5288				//echo '<br />';
5289				//var_dump($crtl_struct);
5290				//echo '<br />';
5291
5292				// return the finding
5293				return $crtl_struct['substr_count'];
5294			}
5295			else
5296			{
5297				return substr_count($haystack, $needle);
5298			}
5299		}
5300
5301		// PHP3 SAFE Version of "is_bool"
5302		/*!
5303		@function is_bool_ex
5304		@abstract Find out whether a variable is boolean
5305		@param $bool  mixed
5306		@author gleaned from the user notes of the php manual
5307		@discussion This is a  PHP3 SAFE Version of php function is_bool.
5308		*/
5309		function is_bool_ex($bool)
5310		{
5311			if (floor(phpversion()) == 3)
5312			{
5313				// this was suggested in the user notes of the php manual
5314				// yes I know there are other ways, but for now this must work in .12 and devel versions
5315				return (gettype($bool) == 'boolean');
5316			}
5317			else
5318			{
5319				return is_bool($bool);
5320			}
5321		}
5322
5323		// PHP3 and PHP<4.0.6 SAFE Version of "array_search"
5324		/*!
5325		@function array_search_ex
5326		@abstract Search an array for a string.
5327		@author Angles
5328		@discussion This is a  PHP3 SAFE and PHP< 4.0.6 Version of php function array_search.
5329		NOTE I did not implement the $strict param.
5330		*/
5331		function array_search_ex($needle='', $haystack='', $strict=False)
5332		{
5333			if(!$haystack)
5334			{
5335				$haystack=array();
5336			}
5337			// error check
5338			if ((trim($needle) == '')
5339			|| (!$haystack)
5340			|| (count($haystack) == 0))
5341			{
5342				return False;
5343			}
5344
5345			$finding = False;
5346			@reset($haystack);
5347			$i = 0;
5348			while(list($key,$value) = each($haystack))
5349			{
5350				//if ((string)$value == (string)$needle)
5351				if ((string)$haystack[$key] == (string)$needle)
5352				{
5353					$finding = $i;
5354					break;
5355				}
5356				else
5357				{
5358					$i++;
5359				}
5360			}
5361			return $finding;
5362		}
5363
5364		/*!
5365		@function minimum_version
5366		@abstract check if the version of php running the script is at least version "vercheck"
5367		@param $vercheck (string) is the minimum version of php you desire, like "4.1.0"
5368		a blank param will retuen false
5369		@discussion semi replacement version-compare, which is a php4.1+ function. This provides
5370		similar functionality. The code was found in the user comments on date Jul 25 2002 on php
5371		doc page www.php.net/manual/en/function.version-compare.php author is webmaster@mgs2online.f2s.com
5372		as indicated on that page.
5373		@example
5374		if (minimum_version("4.1.0")) {
5375			echo "version supports action"
5376		}
5377		@author webmaster@mgs2online.f2s.com from page www.php.net/manual/en/function.version-compare.php
5378		*/
5379		function minimum_version($vercheck='1.0.0')
5380		{
5381			$minver = (int)str_replace('.', '', $vercheck);
5382			$curver = (int)str_replace('.', '', phpversion());
5383			if($curver >= $minver)
5384			{
5385				return true;
5386			}
5387			else
5388			{
5389				return false;
5390			}
5391		}
5392
5393
5394		/**************************************************************************\
5395		*
5396		*  EVENTS
5397		*
5398		\**************************************************************************/
5399
5400		/*!
5401		@capability EVENTS that effect *parts* of cached data.
5402		@discussion These "events" are used to alter cached data to keep it reasonably in sync
5403		with the server, so we do not need to contact the server again if we can estimate the data
5404		change resulting from an event. We alter the cached items directly, and hopefully this
5405		will match what the data on the server is after an event. Used for things like
5406		clearing a messages "Recent" or "Unseen" flags.
5407		*/
5408
5409		/*!
5410		@function event_begin_big_move
5411		@abstract when about to start a big batch move or delete, turn off fancy extreme stuff
5412		@author Angles
5413		@discussion Normally with "extreme" caching, each view, move, or delete of a message
5414		is "mirrored" in the local cache, we pull that item out of cache, manually alter it to be
5415		reasonably "in sync" with the mailserver, and put the item back in cache without asking
5416		the mailserver for any updated information. However, during large, bulk mail moves or
5417		deletes, such as with filtering operations, this "extreme" fancy stuff is really overkill because
5418		so much stuff is changing, it is easier to simply expire items that might be effected, so we
5419		do not do "fancy" stuff with each single move or delete, because the cache data is not
5420		available to pull out and manipulate after we expire it AND BECAUSE we TURN OFF
5421		"session_cache_extreme"for the remainder of this script run.
5422		UNDER DEVELOPMEMT.
5423		*/
5424		function event_begin_big_move($fldball='', $called_by='not_specified')
5425		{
5426			if ($this->debug_events > 0) { $this->dbug->out('mail_msg_base: event_begin_big_move: ('.__LINE__.') ENTERING, called by ['.$called_by.'], $this->session_cache_extreme is ['.serialize($this->session_cache_extreme).']<br />'); }
5427			// remember the *initial* session_cache_extreme value, we will return that
5428			$initial_session_cache_extreme = $this->session_cache_extreme;
5429			$this->set_arg_value('initial_session_cache_extreme', 0, $initial_session_cache_extreme);
5430			$this->set_arg_value('big_move_in_progress', 0, True);
5431			// currently param $fldball is NOT used in this function
5432			if (($this->session_cache_enabled == True)
5433			&& ($this->session_cache_extreme == True))
5434			{
5435				// EXTREME MODE
5436				if ($this->debug_events > 1) { $this->dbug->out('mail_msg_base: event_begin_big_move: ('.__LINE__.') (extreme mode) pre-expire cached items before a big delete or move operation, so we do not directly alter cached items for each single move or delete<br />'); }
5437				$this->batch_expire_cached_items('mail_msg_base: event_begin_big_move: LINE '.__LINE__);
5438
5439				if ($this->debug_events > 1) { $this->dbug->out('mail_msg_base: event_begin_big_move: ('.__LINE__.') (extreme mode) now that we expired stuff, we can TURN OFF extreme caching for the rest of this operation, this puts "folder_status_info" in L1 cache only<br />'); }
5440				// TURN OFF "session_cache_extreme"for the remainder of this script run
5441				$this->session_cache_extreme = False;
5442			}
5443			else
5444			{
5445				if ($this->debug_events > 1) { $this->dbug->out('mail_msg_base: event_begin_big_move('.__LINE__.'): eventhough $this->session_cache_extreme is off, WE STILL NEED TO EXPIRE MSGBALL_LIST, because it is cached in non-extreme mode too<br />'); }
5446				$this->batch_expire_cached_items('mail_msg_base: event_begin_big_move: LINE '.__LINE__.' but only for msgball_list', True);
5447			}
5448			if ($this->debug_events > 0) { $this->dbug->out('mail_msg_base: event_begin_big_move: LEAVING, ('.__LINE__.') exiting $this->session_cache_extreme is ['.serialize($this->session_cache_extreme).'], returning the $initial_session_cache_extreme ['.serialize($initial_session_cache_extreme).']<br />'); }
5449			return $initial_session_cache_extreme;
5450		}
5451
5452		/*!
5453		@function event_begin_big_end
5454		@abstract cache extreme is duisabled during a big batch move or delete, this will restore it to its original state.
5455		@author Angles
5456		@discussion If session_cache_extreme was ON before the even to notify of a big move or delete, then
5457		this function will restore that original value when this is called, so that after the big move or delete, when
5458		the next page is displayed, the caching may begin again immediately. Otherwise session_cache_extreme
5459		would remain disabled until the next page view, even when its initial value before the bigmove notice
5460		may have been enabled. UNDER DEVELOPMEMT.
5461		*/
5462		function event_begin_big_end($called_by='not_specified')
5463		{
5464			if ($this->debug_events > 0) { $this->dbug->out('mail_msg_base: event_begin_big_end: ('.__LINE__.') ENTERING, called by ['.$called_by.'], at this moment $this->session_cache_extreme is ['.serialize($this->session_cache_extreme).']<br />'); }
5465			// remember the *initial* session_cache_extreme value, we will return that
5466			$temp_session_cache_extreme = $this->session_cache_extreme;
5467			if ( ($this->get_isset_arg('initial_session_cache_extreme', 0))
5468			&& ($this->get_arg_value('initial_session_cache_extreme', 0) == True)
5469			&& ($this->get_isset_arg('big_move_in_progress', 0))
5470			&& ($this->get_arg_value('big_move_in_progress', 0) == True)
5471			&& ($temp_session_cache_extreme != True))
5472			{
5473				// restore EXTREME MODE setting
5474				if ($this->debug_events > 1) { $this->dbug->out('mail_msg_base: event_begin_big_end: ('.__LINE__.') session_cache_extreme WAS True before disabling for the big move, now restoring value to True, so caching may begin again<br />'); }
5475				$this->session_cache_extreme = True;
5476				// unset these temporary flags
5477				$this->unset_arg('initial_session_cache_extreme', 0);
5478				$this->unset_arg('big_move_in_progress', 0);
5479			}
5480			if ($this->debug_events > 0) { $this->dbug->out('mail_msg_base: event_begin_big_end: LEAVING, ('.__LINE__.') returning now current $this->session_cache_extreme ['.serialize($this->session_cache_extreme).']<br />'); }
5481			return $this->session_cache_extreme;
5482		}
5483
5484
5485		/*!
5486		@function batch_expire_cached_items
5487		@abstract expires all data associated with "extreme" caching for ALL account
5488		@param (string) $called_by optional for debug information
5489		@param (boolean) $only_msgball_list DEFAULT is False, specify true when extreme mode is off BUT
5490		WE STILL NEED TO EXPIRE ALL MSGBALL_LIST DATA because it is cached outside of extreme mode.
5491		@author Angles
5492		@discussion Plain, unconditional expiration of phpgw_header, msg_structure,
5493		msgball_list, folder_status_info (in appsession) items, for all accounts that are "enabled".
5494		Does a loop thru existing accounts. NOTE THIS REALLY WIPES DATA completely, it is not
5495		very smart, it wipes cached data that may still be useful, so this really does clear the cache.
5496		UNDER DEVELOPMEMT
5497		UPDATE we use folder as a key in msgball_list but batch expire still works because
5498		it wipes data based on a key prior to folder name, same as with other data that has a
5499		folder name in its data key.
5500		*/
5501		function batch_expire_cached_items($called_by='not_specified', $only_msgball_list=False)
5502		{
5503			if ($this->debug_events > 0) { $this->dbug->out('mail_msg_base: batch_expire_cached_items: ('.__LINE__.') ENTERING, called by ['.$called_by.'], $only_msgball_list: ['.serialize($only_msgball_list).'], $this->session_cache_extreme is ['.serialize($this->session_cache_extreme).']<br />'); }
5504			for ($i=0; $i < count($this->extra_and_default_acounts); $i++)
5505			{
5506				if ($this->extra_and_default_acounts[$i]['status'] == 'enabled')
5507				{
5508					$this_acctnum = $this->extra_and_default_acounts[$i]['acctnum'];
5509					$this->expire_session_cache_item('msgball_list', $this_acctnum);
5510					if ($this->debug_events > 1) { $this->dbug->out(' * mail_msg_base: batch_expire_cached_items: ('.__LINE__.') (extreme OR non-extreme mode) for acctnum ['.$this_acctnum.'] expire whatever msgball_list is cached for this account<br />'); }
5511					if ($only_msgball_list == False)
5512					{
5513						if ($this->debug_events > 1) { $this->dbug->out(' * mail_msg_base: batch_expire_cached_items: ('.__LINE__.') (extreme mode) for acctnum ['.$this_acctnum.'] expire extreme cached items NOTE this will WIPE CLEAN most all cached items, pretty extreme<br />'); }
5514						$this->expire_session_cache_item('phpgw_header', $this_acctnum);
5515						$this->expire_session_cache_item('msg_structure', $this_acctnum);
5516						$this->expire_session_cache_item('folder_status_info', $this_acctnum);
5517					}
5518				}
5519			}
5520			// for DB sessions_db ONLY
5521			if (
5522			(
5523				($GLOBALS['phpgw_info']['server']['sessions_type'] == 'db')
5524				|| ($this->use_private_table == True)
5525			)
5526			&& ($only_msgball_list == False))
5527			{
5528				// we already expired actual DB msgball data above, this will erase all other data, that function may save a few important things though
5529				if ($this->debug_events > 1) { $this->dbug->out('mail_msg_base: batch_expire_cached_items: ('.__LINE__.') session_db IS in use, calling the appsession eraser function $this->so->expire_db_session_bulk_data <br />'); }
5530				$this->so->expire_db_session_bulk_data($called_by='batch_expire_cached_items LINE '.__LINE__);
5531			}
5532			if ($this->debug_events > 0) { $this->dbug->out('mail_msg_base: batch_expire_cached_items: ('.__LINE__.') LEAVING, called by ['.$called_by.'],  $only_msgball_list: ['.serialize($only_msgball_list).'], $this->session_cache_extreme is ['.serialize($this->session_cache_extreme).']<br />'); }
5533		}
5534
5535
5536		/*!
5537		@function event_msg_seen
5538		@abstract when a message is viewed
5539		@author Angles
5540		@result (boolean) True if the item needed altering, False if item did not need altering
5541		@discussion When a message is viewed, its "recent" and/or"unseen" flag is cleared. If
5542		we cache this information, to stay reasonably in sync with the actual messge flags,
5543		we need to alter the cached item so it has no "recent" or "seen" flags. NOTE on
5544		php headers structure FLAGS handling, if a flag is NOT SET, that structure
5545		will have a " " as that flags value, that is a STRING WITH ONE BLANK SPACE.
5546		So if we are clearing a flag, we set it to " " in that structure, and save it back to the cache.
5547		UNDER DEVELOPMEMT.
5548		*/
5549		function event_msg_seen($msgball='', $called_by='not_specified')
5550		{
5551			if ($this->debug_events > 0) { $this->dbug->out('mail_msg_base: event_msg_seen('.__LINE__.'): ENTERING, called by ['.$called_by.'], $this->session_cache_extreme is ['.serialize($this->session_cache_extreme).']<br />'); }
5552
5553			// CACHE NOTE: FLAGS: if this message we are about to read has flags saying it is UNREAD
5554			// (a) $this->session_cache_extreme == False - expire that "phpgw_header" item
5555			// (b) $this->session_cache_extreme == True - ALTER the "phpgw_header" cached item and save back to cache
5556
5557			if (($this->session_cache_enabled == True)
5558			&& ($this->session_cache_extreme == False))
5559			{
5560				// NON-EXTREME MODE
5561				if ($this->debug_events > 0) { $this->dbug->out('mail_msg_base: event_msg_seen('.__LINE__.'): (non-extreme mode) session_cache_extreme is ['.serialize($this->session_cache_extreme).'] (false) means "phpgw_header" is NOT cached and we DO NOTHING here.<br />'); }
5562				// DO NOTHING, this data is not cached in non-extreme mode
5563				$did_expire = False;
5564
5565				if ($this->debug_events > 0) { $this->dbug->out('mail_msg_base: event_msg_seen('.__LINE__.'): (non-extreme mode) LEAVING, $did_expire is ['.serialize($did_expire).']<br />'); }
5566				return $did_expire;
5567			}
5568			elseif (($this->session_cache_enabled == True)
5569			&& ($this->session_cache_extreme == True))
5570			{
5571				// EXTREME MODE
5572				if ($this->debug_events > 0) { $this->dbug->out('mail_msg_base: event_msg_seen('.__LINE__.'): (extreme mode) $this->session_cache_extreme is ['.serialize($this->session_cache_extreme).'] means we should directly alter a stale "phpgw_header" item and resave to cache <br />'); }
5573				// we only care about doing this is caching is enabled
5574				// this should already be cached, if not, it will be after this call
5575				// this works OK for both php4 sessions AND sessions_db
5576				$msg_headers = $this->phpgw_header($msgball);
5577				if ($this->debug_events > 2) { $this->dbug->out('email_msg_base: event_msg_seen('.__LINE__.'): SEEN-UNSEEN "phpgw_header" examination for $msg_headers DUMP:', $msg_headers); }
5578				//if ($this->debug_events > 2) { $this->dbug->out('email_msg_base: event_msg_seen('.__LINE__.'): (extreme mode) SEEN-UNSEEN "phpgw_header" examination for $msg_headers <br /> * '.serialize($msg_headers).'<br />'); }
5579				$did_alter = False;
5580				// SEEN OR UNSEEN/NEW test
5581				if (($msg_headers->Unseen == 'U') || ($msg_headers->Recent == 'N'))
5582				{
5583					// cached data says the message is unseen, yet we are about to see it right now!
5584					// need to clear "Unseen" and/or "Recent" flags
5585					if (isset($msg_headers->Unseen))
5586					{
5587						$msg_headers->Unseen = ' ';
5588					}
5589					if (isset($msg_headers->Recent))
5590					{
5591						$msg_headers->Recent = ' ';
5592					}
5593					if ($this->debug_events > 2) { $this->dbug->out('email_msg_base: event_msg_seen('.__LINE__.'): (extreme mode) SEEN-UNSEEN "phpgw_header" needed to be cleared, altered $msg_headers <br /> * '.serialize($msg_headers).'<br />'); }
5594					// this is the way we pass phpgw_header data to the caching function
5595					$meta_data = array();
5596					$meta_data['msgball'] = array();
5597					$meta_data['msgball'] = $msgball;
5598					$meta_data['phpgw_header'] = $msg_headers;
5599					if ($this->debug_events > 1) { $this->dbug->out('email_msg_base: event_msg_seen('.__LINE__.'): (extreme mode) cached SEEN-UNSEEN "phpgw_header" flags cleared and saved back to cache, for $msgball ['.serialize($msgball).']<br />'); }
5600					// this works OK for both php4 sessions AND sessions_db
5601					$this->save_session_cache_item('phpgw_header', $meta_data, $meta_data['msgball']['acctnum']);
5602					$did_alter = True;
5603
5604
5605					// FUTURE: PART TWO: ALTER FOLDER STATUS INFO, REDUCE UNSEEN COUNT BY ONE
5606					if ($this->debug_events > 1) { $this->dbug->out('mail_msg_base: event_msg_seen('.__LINE__.'): (extreme mode) (step 2) code will adjust folder_status_info to REDUCE UNSEEN count by 1, and resave that to cache <br />'); }
5607
5608					if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_seen('.__LINE__.'): (extreme mode) (step 2) grabbing folder_status_info DIRECTLY from appsession, <br /> * can not call "read_session_cache_item" because when moving multiple mails, we do not "expunge" until the last one, so validity check will fail because we are *ahead* of the mail server in "freshness"<br />'); }
5609					$acctnum = $msgball['acctnum'];
5610					$extra_keys = $msgball['folder'];
5611					$data_name = 'folder_status_info';
5612					//$location = 'acctnum='.(string)$acctnum.';data_name='.$data_name.';extra_keys='.$extra_keys;
5613					//$app = 'email';
5614					// get session data
5615					$folder_status_info = array();
5616					//$folder_status_info = $GLOBALS['phpgw']->session->appsession($location,$app);
5617
5618					// for DB sessions_db ONLY
5619					if (($GLOBALS['phpgw_info']['server']['sessions_type'] == 'db')
5620					|| ($this->use_private_table == True))
5621					{
5622						$my_location = (string)$acctnum.';'.$data_name.';'.$extra_keys;
5623						if ($this->debug_events > 1) { $this->dbug->out('email_msg_base: event_msg_seen('.__LINE__.'): (extreme mode) sessions_type is ['.$GLOBALS['phpgw_info']['server']['sessions_type'].'] SO we have this additional step to read data from phpgw_app_sessions table, $my_location ['.$my_location.']<br />'); }
5624						if ($this->use_private_table == True)
5625						{
5626							$this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum][$data_name][$extra_keys]
5627								= $this->so->so_get_data($my_location);
5628						}
5629						else
5630						{
5631							$this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum][$data_name][$extra_keys]
5632								= $GLOBALS['phpgw']->session->appsession($my_location, 'email');
5633						}
5634						if ($this->debug_events > 2) { $this->dbug->out('email_msg_base: event_msg_seen('.__LINE__.'): (extreme mode) [email][dat]['.$acctnum.']['.$data_name.']['.$extra_keys.']  DUMP:', $this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum][$data_name][$extra_keys]); }
5635					}
5636
5637					$folder_status_info = $this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum][$data_name][$extra_keys];
5638
5639					if ($this->debug_events > 1) { $this->dbug->out('mail_msg_base: event_msg_seen('.__LINE__.'): (extreme mode) (step 2) grabbed $folder_status_info DUMP:', $folder_status_info); }
5640					//if ($this->debug_events > 1) { $this->dbug->out('mail_msg_base: event_msg_seen: (extreme mode) (step 2) grabbed folder_status_info :: unserialized $meta_data[$specific_key] DUMP <pre>'; print_r($folder_status_info); echo '</pre>'); }
5641					//$folder_status_info = unserialize($meta_data['folder_status_info'][$specific_key]);
5642
5643					if (!$folder_status_info)
5644					{
5645						if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_seen('.__LINE__.'): (extreme mode) (step 2) no cached "folder_status_info" exists<br />'); }
5646					}
5647					else
5648					{
5649						if ($this->debug_events > 2) { $this->dbug->out('mail_msg: event_msg_seen('.__LINE__.'): (extreme mode) (step 2) cached msgball_list $folder_status_info DUMP:', $folder_status_info); }
5650
5651						$prev_new_count = $folder_status_info['number_new'];
5652						$adjusted_new_count = ($prev_new_count - 1);
5653						$folder_status_info['number_new'] = $adjusted_new_count;
5654
5655						// the user alert string needs updating also
5656						$folder_status_info['alert_string'] = str_replace((string)$prev_new_count, (string)$adjusted_new_count, $folder_status_info['alert_string']);
5657
5658						// save altered data back into the cache
5659						if ($this->debug_events > 2) { $this->dbug->out('mail_msg: event_msg_seen('.__LINE__.'): (extreme mode) (step 2) save ADJUSTED "folder_status_info" DUMP:', $folder_status_info); }
5660						if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_seen('.__LINE__.'): (extreme mode) (step 2) save ADJUSTED "folder_status_info" back to cache with "save_session_cache_item", note the timestamp not changed<br />'); }
5661						// thid call is OK, it will not change the data, it just puts it in cache, no need for direct APPSESSION call
5662						//$this->save_session_cache_item('folder_status_info', $folder_status_info, $acctnum, $extra_keys);
5663						$this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum][$data_name][$extra_keys] = $folder_status_info;
5664						// for DB sessions_db ONLY
5665						if (($GLOBALS['phpgw_info']['server']['sessions_type'] == 'db')
5666						|| ($this->use_private_table == True))
5667						{
5668							$my_location = (string)$acctnum.';'.$data_name.';'.$extra_keys;
5669							if ($this->debug_events > 1) { $this->dbug->out('email_msg_base: event_msg_seen('.__LINE__.'): (extreme mode) sessions_type is ['.$GLOBALS['phpgw_info']['server']['sessions_type'].'] SO we have this additional step to save data to phpgw_app_sessions table, $my_location ['.$my_location.']<br />'); }
5670							if ($this->use_private_table == True)
5671							{
5672								$this->so->so_set_data($my_location, $this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum][$data_name][$extra_keys]);
5673							}
5674							else
5675							{
5676								$GLOBALS['phpgw']->session->appsession($my_location, 'email', $this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum][$data_name][$extra_keys]);
5677							}
5678						}
5679						$did_alter = True;
5680					}
5681				}
5682				if ($this->debug_events > 0) { $this->dbug->out('mail_msg_base: event_msg_seen('.__LINE__.'): (extreme mode) LEAVING, $did_alter is ['.serialize($did_alter).']<br />'); }
5683				return $did_alter;
5684			}
5685
5686			if ($this->debug_events > 0) { $this->dbug->out('mail_msg_base: event_msg_seen: LEAVING, unhaandled situation, or caching is turned off<br />'); }
5687			return False;
5688		}
5689
5690		/*!
5691		@function event_msg_move_or_delete
5692		@abstract when a message is moved out of a folder.
5693		@author Angles
5694		@discussion When a message moved OUT of a folder, whether deleted or moved to
5695		another folder, the "msgball_list", is not longer valid. If we are caching with periods of
5696		forced non-connection to the mail server, we need to pop out that individual magball
5697		from the msgball_list.. UNDER DEVELOPMEMT.  FUTURE, PART TWO, alter folder status info.
5698		If message being moved has flags "Recent" or "Unseen", folder status info needs its unseen count reduced
5699		by one AND its total count reduced by one.
5700		*/
5701		function event_msg_move_or_delete($msgball='', $called_by='not_specified', $to_fldball='')
5702		{
5703			if ($this->debug_events > 0) { $this->dbug->out('mail_msg: event_msg_move_or_delete: ('.__LINE__.') ENTERING, called by ['.$called_by.']<br />'); }
5704			if (($this->session_cache_enabled == False)
5705			&& ($this->session_cache_extreme == False))
5706			{
5707				if ($this->debug_events > 0) { $this->dbug->out('mail_msg: event_msg_move_or_delete: ('.__LINE__.') LEAVING, BOTH session_cache_enabled AND session_cache_extreme are FALSE, we have nothing to do here, returning False<br />'); }
5708				return False;
5709			}
5710
5711			if ( (isset($msgball) == False)
5712			|| (!$msgball)
5713			|| (is_array($msgball) == False) )
5714			{
5715				if ($this->debug_events > 0) { $this->dbug->out('mail_msg: event_msg_move_or_delete: ('.__LINE__.') FALLBACK BATCH EXPIRE because param $msgball ['.serialize($msgball).'] is not set or not an array, we do not know what account nor folder we need to operate on, but we still need to clean cache so it matches reality after the move or delete<br />'); }
5716				$this->batch_expire_cached_items('mail_msg: event_msg_move_or_delete: ('.__LINE__.') (because we got erronious msgball data) ');
5717				if ($this->debug_events > 0) { $this->dbug->out('mail_msg: event_msg_move_or_delete: ('.__LINE__.') LEAVING, returning True because we did expire stuff<br />'); }
5718				return True;
5719			}
5720			$did_alter_or_expire = False;
5721			/*
5722			// make sure folder name in msgball is in URLENCODED form
5723			if (isset($msgball['folder']))
5724			//&& (stristr($msgball['folder'],'%') == False))
5725			{
5726				$msgball['folder'] = $this->prep_folder_out($msgball['folder']);
5727			}
5728			*/
5729			$clean_folder = $this->prep_folder_in($msgball['folder']);
5730			$urlencoded_folder = $this->prep_folder_out($clean_folder);
5731			if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete: ('.__LINE__.') $clean_folder ['.$clean_folder.'], $urlencoded_folder ['.$urlencoded_folder.']<br />'); }
5732
5733			$msgball['folder'] = $urlencoded_folder;
5734			$acctnum = $msgball['acctnum'];
5735			if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete: ('.__LINE__.') (pre step 1) $this->read_session_cache_item("msgball_list", $acctnum); to see if we have a session cached folderlist (is that the right function to call?)<br />'); }
5736			// so we have data in the cache?
5737			$data_name = 'msgball_list';
5738			// currently we DO NOT use the $extra_keys param for msgball_list data
5739			// UPDATE YES NOW WE USE FOLDER NAME IN THE DATA KEYS FOR MSGBALL_LIST
5740			$ex_folder = $urlencoded_folder;
5741			// get session data
5742			//if (($this->debug_events > 1) || ($this->debug_session_caching > 1)) { echo 'mail_msg: event_msg_move_or_delete('.__LINE__.'): DIRECT CALL to get appsession data for $location ['.$location.'], $app ['.$app.']<br />'; }
5743
5744			//$cached_msgball_data = $GLOBALS['phpgw']->session->appsession($location,$app);
5745			//$cached_msgball_data = $this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum][$data_name];
5746			//$cached_msgball_data = $this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum]['msgball_list'];
5747
5748			// $cached_msgball_data is saved in cache with validity data, like this:
5749			// 	$cached_msgball_data['msgball_list']
5750			// 	$cached_msgball_data['validity']
5751			// this makes for a strange looking array with the string "msgball_list" appearing two times in a row, but that is how it works, like this
5752			// 	['email']['dat'][0]['msgball_list']
5753			// is the base "node" the data is attached to, and the rest looks like this
5754			// 	['email']['dat'][0]['msgball_list']['msgball_list']
5755			// 	['email']['dat'][0]['msgball_list']['validity']
5756
5757			// for DB sessions_db ONLY
5758			if (($GLOBALS['phpgw_info']['server']['sessions_type'] == 'db')
5759			|| ($this->use_private_table == True))
5760			{
5761				if ((isset($this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum]['msgball_list'][$ex_folder]) == False)
5762				|| (!$this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum]['msgball_list'][$ex_folder]))
5763				{
5764					//$my_location = (string)$acctnum.';msgball_list';
5765					// NOW WE USE FOLDER TOO as a data key for msgball_list
5766					$my_location = (string)$acctnum.';msgball_list;'.$ex_folder;
5767					if (($this->debug_events > 1) || ($this->debug_session_caching > 1)) { echo 'mail_msg: event_msg_move_or_delete('.__LINE__.'): DIRECT CALL to get appsession data for $location ['.$location.'], $app ['.$app.']<br />'; }
5768					if ($this->debug_events > 1) { $this->dbug->out('email_msg_base: event_msg_move_or_delete('.__LINE__.'): (extreme mode) sessions_type is ['.$GLOBALS['phpgw_info']['server']['sessions_type'].'] SO we have this additional step to read data from a database table, $my_location ['.$my_location.']<br />'); }
5769					if ($this->use_private_table == True)
5770					{
5771						//$this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum]['msgball_list'][$ex_folder]
5772						//	= $this->so->so_get_data($my_location);
5773						// TRY USING COMPRESSION for msgball_list
5774						$this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum]['msgball_list'][$ex_folder]
5775							= $this->so->so_get_data($my_location, True);
5776					}
5777					else
5778					{
5779						$this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum]['msgball_list'][$ex_folder]
5780							= $GLOBALS['phpgw']->session->appsession($my_location, 'email');
5781					}
5782					//if ($this->debug_events > 2) { $this->dbug->out('email_msg_base: event_msg_move_or_delete('.__LINE__.'): (extreme mode) [email][dat]['.$acctnum.'][msgball_list] DUMP:<pre>'; print_r($this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum]['msgball_list'][ex_folder]); echo '</pre>'); }
5783				}
5784			}
5785
5786			$cached_msgball_data =& $this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum]['msgball_list'][$ex_folder];
5787
5788			if (($this->debug_events > 2) && ($this->debug_allow_magball_list_dumps)) { $this->dbug->out('mail_msg: event_msg_move_or_delete('.__LINE__.'): for $my_location ['.$my_location.'], restored $cached_msgball_data DUMP:', $cached_msgball_data); }
5789
5790			if ((!$cached_msgball_data)
5791			&& ($this->session_cache_extreme == False))
5792			{
5793				if ($this->debug_events > 0) { $this->dbug->out('mail_msg: event_msg_move_or_delete: ('.__LINE__.') LEAVING, because NOTHING TO DO, IN NON-EXTREME MODE, and we have NO CACHED MSGBALL_LIST, there is no action we need to take, return False<br />'); }
5794				return False;
5795			}
5796			elseif ((!$cached_msgball_data)
5797			&& ($this->session_cache_extreme == True))
5798			{
5799				if ($this->debug_events > 0) { $this->dbug->out('mail_msg: event_msg_move_or_delete: ('.__LINE__.') LEAVING, because NOTHING TO DO, we are in EXTREME-MODE, BUT we have NO CACHED MSGBALL_LIST, so skip down to the other stuff we do in extreme mode here<br />'); }
5800			}
5801			elseif (($this->session_cache_extreme == False)
5802			&& ($cached_msgball_data))
5803			{
5804				// NON-EXTREME MODE but session cache is on, so expire msgball_list for this folder
5805				if ($this->debug_events > 1) { $this->dbug->out('mail_msg_base: event_msg_move_or_delete: ('.__LINE__.') (non-extreme mode) session_cache_extreme is ['.serialize($this->session_cache_extreme).'] (false) means "msg_structure" and "phpgw_header" is NOT cached BUT msgball_list IS cached in non-extreme mode, so ...<br />'); }
5806
5807				if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete: ('.__LINE__.') (non-extreme mode) session_cache_extreme is ['.serialize($this->session_cache_extreme).'] means we should simply expire the entire "msgball_list" (and maybe the "folder_status_info" too? no "folder_status_info" is not even cached in non extreme mode<br />'); }
5808				// expire entire msgball_list and the folder_status_info
5809				if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete: ('.__LINE__.') (non-extreme mode) calling $this->expire_session_cache_item("msgball_list", '.$msgball['acctnum'].')<br />'); }
5810				// FUTURE: if each account ever saves msgball_list for individual folders instead of just one folder per account, then add extra_keys to this command
5811				$this->expire_session_cache_item('msgball_list', $msgball['acctnum'], $ex_folder);
5812
5813				// ANYTIME a message is moved out of a folder, we need to remove any cached "msg_structure" and "phpgw_header" data
5814				// damn why are we doing this in non-extreme mode?
5815				////$specific_key = (string)$msgball['msgnum'].'_'.$msgball['folder'];
5816				//$specific_key = $msgball['folder'].'_'.(string)$msgball['msgnum'];
5817				//if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete: ('.__LINE__.') (non-extreme mode) extreme or not, "msg_structure" and "phpgw_header" needs expired this specific message leaving this folder, $specific_key ['.$specific_key.'] (but why would that data exist in non-extreme mode?)<br />'); }
5818				//$this->expire_session_cache_item('msg_structure', $msgball['acctnum'], $specific_key);
5819				//$this->expire_session_cache_item('phpgw_header', $msgball['acctnum'], $specific_key);
5820
5821				// folder_status_info in "non-extreme" mode is not saved to appsession, so it does not need expiring
5822				if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete: ('.__LINE__.') (non-extreme mode) in non-extreme mode we do NOT alter the "folder_status_info", in fact "folder_status_info" is not even appsession cached in non-extreme mode <br />'); }
5823
5824				if ($this->debug_events > 0) { $this->dbug->out('mail_msg: event_msg_move_or_delete: ('.__LINE__.') (non-extreme mode) LEAVING, expiring entire msgball list<br />'); }
5825				return True;
5826			}
5827			// IF EXTREME MODE IS OFF, WE SIMPLY EXPIRE THE WHOLE MSGBALL_LIST
5828			// UNLESS POPPING SINGLE ITEMS IS USEFUL ENOUGH TO DO ANYWAY
5829			// lex added this code, angles commented out (oops :(  but I saw what he did and incorporated it above
5830			// however we still need to answer that qestion in cap letters 2 lines up
5831			elseif (($this->session_cache_extreme == True)
5832			&& ($cached_msgball_data))
5833			{
5834				// EXTREME MODE
5835				// directloy manipulate existing cached items to make them "fresh" and resave to cache
5836				if ($this->debug_events > 0) { $this->dbug->out('mail_msg: event_msg_move_or_delete: ('.__LINE__.') (extreme mode) (step 1) pop out a single msgball from the msgball_list and resave to cache<br />'); }
5837				if ($this->debug_events > 2) { $this->dbug->out('mail_msg: event_msg_move_or_delete: ('.__LINE__.') (extreme mode) (step 1) search msgball_list looking for this $msgball DUMP:', $msgball); }
5838				$did_alter = False;
5839				// STEP ONE:
5840				// we should be able to pop out a single msgball from the msg_ball list
5841				// when mail moves OUT of a folder
5842
5843				// NOTE: can not call "read_session_cache_item" because when moving multiple mails, we do not "expunge" until the last one, so validity check will fail because we are *ahead* of the mail server in "freshness"
5844				//$meta_data = $this->read_session_cache_item('msgball_list', $msgball['acctnum']);
5845				// daa... we already got the msgball_list up above
5846				//if ($this->debug_events > 2) { $this->dbug->out('mail_msg: event_msg_move_or_delete: ('.__LINE__.') (extreme mode) (step 1) cached msgball_list $meta_data DUMP<pre>'; print_r($meta_data); echo '</pre>'); }
5847
5848				/*
5849				// get the array index if the msgball we want to delete
5850				// fallback indicator
5851				$found_msgball_idx = $this->not_set;
5852				$loops = count($cached_msgball_data['msgball_list']);
5853				for ($i = 0; $i < $loops; $i++)
5854				{
5855					$this_msgball = $cached_msgball_data['msgball_list'][$i];
5856					if ($this->debug_events > 2) { $this->dbug->out('mail_msg: event_msg_move_or_delete: ('.__LINE__.') (extreme mode) (step 1) cached msgball_list loop ['.$i.'] $this_msgball DUMP<pre>'; print_r($this_msgball); echo '</pre>'); }
5857					if (($this_msgball['acctnum'] == $msgball['acctnum'])
5858					&& ($this_msgball['folder'] == $msgball['folder'])
5859					&& ($this_msgball['msgnum'] == $msgball['msgnum']))
5860					{
5861						$found_msgball_idx = $i;
5862						break;
5863					}
5864				}
5865				if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete: ('.__LINE__.') (extreme mode) (step 1) searched for msgball from the msgball_list, $found_msgball_idx ['.serialize($found_msgball_idx).'] <br />'); }
5866				*/
5867				// get the array index if the msgball we want to delete
5868				if ((!isset($msgball['uri']))
5869				|| (!$msgball['uri']))
5870				{
5871					$msgball['uri'] =
5872						  'msgball[msgnum]='.$msgball['msgnum']
5873						.'&msgball[folder]='.$msgball['folder']
5874						.'&msgball[acctnum]='.$msgball['acctnum'];
5875				}
5876				// get the idx of the msgball if it is in the msgball_list
5877				$found_msgball_idx = array_search($msgball['uri'],$cached_msgball_data['msgball_list']);
5878				if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete: ('.__LINE__.') (extreme mode) (step 1) searched for msgball from the msgball_list, $found_msgball_idx ['.serialize($found_msgball_idx).'] <br />'); }
5879
5880				// if we have an idx, we can delete it
5881				//if ((string)$found_msgball_idx != $this->not_set)
5882				if ($found_msgball_idx === False)
5883				{
5884					// DO NOTHING, we did not get an index value
5885				}
5886				else
5887				{
5888					if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete: ('.__LINE__.') (extreme mode) (step 1) searched SUCCESS, $found_msgball_idx ['.serialize($found_msgball_idx).'] , now doing an ARRAY_SPLICE<br />'); }
5889					array_splice($cached_msgball_data['msgball_list'], $found_msgball_idx, 1);
5890					if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete: ('.__LINE__.') (extreme mode) (step 1) now msgball_list has 1 less item, update msgball_list "vality" data to match this deletion, $cached_msgball_data[validity][folder_status_info][number_all] before '.serialize($cached_msgball_data['validity']['folder_status_info']['number_all']).'<br />'); }
5891					$old_count = (int)$cached_msgball_data['validity']['folder_status_info']['number_all'];
5892					$new_count = ($old_count - 1);
5893					$cached_msgball_data['validity']['folder_status_info']['number_all'] = $new_count;
5894					if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete: ('.__LINE__.') (extreme mode) (step 1) $cached_msgball_data[validity][folder_status_info][number_all] AFTER reduction '.serialize($cached_msgball_data['validity']['folder_status_info']['number_all']).'<br />'); }
5895					if (($this->debug_events > 2) && ($this->debug_allow_magball_list_dumps)) { $this->dbug->out('mail_msg: event_msg_move_or_delete('.__LINE__.'): (extreme mode) (step 1) array_splice of $cached_msgball_data[msgball_list] results in this $cached_msgball_data DUMP:', $cached_msgball_data); }
5896
5897					// save altered data back into the cache
5898					// NOT needed if using a REFERENCE and only using regular appsession (i.e. NOT the anglemail table)
5899					//if (($this->debug_session_caching > 1) || ($this->debug_events > 1)) { $this->dbug->out('mail_msg: event_msg_move_or_delete: ('.__LINE__.') saving altered msgball_list directly to appsession, location: ['.$location.'] $app ['.$app.']<br />'); }
5900					// COMMENT IF USING REF, UNCOMMENT IF NOT USING REFERENCES
5901					//$this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum]['msgball_list'] = $cached_msgball_data;
5902
5903					// for DB sessions_db ONLY
5904					if (($GLOBALS['phpgw_info']['server']['sessions_type'] == 'db')
5905					|| ($this->use_private_table == True))
5906					{
5907						//$my_location = (string)$acctnum.';msgball_list';
5908						// NOW WE USE FOLDER TOO as a data key for msgball_list
5909						$my_location = (string)$acctnum.';msgball_list;'.$ex_folder;
5910						if ($this->debug_events > 1) { $this->dbug->out('email_msg_base: event_msg_move_or_delete('.__LINE__.'): (extreme mode) sessions_type is ['.$GLOBALS['phpgw_info']['server']['sessions_type'].'] SO we have this additional step to save data to a database table, $my_location ['.$my_location.'], if using anglemail table this step is always necessary<br />'); }
5911						if ($this->use_private_table == True)
5912						{
5913							//$this->so->so_set_data($my_location, $this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum]['msgball_list']);
5914							// TRY USING COMPRESSION for msgball_list (only available for anglemail table)
5915							$this->so->so_set_data(
5916								$my_location,
5917								$this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum]['msgball_list'][$ex_folder],
5918								True
5919							);
5920						}
5921						else
5922						{
5923							$GLOBALS['phpgw']->session->appsession($my_location, 'email', $this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum]['msgball_list'][$ex_folder]);
5924						}
5925					}
5926					$did_alter = True;
5927				}
5928			}
5929			// PARTS 2 and 3 -- only attempt if in extreme-mode
5930			if ($this->session_cache_extreme == True)
5931			{
5932				// PART TWO, alter folder status info.
5933				// reduct TOTAL by one, reduce UNDEEN by one if moving an UNSEEN mail
5934				if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete: (extreme mode) (step 2) alter and resave the "folder_status_info" appsession cache<br />'); }
5935
5936				// this grabs from MAILSERVER if required, do we really want this?
5937				//$msg_headers = $GLOBALS['phpgw']->msg->phpgw_header($msgball);
5938				// NO, but we kind of do need this seen vs. unseen data
5939				// if we want to make the folder_status_info accurate to what is actually the stats of the folder on the mailserver
5940				// BUT if this requires is to grab these headers, WE DO NOT GAIN ANY SPEED advantage,
5941				// ONLY do this is the $msg_headers are ALREADY in cache
5942				// this call is OK because it only returns data if it exists, false if not, $extra_keys is FOLDERNAME_MSGNUM
5943				if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete('.__LINE__.'): (extreme mode) check if mail leaving folder was UNSEEN, possible only IF "phpgw_header" is cached, else we loose speed to obtain ot from mailserver<br />'); }
5944				//$extra_keys = $msgball['folder'].'_'.(string)$msgball['msgnum'];
5945				//$msg_headers = $this->read_session_cache_item('phpgw_header', $msgball['acctnum'], $extra_keys);
5946				$msg_headers = $this->read_session_cache_item('phpgw_header', $msgball['acctnum'], $msgball['folder'], $msgball['msgnum']);
5947				if (!$msg_headers)
5948				{
5949					if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete('.__LINE__.'): (extreme mode) NO $msg_headers data was cached, THIS IS NOT ACCURATE but just assume mail leaving folder was NOT recent, NOT unseen, we do not want to contact mailserver that is slow <br />'); }
5950					$reduce_unseen = False;
5951				}
5952				// SEEN OR UNSEEN/NEW test
5953				elseif (($msg_headers->Unseen == 'U') || ($msg_headers->Recent == 'N'))
5954				{
5955					if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete('.__LINE__.'): (extreme mode) msg_headers indicate mail leaving folder was UNSEEN <br />'); }
5956					$reduce_unseen = True;
5957				}
5958				else
5959				{
5960					if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete('.__LINE__.'): (extreme mode) msg_headers indicate mail leaving folder was NOT recent, NOT unseen <br />'); }
5961					$reduce_unseen = False;
5962				}
5963
5964
5965				//$did_alter = False;
5966				if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete('.__LINE__.'): (extreme mode) (step 2) grabbing folder_status_info DIRECTLY from appsession, <br /> * can not call "read_session_cache_item" because when moving multiple mails, we do not "expunge" until the last one, so validity check will fail because we are *ahead* of the mail server in "freshness"<br />'); }
5967				$acctnum = $msgball['acctnum'];
5968				$ex_folder = $msgball['folder'];
5969				$ex_msgnum = $msgball['msgnum'];
5970				$data_name = 'folder_status_info';
5971				// get session data
5972				$folder_status_info = array();
5973				//$folder_status_info = $GLOBALS['phpgw']->session->appsession($location,$app);
5974				// for DB sessions_db ONLY
5975				if (($GLOBALS['phpgw_info']['server']['sessions_type'] == 'db')
5976				|| ($this->use_private_table == True))
5977				{
5978					$my_location = (string)$acctnum.';folder_status_info;'.$ex_folder;
5979					if ($this->debug_events > 1) { $this->dbug->out('email_msg_base: event_msg_move_or_delete('.__LINE__.'): (extreme mode) sessions_type is ['.$GLOBALS['phpgw_info']['server']['sessions_type'].'] SO we have this additional step to read data from phpgw_app_sessions table, $my_location ['.$my_location.']<br />'); }
5980					if ($this->use_private_table == True)
5981					{
5982						$this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum]['folder_status_info'][$ex_folder]
5983							= $this->so->so_get_data($my_location);
5984					}
5985					else
5986					{
5987						$this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum]['folder_status_info'][$ex_folder]
5988							= $GLOBALS['phpgw']->session->appsession($my_location, 'email');
5989					}
5990					//if ($this->debug_events > 2) { $this->dbug->out('email_msg_base: event_msg_move_or_delete('.__LINE__.'): (extreme mode) [email][dat]['.$acctnum.'][folder_status_info]['.$ex_folder.'] DUMP:<pre>'; print_r($this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum]['folder_status_info'][$ex_folder]); echo '</pre>'); }
5991				}
5992
5993				$folder_status_info = $this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum]['folder_status_info'][$ex_folder];
5994
5995				//if ($this->debug_events > 1) { $this->dbug->out('mail_msg_base: event_msg_move_or_delete('.__LINE__.'): (extreme mode) (step 2) session ['.$location.','.$app.'] grabbed $meta_data serialized DUMP <pre>'; echo "\r\n".serialize($meta_data)."\r\n"; echo '</pre>'); }
5996				if ($this->debug_events > 1) { $this->dbug->out('mail_msg_base: event_msg_move_or_delete('.__LINE__.'): (extreme mode) (step 2) session ['.$location.','.$app.'] grabbed $folder_status_info DUMP:', $folder_status_info); }
5997
5998				if (!$folder_status_info)
5999				{
6000					if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete: (extreme mode) (step 2) no cached "folder_status_info" exists<br />'); }
6001				}
6002				else
6003				{
6004					if ($this->debug_events > 2) { $this->dbug->out('mail_msg: event_msg_move_or_delete: (extreme mode) (step 2) unaltered cached msgball_list $meta_data DUMP:', $meta_data); }
6005					// reducr NUMBER ALL - obviously if mail is leaving a folder, number_all must be reduced
6006					if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete: (extreme mode) reducing "folder_status_info" number_all count<br />'); }
6007					$prev_total_count = $folder_status_info['number_all'];
6008					$adjusted_total_count = ($prev_total_count - 1);
6009					$folder_status_info['number_all'] = $adjusted_total_count;
6010
6011					// reduce UNSEEN if necessary
6012					if ($reduce_unseen == True)
6013					{
6014						if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete: (extreme mode) reducing "folder_status_info" UNSEEN count<br />'); }
6015						$prev_new_count = $folder_status_info['number_new'];
6016						$adjusted_new_count = ($prev_new_count - 1);
6017						$folder_status_info['number_new'] = $adjusted_new_count;
6018
6019						// the user alert string needs updating also
6020						$folder_status_info['alert_string'] = str_replace((string)$prev_new_count, (string)$adjusted_new_count, $folder_status_info['alert_string']);
6021					}
6022
6023					// save altered data back into the cache
6024					if ($this->debug_events > 2) { $this->dbug->out('mail_msg: event_msg_move_or_delete: (extreme mode) (step 2) ADJUSTED $folder_status_info DUMP:', $folder_status_info); }
6025					// the $location we used above is still usable
6026					if (($this->debug_session_caching > 1) || ($this->debug_events > 1)) { $this->dbug->out('mail_msg: event_msg_move_or_delete: saving altered folder_status_info **directly** to appsession, $location: ['.$location.'] $app['.$app.']<br />'); }
6027					//$GLOBALS['phpgw']->session->appsession($location,$app,$folder_status_info);
6028					$this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum]['folder_status_info'][$ex_folder] = $folder_status_info;
6029					// for DB sessions_db ONLY
6030					if (($GLOBALS['phpgw_info']['server']['sessions_type'] == 'db')
6031					|| ($this->use_private_table == True))
6032					{
6033						$my_location = (string)$acctnum.';folder_status_info;'.$ex_folder;
6034						if ($this->debug_events > 1) { $this->dbug->out('email_msg_base: event_msg_move_or_delete('.__LINE__.'): (extreme mode) sessions_type is ['.$GLOBALS['phpgw_info']['server']['sessions_type'].'] SO we have this additional step to save data to phpgw_app_sessions table, $my_location ['.$my_location.']<br />'); }
6035						if ($this->use_private_table == True)
6036						{
6037							$this->so->so_set_data($my_location, $this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum]['folder_status_info'][$ex_folder]);
6038						}
6039						else
6040						{
6041							$GLOBALS['phpgw']->session->appsession($my_location, 'email', $this->ref_SESSION['phpgw_session']['phpgw_app_sessions']['email']['dat'][$acctnum]['folder_status_info'][$ex_folder]);
6042						}
6043					}
6044					$did_alter = True;
6045				}
6046
6047				// PART3 - THINGS NECESSARY DURING ANY DELETE
6048				// ANYTIME a message is moved out of a folder, we need to remove any cached "msg_structure" and "phpgw_header" data
6049				$ex_folder = $msgball['folder'];
6050				$ex_msgnum = $msgball['msgnum'];
6051				if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete('.__LINE__.'): (step 3a) (extreme mode) extreme or not, "msg_structure" and "phpgw_header" needs expired this specific message leaving this folder, $extra_keys ['.$extra_keys.']<br />'); }
6052				// we got this above, so since we have it, we test if it existed before we try to expire it
6053				if ($msg_headers)
6054				{
6055					$this->expire_session_cache_item('phpgw_header', $msgball['acctnum'], $ex_folder, $ex_msgnum);
6056				}
6057				// we do not know if this exists or not right now, so just call expire, if it exists it will be erased
6058				$this->expire_session_cache_item('msg_structure', $msgball['acctnum'], $ex_folder, $ex_msgnum);
6059
6060
6061				if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete('.__LINE__.'): (step 3b) (extreme mode) IF a target folder is provided and is a valid folder name, EXPIRE the "folder_status_info" for that TARGET folder, $to_fldball ['.serialize($to_fldball).']<br />'); }
6062				//if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete: (step 3) (extreme mode) <b>DISABLED, problem expiring current status info</b> "folder_status_info" for the TARGET folder (if known) needs expired, I will not go to the brain-damaging extent of adjusting Target folder stats, $to_fldball ['.serialize($to_fldball).']<br />'); }
6063				if ((isset($to_fldball['folder']))
6064				&& (isset($to_fldball['acctnum']))
6065				&& ($to_fldball['folder'] != $this->del_pseudo_folder))
6066				{
6067					$target_clean = $this->prep_folder_in($to_fldball['folder']);
6068					$urlencoded_target = $this->prep_folder_out($target_clean);
6069					// make sure that $to_fldball['folder'] is in PREPPED_OUT encoded
6070					$to_fldball['folder'] = $urlencoded_target;
6071					if ($this->debug_events > 0) { $this->dbug->out('mail_msg: event_msg_move_or_delete: ('.__LINE__.') (step 3b) (extreme mode) prepped $target_clean ['.$target_clean.'], $urlencoded_target ['.$urlencoded_target.']; $to_fldball ['.serialize($to_fldball).']<br />'); }
6072					if ((isset($target_clean))
6073					|| (trim($target_clean) != ''))
6074					{
6075						if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete('.__LINE__.'): (step 3b) (extreme mode) <b>expiring current status info</b> "folder_status_info" for the TARGET folder (was provided and it exists) needs expired, I will not go to the brain-damaging extent of adjusting Target folder stats, $to_fldball ['.serialize($to_fldball).']<br />'); }
6076						if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete: ('.__LINE__.') (step 3b) (extreme mode) TARGET folder data was provided, MUST expire target folders "folder_status_info", $to_fldball[acctnum] is ['.$acctnum.'], $ex_folder is urlencoded target folder name ['.$urlencoded_target.']<br />'); }
6077						$this->expire_session_cache_item('folder_status_info', $to_fldball['acctnum'], $urlencoded_target);
6078					}
6079					else
6080					{
6081						if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete('.__LINE__.'): (step 3b) (extreme mode) can not do this step3b because TARGET FOLDER data was provided BUT empty $target_clean ['.$target_clean.'] indicates we could not verify it is a known valid folder, $to_fldball ['.serialize($to_fldball).']<br />'); }
6082					}
6083				}
6084				else
6085				{
6086					if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_move_or_delete('.__LINE__.'): (step 3b) (extreme mode) can not do this step3 because TARGET FOLDER data was NOT provided OR the folder is $this->del_pseudo_folder: ['.$this->del_pseudo_folder.'], note data for $to_fldball was ['.serialize($to_fldball).']<br />'); }
6087				}
6088
6089				if ($this->debug_events > 0) { $this->dbug->out('mail_msg: event_msg_move_or_delete('.__LINE__.'): (extreme mode) LEAVING, $did_alter ['.serialize($did_alter).']<br />'); }
6090				return $did_alter;
6091			}
6092
6093			if ($this->debug_events > 0) { $this->dbug->out('mail_msg: event_msg_move_or_delete('.__LINE__.'): LEAVING, unhandled situation or caching not enabled<br />'); }
6094			return False;
6095		}
6096
6097		/*!
6098		@function event_msg_append
6099		@abstract when a message is appended to a folder
6100		@author Angles
6101		@discussion ?
6102		*/
6103		function event_msg_append($target_fldball='', $called_by='not_specified')
6104		{
6105			if ($this->debug_events > 0) { $this->dbug->out('mail_msg: event_msg_append: ENTERING<br />'); }
6106			if ($this->debug_events > 0) { $this->dbug->out('mail_msg: event_msg_append: DISABLED UNTIL I FIGURE OUT HOW NOT TO EXPIRE CURRENT FOLDER STATS WHEN PASSED NOT ENOUGH INFO<br />'); }
6107
6108			$did_expire = False;
6109			/*
6110			$current_fldball = array();
6111			$current_fldball['folder'] = $this->get_arg_value('folder');
6112			$current_fldball['acctnum'] = $this->get_acctnum();
6113
6114			if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_append: we expire ONLY if $current_fldball ['.serialize($current_fldball).'] == $target_fldball ['.serialize($target_fldball).']<br />'); }
6115			if (($target_fldball['folder'] == $current_fldball['folder'])
6116			&& ($target_fldball['acctnum'] == $current_fldball['acctnum']))
6117			{
6118				if ($this->debug_events > 1) { $this->dbug->out('mail_msg: event_msg_append: we MUST expire "msgball_list" because $current_fldball == $target_fldball<br />'); }
6119				$this->expire_session_cache_item('msgball_list', $target_fldball['acctnum']);
6120				$did_expire = True;
6121			}
6122			*/
6123			if ($this->debug_events > 0) { $this->dbug->out('mail_msg: event_msg_append: LEAVING, returning $did_expire ['.serialize($did_expire).']<br />'); }
6124			return $did_expire;
6125		}
6126
6127		/**************************************************************************\
6128		* USEFUL  AND   SIMPLE  HTML  FUNCTIONS	*
6129		\**************************************************************************/
6130
6131		/*!
6132		@function href_maketag
6133		@abstract will generate a typical A HREF html item
6134		*/
6135		function href_maketag($href_link='',$href_text='default text')
6136		{
6137			return '<a href="' .$href_link .'">' .$href_text .'</a>' ."\n";
6138		}
6139
6140		/*!
6141		@function href_maketag_class
6142		@abstract will generate a typical A HREF html item with optional CLASS value for css specs
6143		*/
6144		function href_maketag_class($href_link='',$href_text='default text', $css_class_name='')
6145		{
6146			if ($css_class_name != '')
6147			{
6148				$class_prop=' class="'.$css_class_name.'" ';
6149			}
6150			else
6151			{
6152				$class_prop='';
6153			}
6154			return '<a '.$class_prop.' href="' .$href_link .'">' .$href_text .'</a>' ."\n";
6155		}
6156
6157		/*!
6158		@function img_maketag
6159		@abstract will generate a typical IMG html item
6160		*/
6161		function img_maketag($location='',$alt='',$height='',$width='',$border='')
6162		{
6163			$alt_default_txt = 'image';
6164			$alt_unknown_txt = 'unknown';
6165			if ($location == '')
6166			{
6167				return '<img src="" alt="['.$alt_unknown_txt.']">';
6168			}
6169			if ($alt != '')
6170			{
6171				$alt_tag = ' alt="['.$alt.']"';
6172				$title_tag = ' title="'.$alt.'"';
6173			}
6174			else
6175			{
6176				$alt_tag = ' alt="['.$alt_default_txt.']"';
6177				$title_tag = '';
6178			}
6179			if ($height != '')
6180			{
6181				$height_tag = ' height="' .$height .'"';
6182			}
6183			else
6184			{
6185				$height_tag = '';
6186			}
6187			if ($width != '')
6188			{
6189				$width_tag = ' width="' .$width .'"';
6190			}
6191			else
6192			{
6193				$width_tag = '';
6194			}
6195			if ($border != '')
6196			{
6197				$border_tag = ' border="' .$border .'"';
6198			}
6199			else
6200			{
6201				$border_tag = '';
6202			}
6203			$image_html = '<img src="'.$location.'"' .$height_tag .$width_tag .$border_tag .$title_tag .$alt_tag .'>';
6204			return $image_html;
6205		}
6206
6207	}
6208	// end of class mail_msg
6209?>
6210