1<?php
2	/**************************************************************************\
3	* AngleMail - E-Mail Filters									*
4	* http://www.anglemail.org									*
5	* Written by Angelo (Angles) Puglisi <angles@aminvestments.com>		*
6	* Copyright (C) 2001, 2002 Angelo Puglisi (Angles)					*
7	* -----------------------------------------------                         				*
8	*  This program is free software; you can redistribute it and/or modify it		*
9	*  under the terms of the GNU General Public License as published by the	*
10	*  Free Software Foundation; either version 2 of the License, or (at your		*
11	*  option) any later version.										*
12	\**************************************************************************/
13
14	/* $Id: class.bofilters.inc.php 15464 2004-11-06 16:13:49Z powerstat $ */
15
16	/*!
17	@class bofilters
18	@abstract BO functions for email filters
19	@author Angles
20	@param $not_set (string) always a string that it "-1"
21	@param $all_filters (array)
22	@param $filter_num (int)
23	@param $add_new_filter_token (string)  "add_new"
24	@param $template (object)
25	@param $finished_mlist ?
26	@param $submit_mlist_to_class_form ?
27	@param $debug (int) 0 to 3
28	@param $debug_set_prefs (int) 0 to 3
29	@param $examine_imap_search_keys_map (array)
30	@param $match_keeper_row_values (array)
31	@param $result_set (array)
32	@param $result_set_mlist (array)
33	@param $fake_folder_info (array)
34	@param $do_filter_apply_all (boolean)
35	@param $inbox_full_msgball_list (array)
36	@param $each_filter_mball_list  (array)
37	@param $html_matches_table (string)
38	@access pubic
39	*/
40	class bofilters
41	{
42		var $public_functions = array(
43			'process_submitted_data'	=> True,
44			'delete_filter'	=> True,
45			'do_filter'	=> True,
46			'move_up'	=> True,
47			'move_down'	=> True
48		);
49
50		var $not_set='-1';
51		var $all_filters = Array();
52		var $filter_num = 0;
53		//var $this_filter = Array();
54		var $add_new_filter_token = 'add_new';
55		var $template = '';
56		var $finished_mlist = '';
57		var $submit_mlist_to_class_form = '';
58		var $debug = 0;
59		var $debug_set_prefs = 0;
60		var $examine_imap_search_keys_map=array();
61		var $match_keeper_row_values=array();
62		var $result_set = Array();
63		var $result_set_mlist = Array();
64		var $fake_folder_info = array();
65
66		var $do_filter_apply_all = True;
67		var $inbox_full_msgball_list = array();
68		//var $each_row_result_mball_list = array();
69		//var $each_acct_final_mball_list = array();
70		var $each_filter_mball_list = array();
71		var $html_matches_table = '';
72
73		/*!
74		@function bofilters
75		@abstract constructor
76		@discussion Several important data structures are initialized here, including this
77		constructor calls the member function "read_filter_data_from_prefs" passing param to
78		$also_undo_defang as True only if the string "uifilters" is NOT in the menuaction. The UI
79		forms need the defanging to remain intact, BUT if "uifilters" is not in the menuaction,
80		then we assume we are going to apply or otherwise use the filters requiring the actual
81		unencoded chars, in which case function "read_filter_data_from_prefs" is passed param
82		also_undo_defang as True.
83		@author Angles
84		*/
85		function bofilters()
86		{
87			if ($this->debug > 0) { echo 'email.bofilters *constructor*: ENTERING <br />'; }
88
89			define('F_ROW_0_MATCH',1);
90			define('F_ROW_1_MATCH',2);
91			define('F_ROW_2_MATCH',4);
92			define('F_ROW_3_MATCH',8);
93
94			$this->examine_imap_search_keys_map = Array(
95				'from'		=> 'FROM',
96				'to'		=> 'TO',
97				'cc'		=> 'CC',
98				'bcc'		=> 'BCC',
99				'recipient'	=> 'RECIPIENT',
100				'sender'	=> 'SENDER',
101				'subject'	=> 'SUBJECT',
102				'received'	=> 'RECEIVED',
103				'header'	=> 'FIX_ME SEARCHHEADER FIX_ME',
104				'size_larger'	=> 'FIX_ME LARGER',
105				'size_smaller'	=> 'FIX_ME SMALLER',
106				'allmessages'	=> 'FIX_ME (matches all messages)',
107				'body'		=> 'FIX_ME BODY'
108			);
109
110			$this->match_keeper_row_values = Array(
111				0	=>	F_ROW_0_MATCH,
112				1	=>	F_ROW_1_MATCH,
113				2	=>	F_ROW_2_MATCH,
114				3	=>	F_ROW_3_MATCH
115			);
116
117
118			// make sure we have msg object
119			$this->msg_bootstrap = CreateObject("email.msg_bootstrap");
120			// should we log in or not, no, we only need prefs initialized
121			// if any data is needed mail_msg will open stream for us
122			// UPDATE: extreme caching takes care of the login / no login issue
123			//$this->msg_bootstrap->set_do_login(False);
124			// USE NEW login instructions, defined in bootstrap class
125			$this->msg_bootstrap->set_do_login(BS_LOGIN_ONLY_IF_NEEDED);
126			$this->msg_bootstrap->ensure_mail_msg_exists('email.bofilters *constructor*', $this->debug);
127
128			$this->not_set = $GLOBALS['phpgw']->msg->not_set;
129			// when we get filter data from database, we undo the DB defang ONLY is we are going to USE the filters
130			// because if only displaying the filter data in a form, the data needs to remain html encoded
131			if (isset($GLOBALS['phpgw']->msg->ref_GET['menuaction']))
132			{
133				$my_menuaction = $GLOBALS['phpgw']->msg->ref_GET['menuaction'];
134			}
135			elseif (isset($GLOBALS['phpgw']->msg->ref_POST['menuaction']))
136			{
137				$my_menuaction = $GLOBALS['phpgw']->msg->ref_POST['menuaction'];
138			}
139			else
140			{
141				$my_menuaction = 'error: none found';
142			}
143			if ($this->debug > 0) { echo 'email.bofilters. *constructor*('.__LINE__.'): $my_menuaction ['.$my_menuaction.']<br />'; }
144
145			if (stristr($my_menuaction, 'email.uifilter'))
146			{
147				if ($this->debug > 0) { echo 'email.bofilters. *constructor*('.__LINE__.'): GPC menuaction indicates this is a UI call, NOT applying filters, so do NOT html decode pref filter data<br />'; }
148				$also_undo_defang = False;
149			}
150			else
151			{
152				if ($this->debug > 0) { echo 'email.bofilters. *constructor*('.__LINE__.'): GPC menuaction indicates this is NOT simply a UI call, so DO html decode (defang) pref filter data<br />'; }
153				$also_undo_defang = True;
154			}
155
156			if ($this->debug > 0) { echo 'email.bofilters. *constructor*: calling $this->read_filter_data_from_prefs('.serialize($also_undo_defang).')<br />'; }
157			$this->read_filter_data_from_prefs($also_undo_defang);
158			if ($this->debug > 0) { echo 'email.bofilters. *constructor*: LEAVING<br />'; }
159			//return;
160		}
161
162		/*!
163		@function read_filter_data_from_prefs
164		@abstract MISNAMED because ->msg actually reads the prefs, and we get them from ->msg->raw_filters
165		@param $also_undo_defang (boolean) also undo the html encoding of offending chars needed during pref table storage.
166		Default is empty or False, meaining to leave the encoded chars as encoded, useful for displaying the data. To
167		actually apply the filters, you MUST pass True here, so the chars are decoded to their actual char value.
168		@discussion Use to obtain the raw, unprocessed filters array as extracted from the prefs database. In this case
169		we simple get the array from GLOBALS[phpgw]->msg->raw_filters becauase the ->msg object actually
170		gets the prefs from the database and the constructor for this class has a msg bootstrap call so we know we
171		have a msg object to use, hopefully. Also, there is a fallback location to find the data,
172		GLOBALS[phpgw]->preferences->data[email][filters] but this is NOT the best way to do it since that is
173		potentially "private" data of the preferences object, but since php as of now has no "private" data enviornment, I am guessing.
174		NOTE that prefs data is stored in database friendly "defanged" mode where certain offending chars are html encoded,
175		during this function that encoding is UNDONE, the chars are returned to their actual state as slashes or quotes, etc.
176		@author Angles
177		*/
178		function read_filter_data_from_prefs($also_undo_defang='')
179		{
180			if ($this->debug > 0) { echo 'bofilters.read_filter_data_from_prefs('.__LINE__.'): ENTERING, param $also_undo_defang ['.serialize($also_undo_defang).']<br />'."\r\n"; }
181			/*
182			$this->all_filters = array();
183			// read sublevel data from prefs
184			// since we know the constructor called begin_request, we know we can get that data here:
185			if ((isset($GLOBALS['phpgw']->msg->unprocessed_prefs['email']['filters']))
186			&& (is_array($GLOBALS['phpgw']->msg->unprocessed_prefs['email']['filters']))
187			&& (count($GLOBALS['phpgw']->msg->unprocessed_prefs['email']['filters']) > 0)
188			&& (isset($GLOBALS['phpgw']->msg->unprocessed_prefs['email']['filters'][0]['source_accounts'])))
189			{
190				$this->all_filters = $GLOBALS['phpgw']->msg->unprocessed_prefs['email']['filters'];
191			}
192			return $this->all_filters;
193			*/
194
195			// METHOD1 - uses email msg objects "raw_filters" array
196			$this->all_filters = array();
197			if ((isset($GLOBALS['phpgw']->msg->raw_filters))
198			&& (is_array($GLOBALS['phpgw']->msg->raw_filters)))
199			{
200				$this->all_filters = $GLOBALS['phpgw']->msg->raw_filters;
201			}
202			// fallback location to try also
203			elseif ((isset($GLOBALS['phpgw']->preferences->data['email']['filters']))
204			&& (is_array($GLOBALS['phpgw']->preferences->data['email']['filters'])))
205			{
206				// METHOD2 (works but requires "access" to a maybe private object of prefernces object, so 2nd choice for data)
207				$this->all_filters = $GLOBALS['phpgw']->preferences->data['email']['filters'];
208			}
209			// UNDO the DATABASE DEFANG if instructions specified this
210			if ($also_undo_defang)
211			{
212				if ($this->debug > 1) { echo 'bofilters.read_filter_data_from_prefs('.__LINE__.'): about to call $this->all_filters_bulk_undo_defang because param $also_undo_defang is ['.serialize($also_undo_defang).']<br />'."\r\n"; }
213				$this->all_filters_bulk_undo_defang();
214			}
215			else
216			{
217				if ($this->debug > 1) { echo 'bofilters.read_filter_data_from_prefs('.__LINE__.'): leaving html encoded chars AS-IS because param $also_undo_defang is ['.serialize($also_undo_defang).']<br />'."\r\n"; }
218			}
219			if ($this->debug > 2) { echo 'bofilters.read_filter_data_from_prefs('.__LINE__.'): obtained $this->all_filters DUMP:<pre>'; print_r($this->all_filters); echo '</pre>'."\r\n"; }
220			if ($this->debug > 0) { echo 'bofilters.read_filter_data_from_prefs('.__LINE__.'): LEAVING <br />'."\r\n"; }
221			return $this->all_filters;
222		}
223
224		/*!
225		@function obtain_filer_num
226		@abstract ?
227		@param $get_next_avail_if_none (boolean) default True
228		@author Angles
229		*/
230		function obtain_filer_num($get_next_avail_if_none=True)
231		{
232			if ($this->debug > 0) { echo 'bofilters.obtain_filer_num: ENTERING ; $get_next_avail_if_none : [<code>'.serialize($get_next_avail_if_none).'</code>]<br />'."\r\n"; }
233			if (isset($GLOBALS['phpgw']->msg->ref_POST['filter_num']))
234			{
235				if ($GLOBALS['phpgw']->msg->ref_POST['filter_num'] == $this->add_new_filter_token)
236				{
237					$filter_num = $this->get_next_avail_num();
238				}
239				else
240				{
241					$filter_num = $GLOBALS['phpgw']->msg->ref_POST['filter_num'];
242					$filter_num = (int)$filter_num;
243				}
244			}
245			elseif (isset($GLOBALS['phpgw']->msg->ref_GET['filter_num']))
246			{
247				if ($GLOBALS['phpgw']->msg->ref_GET['filter_num'] == $this->add_new_filter_token)
248				{
249					$filter_num = $this->get_next_avail_num();
250				}
251				else
252				{
253					$filter_num = $GLOBALS['phpgw']->msg->ref_GET['filter_num'];
254					$filter_num = (int)$filter_num;
255				}
256			}
257			elseif($get_next_avail_if_none == True)
258			{
259				$filter_num = $this->get_next_avail_num();
260			}
261			else
262			{
263				$filter_num = $this->not_set;
264			}
265			if ($this->debug > 0) { echo 'bofilters.obtain_filer_num: LEAVING ; returning $filter_num : [<code>'.serialize($filter_num).'</code>]<br />'."\r\n"; }
266			return $filter_num;
267		}
268
269		/*!
270		@function get_next_avail_num
271		@abstract ?
272		@author Angles
273		*/
274		function get_next_avail_num()
275		{
276			return count($this->all_filters);
277		}
278
279		/*!
280		@function just_testing
281		@abstract ?
282		@author Angles
283		*/
284		function just_testing()
285		{
286			if ((isset($GLOBALS['phpgw']->msg->ref_POST['filter_test']))
287			&& ((string)$GLOBALS['phpgw']->msg->ref_POST['filter_test'] != ''))
288			{
289				$just_testing = True;
290			}
291			elseif ((isset($GLOBALS['phpgw']->msg->ref_GET['filter_test']))
292			&& ((string)$GLOBALS['phpgw']->msg->ref_GET['filter_test'] != ''))
293			{
294				$just_testing = True;
295			}
296			else
297			{
298				$just_testing = False;
299			}
300			return $just_testing;
301		}
302
303		/*!
304		@function filter_exists
305		@abstract ?
306		@author Angles
307		*/
308		function filter_exists($feed_filter_num)
309		{
310			$feed_filter_num = (int)$feed_filter_num;
311			if ((isset($this->all_filters[$feed_filter_num]))
312			&& (isset($this->all_filters[$feed_filter_num]['source_accounts'])))
313			{
314				return True;
315			}
316			else
317			{
318				return False;
319			}
320		}
321
322		/*!
323		@function move_up
324		@abstract ?
325		@author Angles
326		*/
327		function move_up()
328		{
329			// "False" means  return $this->not_set  if no filter number was found anywhere
330			$found_filter_num = $this->obtain_filer_num(False);
331			if ($this->debug > 1) { echo 'bofilters.move_up: $found_filter_num : [<code>'.serialize($found_filter_num).'</code>]<br />'."\r\n"; }
332
333			if ($found_filter_num == $this->not_set)
334			{
335				if ($this->debug > 0) { echo 'bofilters.move_up: LEAVING with error, no filter num was found<br />'."\r\n"; }
336				return False;
337			}
338			elseif($this->filter_exists($found_filter_num) == False)
339			{
340				if ($this->debug > 0) { echo 'bofilters.move_up: LEAVING with error, filter $found_filter_num [<code>'.serialize($found_filter_num).'</code>] does not exist<br />'."\r\n"; }
341				return False;
342			}
343			elseif((string)$found_filter_num == '0')
344			{
345				if ($this->debug > 0) { echo 'bofilters.move_up: LEAVING with error, filter $found_filter_num [<code>'.serialize($found_filter_num).'</code>] can not be moved up<br />'."\r\n"; }
346				return False;
347			}
348			// if we get here we need to move up this filter
349			$take_my_position = $this->all_filters[$found_filter_num-1];
350			$im_moving_up = $this->all_filters[$found_filter_num];
351			$this->all_filters[$found_filter_num-1] = array();
352			$this->all_filters[$found_filter_num-1] = $im_moving_up;
353			$this->all_filters[$found_filter_num] = array();
354			$this->all_filters[$found_filter_num] = $take_my_position;
355			$this->save_all_filters_to_repository();
356			// redirect user back to filters list page
357			$take_me_to_url = $GLOBALS['phpgw']->link(
358										'/index.php',
359										'menuaction=email.uifilters.filters_list');
360			if ($this->debug > 0 || $this->debug_set_prefs > 0 ) { echo 'bofilters.move_up: LEAVING with redirect to: <br />'.$take_me_to_url.'<br />'; }
361			Header('Location: ' . $take_me_to_url);
362		}
363
364		/*!
365		@function move_down
366		@abstract ?
367		@author Angles
368		*/
369		function move_down()
370		{
371			// "False" means  return $this->not_set  if no filter number was found anywhere
372			$found_filter_num = $this->obtain_filer_num(False);
373			if ($this->debug > 1) { echo 'bofilters.move_down: $found_filter_num : [<code>'.serialize($found_filter_num).'</code>]<br />'."\r\n"; }
374
375			if ($found_filter_num == $this->not_set)
376			{
377				if ($this->debug > 0) { echo 'bofilters.move_down: LEAVING with error, no filter num was found<br />'."\r\n"; }
378				return False;
379			}
380			elseif($this->filter_exists($found_filter_num) == False)
381			{
382				if ($this->debug > 0) { echo 'bofilters.move_down: LEAVING with error, filter $found_filter_num [<code>'.serialize($found_filter_num).'</code>] does not exist<br />'."\r\n"; }
383				return False;
384			}
385			elseif($found_filter_num == (count($this->all_filters)-1))
386			{
387				if ($this->debug > 0) { echo 'bofilters.move_down: LEAVING with error, filter $found_filter_num [<code>'.serialize($found_filter_num).'</code>] can not be moved down<br />'."\r\n"; }
388				return False;
389			}
390			// if we get here we need to move up this filter
391			$take_my_position = $this->all_filters[$found_filter_num+1];
392			$im_moving_down = $this->all_filters[$found_filter_num];
393			$this->all_filters[$found_filter_num+1] = array();
394			$this->all_filters[$found_filter_num+1] = $im_moving_down;
395			$this->all_filters[$found_filter_num] = array();
396			$this->all_filters[$found_filter_num] = $take_my_position;
397			$this->save_all_filters_to_repository();
398			// redirect user back to filters list page
399			$take_me_to_url = $GLOBALS['phpgw']->link(
400										'/index.php',
401										'menuaction=email.uifilters.filters_list');
402			if ($this->debug_set_prefs > 0) { echo 'bofilters.move_down: LEAVING with redirect to: <br />'.$take_me_to_url.'<br />'; }
403			Header('Location: ' . $take_me_to_url);
404		}
405
406		/*!
407		@function all_filters_bulk_undo_defang
408		@abstract Used on the filter data as a whole, every filter is examined for html encoded DB-Friendly chars, and they are DECODED to their actual char state.
409		@result boolean, True is we actually decoded something, False is no data required decoding.
410		@discussion This is an OOP object call, operates directly on this->all_filters[].
411		Use this function when you are going to actually APPLY the filters, in that case the data
412		MUST be NON-ENCODED in order to match up against the message strings. However, this should NOT be done
413		when simply displaying the pref data, because the html form actually needs these chars to be html encoded.
414		For example, a trailing quote char will actually look like the end of the value quote to the browser, so will
415		not actually be seen, because it was mis-interpreted by the html code. In fact it will disappear since the
416		browser thinks it is part of the markup, so you must leave it html encoded.
417		@author Angles
418		*/
419		function all_filters_bulk_undo_defang()
420		{
421			if ($this->debug > 0) { echo 'bofilters.all_filters_bulk_undo_defang('.__LINE__.'): ENTERING<br />'."\r\n"; }
422			$did_decode = False;
423			if (!$this->all_filters)
424			{
425				if ($this->debug > 0) { echo 'bofilters.all_filters_bulk_undo_defang('.__LINE__.'): LEAVING early, nothing to process, $this->all_filters is empty, returning $did_decode ['.serialize($did_decode).']<br />'."\r\n"; }
426				return $did_decode;
427			}
428			// UNDO the DATABASE DEFANG,
429			if ($this->debug > 1) { echo 'bofilters.all_filters_bulk_undo_defang('.__LINE__.'): about to UNDO the pref friendly defanged chars, so the the html encoding of certain offending chars prefs is UNDONE here<br />'."\r\n"; }
430			$did_decode = False;
431			for ($filter_idx=0; $filter_idx < count($this->all_filters); $filter_idx++)
432			{
433				// currently only 2 elements get the defang, undefang treatment
434				// 1. filtername
435				$refanged_filtername = $this->string_undo_defang($this->all_filters[$filter_idx]['filtername']);
436				if ($this->debug > 1) { echo 'bofilters.all_filters_bulk_undo_defang('.__LINE__.'): still defanged $this->all_filters['.$filter_idx.'][filtername] is ['.serialize($this->all_filters[$filter_idx]['filtername']).'], RE-fanged $refanged_filtername ['.serialize($refanged_filtername).']<br />'."\r\n"; }
437				if ($refanged_filtername != $this->all_filters[$filter_idx]['filtername'])
438				{
439					$did_decode = True;
440				}
441				$this->all_filters[$filter_idx]['filtername'] = $refanged_filtername;
442				// 2. each [matches][x][matchthis]
443				for ($matches_idx=0; $matches_idx < count($this->all_filters[$filter_idx]['matches']); $matches_idx++)
444				{
445					$refanged_matchthis = $this->string_undo_defang($this->all_filters[$filter_idx]['matches'][$matches_idx]['matchthis']);
446					if ($this->debug > 1) { echo 'bofilters.all_filters_bulk_undo_defang('.__LINE__.'): still defanged $this->all_filters['.$filter_idx.'][matches]['.$matches_idx.'][matchthis] is ['.serialize($this->all_filters[$filter_idx]['matches'][$matches_idx]['matchthis']).'], RE-fanged $refanged_matchthis ['.serialize($refanged_matchthis).']<br />'."\r\n"; }
447					if ($refanged_matchthis != $this->all_filters[$filter_idx]['matches'][$matches_idx]['matchthis'])
448					{
449						$did_decode = True;
450					}
451					$this->all_filters[$filter_idx]['matches'][$matches_idx]['matchthis'] = $refanged_matchthis;
452				}
453			}
454			if ($this->debug > 2) { echo 'bofilters.all_filters_bulk_undo_defang('.__LINE__.'): defanged $this->all_filters DUMP:<pre>'; print_r($this->all_filters); echo '</pre>'."\r\n"; }
455			if ($this->debug > 0) { echo 'bofilters.all_filters_bulk_undo_defang('.__LINE__.'): LEAVING, returning $did_decode ['.serialize($did_decode).']<br />'."\r\n"; }
456			return $did_decode;
457		}
458
459		/*!
460		@function string_undo_defang
461		@abstract a REVERSE of the prefs database defang treatment.  Opposite of "string_strip_and_defang".
462		@author Angles
463		*/
464		function string_undo_defang($pref_string='')
465		{
466			if ($this->debug_set_prefs > 0) { echo 'bofilters.string_undo_defang('.__LINE__.'): ENTERING, param $pref_string ['.serialize($pref_string).']<br />'."\r\n"; }
467			if (!$pref_string)
468			{
469				return '';
470			}
471			// undo the _LAME_ way to make the value "database friendly"
472			// return slashes and quotes to their actual form as slashes and quotes
473			$un_defanged_string = $GLOBALS['phpgw']->msg->html_quotes_decode($pref_string);
474			if ($this->debug_set_prefs > 0) { echo 'bofilters.string_undo_defang('.__LINE__.'): LEAVING returning $un_defanged_string ['.serialize($un_defanged_string).']<br />'."\r\n"; }
475			return $un_defanged_string;
476		}
477
478		/*!
479		@function string_strip_and_defang
480		@abstract POST data that is user supplied string needs stripslash and database defang treatment.
481		@param $user_string (string) data from a POST form
482		@result string that was stripslashed and database defanged for storage in the prefs table.
483		@discussion Same problem as for the preferences in general, the preferences database is subject to curruption
484		if certain "database unfriendly" chars are saved to it. Cars like the single quote, certain slashes. See
485		the function "html_quotes_encode" for more info, and also file class.bopreferences too.
486		  Opposite of "string_undo_defang".
487		@author Angles
488		*/
489		function string_strip_and_defang($user_string='')
490		{
491			if ($this->debug_set_prefs > 0) { echo 'bofilters.string_strip_and_defang: ENTERING, para, $user_string ['.serialize($user_string).']<br />'."\r\n"; }
492			if (!$user_string)
493			{
494				return '';
495			}
496			// typical "user_string" needs to strip any slashes
497			// that PHP "magic_quotes_gpc"may have added
498			$prepared_string = $GLOBALS['phpgw']->msg->stripslashes_gpc($user_string);
499			// and this is a _LAME_ way to make the value "database friendly"
500			// because slashes and quotes will FRY the whole preferences repository
501			$prepared_string = $GLOBALS['phpgw']->msg->html_quotes_encode($prepared_string);
502			if ($this->debug_set_prefs > 0) { echo 'bofilters.string_strip_and_defang: LEAVING returning $prepared_string ['.serialize($prepared_string).']<br />'."\r\n"; }
503			return $prepared_string;
504		}
505
506		/*!
507		@function check_duplicate_submit_elements
508		@abstract Apache2 on RH8 will submit duplicate data when the data is numbered array data.
509		@param $key (string) the name of the key in the POST key,value data to inspect,
510		default to "source_accounts" which means POST["source_accounts"][] will be inspected.
511		@discussion For example, with the "source accounts" array submitted from the create or edit
512		filter form, this is the type if numbered array submit data that is subject to this POST duplication
513		bug. Check for and fix if necessary.
514		@author Angles
515		*/
516		function check_duplicate_submit_elements($key='source_accounts')
517		{
518			if ($this->debug_set_prefs > 0) { echo 'bofilters.check_duplicate_submit_elements('.__LINE__.'): ENTERING, param $key is ['.$key.'] <br />'."\r\n"; }
519			if ($this->debug_set_prefs > 1) { echo 'bofilters.check_duplicate_submit_elements('.__LINE__.'): this checks for buggy apache2 duplicated source account POSTED form numbered array data<br />'."\r\n"; }
520			$did_alter = False;
521
522			//source_accounts
523			$seen_list_items=array();
524			$loops = count($GLOBALS['phpgw']->msg->ref_POST[$key]);
525			for($i=0;$i < $loops;$i++)
526			{
527				// buggy apache2: do duplicate test on the supplied $key array items
528				if (in_array($GLOBALS['phpgw']->msg->ref_POST[$key][$i], $seen_list_items) == True)
529				{
530					$did_alter = True;
531					if ($this->debug_set_prefs > 1) { echo 'bofilters: check_duplicate_submit_elements('.__LINE__.'): <u>unsetting</u> and *skipping* duplicate (buggy apache2) POST ['.$key.']['.$i.'] array item ['.$GLOBALS['phpgw']->msg->ref_POST[$key][$i].'] <br />'; }
532					$GLOBALS['phpgw']->msg->ref_POST[$key][$i] = '';
533					// can I UNSET this and have the next $i index item actually be the next one
534					// YES, a) array count calculated before loop, and b) does not squash array to unset an item
535					unset($GLOBALS['phpgw']->msg->ref_POST[$key][$i]);
536					//array_splice($GLOBALS['phpgw']->msg->ref_POST[$key], $i, 1);
537					// NOTE USE OF CONTINUE COMMAND HERE!
538					// we do not increase $i because the next array item just fell into the current slot
539					// UPDAE we are not splicing so we DO increase $i by calling continue
540					continue;
541				}
542				else
543				{
544					// track seen items for duplicate test
545					if ($this->debug_set_prefs > 1) { echo 'bofilters: check_duplicate_submit_elements('.__LINE__.'): good (not duplicate, not buggy apache2) POST ['.$key.']['.$i.'] array item ['.$GLOBALS['phpgw']->msg->ref_POST[$key][$i].'] <br />'; }
546					$tmp_next_idx = count($seen_list_items);
547					$seen_list_items[$tmp_next_idx] = $GLOBALS['phpgw']->msg->ref_POST[$key][$i];
548				}
549			}
550
551			if ($this->debug_set_prefs > 0) { echo 'bofilters.check_duplicate_submit_elements('.__LINE__.'): LEAVING, returning $did_alter ['.serialize($did_alter).']<br />'."\r\n"; }
552		}
553
554		/*!
555		@function process_submitted_data
556		@abstract Handles POST data from the make or edit filter page.
557		@author Angles
558		*/
559		function process_submitted_data()
560		{
561			if ($this->debug_set_prefs > 0) { echo 'bofilters.process_submitted_data('.__LINE__.'): ENTERING<br />'."\r\n"; }
562			if ($this->debug_set_prefs > 2) { echo 'bofilters.process_submitted_data('.__LINE__.'): (pre-buggy apache2 check) ref_POST dump:<pre>'; print_r($GLOBALS['phpgw']->msg->ref_POST); echo '</pre>'."\r\n"; }
563			$this->check_duplicate_submit_elements('source_accounts');
564
565			//if ($this->debug_set_prefs > 1) { echo 'bofilters.process_submitted_data: caling $this->distill_filter_args<br />'."\r\n"; }
566			//$this->distill_filter_args();
567			// we must have data because the form action made this code run
568			$this_filter = array();
569
570			// --- get submitted data that is not in the form of an array  ----
571
572			// FILTER NUMBER
573			//$found_filter_num = $this->obtain_filer_num(False);
574			$found_filter_num = $this->obtain_filer_num();
575			if ((string)$found_filter_num == $this->not_set)
576			{
577				echo 'bofilters.process_submitted_data('.__LINE__.'): LEAVING with ERROR, unable to obtain POST filter_num';
578				return;
579			}
580			if ($this->debug_set_prefs > 1) { echo 'bofilters.process_submitted_data('.__LINE__.'): $this_filter[filter_num]: ['.$found_filter_num.']<br />'; }
581
582			// FILTER NAME
583			if ((isset($GLOBALS['phpgw']->msg->ref_POST['filtername']))
584			&& ((string)$GLOBALS['phpgw']->msg->ref_POST['filtername'] != ''))
585			{
586				$this_filter['filtername'] = $GLOBALS['phpgw']->msg->ref_POST['filtername'];
587				// DEFANG on "filtername" (will need to reverse that on read)
588				$this_filter['filtername'] = $this->string_strip_and_defang($this_filter['filtername']);
589			}
590			else
591			{
592				//$this_filter['filtername'] = 'Filter '.$found_filter_num;
593				$this_filter['filtername'] = 'My Mail Filter';
594			}
595			if ($this->debug_set_prefs > 1) { echo 'bofilters.process_submitted_data('.__LINE__.'): $this_filter[filtername]: ['.$this_filter['filtername'].']<br />'; }
596
597			// ---- The Rest of the data is submitted in  Array Form ----
598
599			// SOURCE ACCOUNTS
600			if ((isset($GLOBALS['phpgw']->msg->ref_POST['source_accounts']))
601			&& ((string)$GLOBALS['phpgw']->msg->ref_POST['source_accounts'] != ''))
602			{
603				// extract the "fake uri" data with parse_str
604				// and fill our filter struct
605				for ($i=0; $i < count($GLOBALS['phpgw']->msg->ref_POST['source_accounts']); $i++)
606				{
607					parse_str($GLOBALS['phpgw']->msg->ref_POST['source_accounts'][$i], $this_filter['source_accounts'][$i]);
608					// re-urlencode the foldername, because we generally keep the fldball urlencoded
609					$this_filter['source_accounts'][$i]['folder'] = urlencode($this_filter['source_accounts'][$i]['folder']);
610					// make sure acctnum is an int
611					$this_filter['source_accounts'][$i]['acctnum'] = (int)$this_filter['source_accounts'][$i]['acctnum'];
612				}
613
614			}
615			else
616			{
617					$this_filter['source_accounts'][0]['folder'] = 'INBOX';
618					$this_filter['source_accounts'][0]['acctnum'] = 0;
619			}
620			if ($this->debug_set_prefs > 2) { echo '.process_submitted_data('.__LINE__.'): $this_filter[source_accounts] dump:<pre>'; print_r($this_filter['source_accounts']); echo '</pre>'."\r\n"; }
621
622			// --- "deep" array form data ---
623			@reset($GLOBALS['phpgw']->msg->ref_POST);
624			// init sub arrays
625			$this_filter['matches'] = Array();
626			$this_filter['actions'] = Array();
627			// look for top level "match_X[]" and "action_X[]" items
628			while(list($key,$value) = each($GLOBALS['phpgw']->msg->ref_POST))
629			{
630				// do not walk thru data we already obtained
631				if (($key == 'filter_num')
632				|| ($key == 'filtername')
633				|| ($key == 'source_accounts'))
634				{
635					if ($this->debug_set_prefs > 1) { echo 'bofilters.process_submitted_data('.__LINE__.'): $GLOBALS[HTTP_POST_VARS] key,value walk thru: $key: ['.$key.'] is data we already processed, skip to next loop<br />'; }
636					continue;
637				}
638				if ($this->debug_set_prefs > 1) { echo 'bofilters.process_submitted_data('.__LINE__.'): $GLOBALS[HTTP_POST_VARS] key,value walk thru: $key: ['.$key.'] ; $value DUMP:<pre>'; print_r($value); echo "</pre>\r\n"; }
639				// extract match and action data from this filter_X data array
640				if (strstr($key, 'match_'))
641				{
642					// now we grab the index value from the key string
643					$match_this_idx = (int)$key[6];
644					if ($this->debug_set_prefs > 1) { echo 'bofilters.process_submitted_data('.__LINE__.'): match_this_idx grabbed value: ['.$match_this_idx.']<br />'; }
645					$match_data = $GLOBALS['phpgw']->msg->ref_POST[$key];
646					// is this row even being used?
647					if ((isset($match_data['andor']))
648					&& ($match_data['andor'] == 'ignore_me'))
649					{
650						if ($this->debug_set_prefs > 1) { echo 'bofilters.process_submitted_data('.__LINE__.'): SKIP this row, $match_data[andor]: ['.$match_data['andor'].']<br />'; }
651					}
652					else
653					{
654						if ($this->debug_set_prefs > 1) { echo 'bofilters.process_submitted_data('.__LINE__.'): $match_data[matchthis] PRE-defang ['.serialize($match_data['matchthis']).']<br />'; }
655						// DEFANG on $match_data["matchthis"] (will need to reverse that on read)
656						$match_data['matchthis'] = $this->string_strip_and_defang($match_data['matchthis']);
657						if ($this->debug_set_prefs > 1) { echo 'bofilters.process_submitted_data('.__LINE__.'): $match_data[matchthis] POST-defang ['.serialize($match_data['matchthis']).']<br />'; }
658						$this_filter['matches'][$match_this_idx] = $match_data;
659						if ($this->debug_set_prefs > 1) { echo 'bofilters.process_submitted_data('.__LINE__.'): $this_filter[matches]['.$match_this_idx.'] = ['.serialize($this_filter['matches'][$match_this_idx]).']<br />'; }
660					}
661				}
662				elseif (strstr($key, 'action_'))
663				{
664					// now we grab the index value from the key string
665					$action_this_idx = (int)$key[7];
666					if ($this->debug_set_prefs > 1) { echo 'bofilters.process_submitted_data('.__LINE__.'): action_this_idx grabbed value: ['.$action_this_idx.']<br />'; }
667					$action_data = $GLOBALS['phpgw']->msg->ref_POST[$key];
668					if ((isset($action_data['judgement']))
669					&& ($action_data['judgement'] == 'ignore_me'))
670					{
671						if ($this->debug_set_prefs > 1) { echo 'bofilters.process_submitted_data('.__LINE__.'): SKIP this row, $action_data[judgement]: ['.$match_data['andor'].']<br />'; }
672					}
673					else
674					{
675						$this_filter['actions'][$action_this_idx] = $action_data;
676						if ($this->debug_set_prefs > 1) { echo 'bofilters.process_submitted_data('.__LINE__.'): $this_filter[actions][$action_this_idx]: ['.serialize($this_filter['actions'][$action_this_idx]).']<br />'; }
677					}
678				}
679			}
680			if ($this->debug_set_prefs > 2) { echo 'bofilters.process_submitted_data('.__LINE__.'): $this_filter[] dump <strong><pre>'; print_r($this_filter); echo "</pre></strong>\r\n"; }
681			$this->all_filters[$found_filter_num] = array();
682			$this->all_filters[$found_filter_num] = $this_filter;
683			$this->save_all_filters_to_repository();
684		}
685
686		/*!
687		@function squash_and_sort_all_filters
688		@abstract ?
689		@author Angles
690		*/
691		function squash_and_sort_all_filters()
692		{
693			// KEY SORT so the filters are numbered in acending array index order
694			ksort($this->all_filters);
695
696			$new_all_filters = array();
697			while(list($key,$value) = each($this->all_filters))
698			{
699				$next_pos = count($new_all_filters);
700				$this_filter = $this->all_filters[$key];
701				$new_all_filters[$next_pos] = $this_filter;
702			}
703			// ok, now we have a compacted list with no gaps
704			$this->all_filters = array();
705			$this->all_filters = $new_all_filters;
706
707
708		}
709
710		/*!
711		@function save_all_filters_to_repository
712		@abstract ?
713		@author Angles
714		*/
715		function save_all_filters_to_repository()
716		{
717			// KEY SORT so the filters are numbered in acending array index order
718			// SQUASH / COMPACT $this->all_prefs so there are NO GAPS
719			$this->squash_and_sort_all_filters();
720
721			// now add this filter piece by piece
722			// we can only set a non-array value, but we can use array string for the base
723			// but we can grab structures
724
725			// NEW we need to wipe the cached filters
726			$my_location = '0;cached_prefs';
727			if ($this->debug_set_prefs > 1) { echo 'bofilters.save_all_filters_to_repository('.__LINE__.'): NEW: EXPIRE CACHED PREFERENCES, calling ->msg->so->so_appsession_passthru('.$my_location.', " ")<br />'; }
728			$GLOBALS['phpgw']->msg->so->so_appsession_passthru($my_location, ' ');
729
730			// first we delete any existing data at the desired prefs location
731			$pref_struct_str = '["filters"]';
732			if ($this->debug_set_prefs > 1) { echo 'bofilters.save_all_filters_to_repository: using preferences->delete_struct("email", $pref_struct_str) which will eval $pref_struct_str='.$pref_struct_str.'<br />'; }
733			$GLOBALS['phpgw']->preferences->delete_struct('email',$pref_struct_str);
734
735			for ($filter_idx=0; $filter_idx < count($this->all_filters); $filter_idx++)
736			{
737				// SAVE TO PREFS DATABASE
738				// we called begin_request in the constructor, so we know the prefs object exists
739
740				$this_filter = $this->all_filters[$filter_idx];
741				// filters are based at [filters][X] where X is the filter_num, based on the [email] top level array tree
742
743				// $this_filter['filtername']	string (will require htmlslecialchars_encode and decode
744				$pref_struct_str = '["filters"]['.$filter_idx.']["filtername"]';
745				if ($this->debug_set_prefs > 1) { echo 'bofilters.save_all_filters_to_repository: using preferences->add_struct("email", $pref_struct_str, '.$this_filter['filtername'].') which will eval $pref_struct_str='.$pref_struct_str.'<br />'; }
746				$GLOBALS['phpgw']->preferences->add_struct('email', $pref_struct_str, $this_filter['filtername']);
747
748				// $this_filter['source_accounts']	array
749				// $this_filter['source_accounts'][X]	array
750				// $this_filter['source_accounts'][X]['folder']	string
751				// $this_filter['source_accounts'][X]['acctnum']	integer
752				for ($i=0; $i < count($this_filter['source_accounts']); $i++)
753				{
754					// folder
755					$pref_struct_str = '["filters"]['.$filter_idx.']["source_accounts"]['.$i.']["folder"]';
756					if ($this->debug_set_prefs > 1) { echo 'bofilters.save_all_filters_to_repository: using preferences->add_struct("email", $pref_struct_str, '.$this_filter['source_accounts'][$i]['folder'].') which will eval $pref_struct_str='.$pref_struct_str.'<br />'; }
757					$GLOBALS['phpgw']->preferences->add_struct('email', $pref_struct_str, $this_filter['source_accounts'][$i]['folder']);
758					// acctnum
759					$pref_struct_str = '["filters"]['.$filter_idx.']["source_accounts"]['.$i.']["acctnum"]';
760					if ($this->debug_set_prefs > 1) { echo 'bofilters.save_all_filters_to_repository: using preferences->add_struct("email", $pref_struct_str, '.$this_filter['source_accounts'][$i]['acctnum'].') which will eval $pref_struct_str='.$pref_struct_str.'<br />'; }
761					$GLOBALS['phpgw']->preferences->add_struct('email', $pref_struct_str, $this_filter['source_accounts'][$i]['acctnum']);
762				}
763
764				// $this_filter['matches']	Array
765				// $this_filter['matches'][X]	Array
766				// $this_filter['matches'][X]['andor']	UNSET for $this_filter['matches'][0], SET for all the rest : and | or | ignore_me
767				// $this_filter['matches'][X]['examine']		known_string : IMAP search keys
768				// $this_filter['matches'][X]['comparator']	known_string : contains | notcontains
769				// $this_filter['matches'][X]['matchthis']	user_string
770				for ($i=0; $i < count($this_filter['matches']); $i++)
771				{
772					// andor
773					if (isset($this_filter['matches'][$i]['andor']))
774					{
775						$pref_struct_str = '["filters"]['.$filter_idx.']["matches"]['.$i.']["andor"]';
776						if ($this->debug_set_prefs > 1) { echo 'bofilters.save_all_filters_to_repository: using preferences->add_struct("email", $pref_struct_str, '.$this_filter['matches'][$i]['andor'].') which will eval $pref_struct_str='.$pref_struct_str.'<br />'; }
777						$GLOBALS['phpgw']->preferences->add_struct('email', $pref_struct_str, $this_filter['matches'][$i]['andor']);
778					}
779					// examine
780					$pref_struct_str = '["filters"]['.$filter_idx.']["matches"]['.$i.']["examine"]';
781					if ($this->debug_set_prefs > 1) { echo 'bofilters.save_all_filters_to_repository: using preferences->add_struct("email", $pref_struct_str, '.$this_filter['matches'][$i]['examine'].') which will eval $pref_struct_str='.$pref_struct_str.'<br />'; }
782					$GLOBALS['phpgw']->preferences->add_struct('email', $pref_struct_str, $this_filter['matches'][$i]['examine']);
783					// comparator
784					$pref_struct_str = '["filters"]['.$filter_idx.']["matches"]['.$i.']["comparator"]';
785					if ($this->debug_set_prefs > 1) { echo 'bofilters.save_all_filters_to_repository: using preferences->add_struct("email", $pref_struct_str, '.$this_filter['matches'][$i]['comparator'].') which will eval $pref_struct_str='.$pref_struct_str.'<br />'; }
786					$GLOBALS['phpgw']->preferences->add_struct('email', $pref_struct_str, $this_filter['matches'][$i]['comparator']);
787					// matchthis
788					// user_string, may need htmlslecialchars_encode decode and/or the user may forget to tnter data here
789					if ((!isset($this_filter['matches'][$i]['matchthis']))
790					|| (trim($this_filter['matches'][$i]['matchthis']) == ''))
791					{
792						$this_filter['matches'][$i]['matchthis'] = 'user_string_not_filled_by_user';
793					}
794					$pref_struct_str = '["filters"]['.$filter_idx.']["matches"]['.$i.']["matchthis"]';
795					if ($this->debug_set_prefs > 1) { echo 'bofilters.save_all_filters_to_repository: using preferences->add_struct("email", $pref_struct_str, '.$this_filter['matches'][$i]['matchthis'].') which will eval $pref_struct_str='.$pref_struct_str.'<br />'; }
796					$GLOBALS['phpgw']->preferences->add_struct('email', $pref_struct_str, $this_filter['matches'][$i]['matchthis']);
797				}
798
799				// $this_filter['actions']	Array
800				// $this_filter['actions'][X]		Array
801				// $this_filter['actions'][X]['judgement']	known_string
802				// $this_filter['actions'][X]['folder']		string contains URI style data ex. "&folder=INBOX.Trash&acctnum=0"
803				// $this_filter['actions'][X]['actiontext']	user_string
804				// $this_filter['actions'][X]['stop_filtering']	UNSET | SET string "True"
805				for ($i=0; $i < count($this_filter['actions']); $i++)
806				{
807					// judgement
808					$pref_struct_str = '["filters"]['.$filter_idx.']["actions"]['.$i.']["judgement"]';
809					if ($this->debug_set_prefs > 1) { echo 'bofilters.save_all_filters_to_repository: using preferences->add_struct("email", $pref_struct_str, '.$this_filter['actions'][$i]['judgement'].') which will eval $pref_struct_str='.$pref_struct_str.'<br />'; }
810					$GLOBALS['phpgw']->preferences->add_struct('email', $pref_struct_str, $this_filter['actions'][$i]['judgement']);
811					// folder
812					$pref_struct_str = '["filters"]['.$filter_idx.']["actions"]['.$i.']["folder"]';
813					if ($this->debug_set_prefs > 1) { echo 'bofilters.save_all_filters_to_repository: using preferences->add_struct("email", $pref_struct_str, '.$this_filter['actions'][$i]['folder'].') which will eval $pref_struct_str='.$pref_struct_str.'<br />'; }
814					$GLOBALS['phpgw']->preferences->add_struct('email', $pref_struct_str, $this_filter['actions'][$i]['folder']);
815					// actiontext
816					$pref_struct_str = '["filters"]['.$filter_idx.']["actions"]['.$i.']["actiontext"]';
817					if ($this->debug_set_prefs > 1) { echo 'bofilters.save_all_filters_to_repository: using preferences->add_struct("email", $pref_struct_str, '.$this_filter['actions'][$i]['actiontext'].') which will eval $pref_struct_str='.$pref_struct_str.'<br />'; }
818					$GLOBALS['phpgw']->preferences->add_struct('email', $pref_struct_str, $this_filter['actions'][$i]['actiontext']);
819					// stop_filtering
820					if (isset($this_filter['actions'][$i]['stop_filtering']))
821					{
822						$pref_struct_str = '["filters"]['.$filter_idx.']["actions"]['.$i.']["stop_filtering"]';
823						if ($this->debug_set_prefs > 1) { echo 'bofilters.save_all_filters_to_repository: using preferences->add_struct("email", $pref_struct_str, '.$this_filter['actions'][$i]['stop_filtering'].') which will eval $pref_struct_str='.$pref_struct_str.'<br />'; }
824						$GLOBALS['phpgw']->preferences->add_struct('email', $pref_struct_str, $this_filter['actions'][$i]['stop_filtering']);
825					}
826				}
827			}
828
829			// DONE processing prefs, SAVE to the Repository
830			if ($this->debug_set_prefs > 3)
831			{
832				echo 'bofilters.save_all_filters_to_repository: *debug* at ['.$this->debug_set_prefs.'] so skipping save_repository<br />';
833			}
834			else
835			{
836				if ($this->debug_set_prefs > 2) { echo 'bofilters.save_all_filters_to_repository: direct pre-save $GLOBALS[phpgw]->preferences->data[email][filters] DUMP:<pre>'; print_r($GLOBALS['phpgw']->preferences->data['email']['filters']); echo '</pre>'; }
837				if ($this->debug_set_prefs > 1) { echo 'bofilters.save_all_filters_to_repository: SAVING REPOSITORY<br />'; }
838				$GLOBALS['phpgw']->preferences->save_repository();
839				// re-grab data from prefs
840
841			}
842			// end the email session
843			$GLOBALS['phpgw']->msg->end_request();
844
845			// redirect user back to filters list page
846			$take_me_to_url = $GLOBALS['phpgw']->link(
847										'/index.php',
848										'menuaction=email.uifilters.filters_list');
849
850			if ($this->debug_set_prefs > 0) { echo 'bofilters.save_all_filters_to_repository: almost LEAVING, about to issue a redirect to:<br />'.$take_me_to_url.'<br />'; }
851			if ($this->debug_set_prefs > 1)
852			{
853				echo 'bofilters.save_all_filters_to_repository: LEAVING, *debug* at ['.$this->debug_set_prefs.'] so skipping Header redirection to: ['.$take_me_to_url.']<br />';
854			}
855			else
856			{
857				if ($this->debug_set_prefs > 0) { echo 'bofilters.save_all_filters_to_repository: LEAVING with redirect to: <br />'.$take_me_to_url.'<br />'; }
858				Header('Location: ' . $take_me_to_url);
859			}
860		}
861
862		/*!
863		@function delete_filter
864		@abstract ?
865		@author Angles
866		*/
867		function delete_filter()
868		{
869			if ($this->debug_set_prefs > 0) { echo 'bofilters.delete_filter: ENTERING<br />'; }
870			// FILTER NUMBER
871			$filter_num = $this->obtain_filer_num();
872
873			if (!$this->filter_exists($filter_num))
874			{
875				echo 'bofilters.delete_filter: LEAVING with ERROR, filter $filter_num ['.serialize($filter_num).'] does not even exist';
876				return;
877			}
878
879			// by now it's ok to unset the target filter
880			$this->all_filters[$filter_num] = array();
881			unset($this->all_filters[$filter_num]);
882			$this->save_all_filters_to_repository();
883			if ($this->debug_set_prefs > 0) { echo 'bofilters.delete_filter: LEAVING<br />'; }
884		}
885
886
887		/*!
888		@function do_filter
889		@abstract this appears to be the mail access point to apply filter, single or all, test or apply, this is the function
890		@author Angles
891		*/
892		function do_filter()
893		{
894			if ($this->debug > 0) { echo 'bofilters.do_filter('.__LINE__.'): ENTERING<br />'; }
895			if (count($this->all_filters) == 0)
896			{
897				if ($this->debug > 0) { echo 'bofilters.do_filter('.__LINE__.'): LEAVING with ERROR, no filters exist<br />'; }
898				return False;
899			}
900
901			//if ($this->debug > 0) { echo 'bofilters.do_filter: LINE '.__LINE__.' call "->msg->event_begin_big_move" to notice event of impending big batch moves or deletes<br />'; }
902			// CORRECTION: the move function now buffers the commands and the count of those buffered commands is kept there, where big move or not is now determined
903			//$GLOBALS['phpgw']->msg->event_begin_big_move(array(), 'bofilters.do_filter: LINE '.__LINE__);
904
905			// filtering thousands of messages can require more time
906			if ($this->debug > 0) { echo 'bofilters.do_filter('.__LINE__.'): calling set_time_limit giving value of 120 ie 2 minutes? <br />'; }
907			set_time_limit(120);
908
909			// "False" means  return $this->not_set  if no filter number was found anywhere
910			$found_filter_num = $this->obtain_filer_num(False);
911			if ($this->debug > 1) { echo 'bofilters.do_filter('.__LINE__.'): $found_filter_num : [<code>'.serialize($found_filter_num).'</code>]<br />'."\r\n"; }
912
913			if ($found_filter_num == $this->not_set)
914			{
915				// NO filter number was specified, that means run ALL filters
916				$this->do_filter_apply_all = True;
917				for ($filter_idx=0; $filter_idx < count($this->all_filters); $filter_idx++)
918				{
919					if ($this->debug > 1) { echo 'bofilters.do_filter('.__LINE__.'): run_all_finters_mode: calling $this->run_single_filter['.$filter_idx.']<br />'; }
920					$this->run_single_filter((int)$filter_idx);
921					if ($this->just_testing())
922					{
923						// add this message to the report
924						$this->make_filter_match_report((int)$filter_idx);
925					}
926				}
927			}
928			else
929			{
930				// we were given a filter_num, that means run THAT FILTER ONLY
931				$this->do_filter_apply_all = False;
932				if ($this->debug > 1) { echo 'bofilters.do_filter('.__LINE__.'): run_single_filter mode: calling $this->run_single_filter['.$found_filter_num.']<br />'; }
933				$this->run_single_filter((int)$found_filter_num);
934				if ($this->just_testing())
935				{
936					// add this message to the report
937					$this->make_filter_match_report((int)$found_filter_num);
938				}
939			}
940
941			// ok, filters have run, EXPUNGE now
942			if ($this->debug > 1) { echo 'bofilters.do_filter ('.__LINE__.'): done filtering, now call $GLOBALS[phpgw]->msg->expunge_expungable_folders<br />'; }
943			$did_expunge = False;
944			$did_expunge = $GLOBALS['phpgw']->msg->expunge_expungable_folders('bofilters.do_filter LINE '.__LINE__);
945			if ($this->debug > 1) { echo 'bofilters.do_filter ('.__LINE__.'): $GLOBALS[phpgw]->msg->expunge_expungable_folders() returns ['.serialize($did_expunge).']<br />'; }
946
947			// ok, filters have run, do we have a report to show?
948			if ($this->just_testing())
949			{
950				//echo '<html>'.$this->html_matches_table.'</html>';
951				unset($GLOBALS['phpgw_info']['flags']['noheader']);
952				unset($GLOBALS['phpgw_info']['flags']['nonavbar']);
953				$GLOBALS['phpgw_info']['flags']['noappheader'] = True;
954				$GLOBALS['phpgw_info']['flags']['noappfooter'] = True;
955				$GLOBALS['phpgw']->common->phpgw_header();
956				echo '<p>&nbsp</p>'."\r\n";
957				echo $this->html_matches_table;
958			}
959			else
960			{
961				// FIX ME - make a better report
962				unset($GLOBALS['phpgw_info']['flags']['noheader']);
963				unset($GLOBALS['phpgw_info']['flags']['nonavbar']);
964				$GLOBALS['phpgw_info']['flags']['noappheader'] = True;
965				$GLOBALS['phpgw_info']['flags']['noappfooter'] = True;
966				$GLOBALS['phpgw']->common->phpgw_header();
967
968				echo '<h4>'.lang('Apply Filters Report:').'</h4>'."\r\n";
969				for ($filter_idx=0; $filter_idx < count($this->all_filters); $filter_idx++)
970				{
971					$this_filter = $this->all_filters[$filter_idx];
972					$num_matches = count($this->each_filter_mball_list[$filter_idx]);
973					parse_str($this_filter['actions'][0]['folder'], $target_folder);
974					echo '<p>'."\r\n"
975					.'<strong>'.lang('Filter number').' '.(string)$filter_idx.':</strong>'.'<br />'."\r\n"
976					.'&nbsp;&nbsp;&nbsp;'.lang('filter name:').' ['.$this_filter['filtername'].']<br />'."\r\n"
977					.'&nbsp;&nbsp;&nbsp;'.lang('number of matches:').' ['.(string)$num_matches.']'.'<br />'."\r\n"
978
979					.'&nbsp;&nbsp;&nbsp;'.lang('requested filter action:').' ['.$this_filter['actions'][0]['judgement'].'] ; Acctnum ['.(string)$target_folder['acctnum'].'] ;  '.lang('Folder').': ['.htmlspecialchars($target_folder['folder']).']<br />'."\r\n"
980					.'</p>'."\r\n"
981					.'<p>&nbsp;</p>'."\r\n";
982				}
983			}
984			if ($this->debug > 1) { echo 'bofilters.do_filter('.__LINE__.'): calling end_request<br />'; }
985			$GLOBALS['phpgw']->msg->end_request();
986			if ($this->debug > 0) { echo 'bofilters.do_filter('.__LINE__.'): LEAVING<br />'; }
987			$take_me_to_url = $GLOBALS['phpgw']->link(
988										'/index.php',
989										//'menuaction=email.uifilters.filters_list');
990										'menuaction=email.uiindex.index');
991			$take_me_to_href = '<a href="'.$take_me_to_url.'"> '.lang('Go Back').' </a>';
992			//Header('Location: ' . $take_me_to_url);
993			echo '<br /><p>'.'&nbsp;&nbsp;&nbsp;'.$take_me_to_href.'</p><br />';
994
995			if ($this->debug > 0) { echo 'bofilters.do_filter('.__LINE__.'): LEAVING<br />'; }
996		}
997
998		// PRIVATE
999		/*!
1000		@function run_single_filter
1001		@abstract ?
1002		@author Angles
1003		@access private
1004		*/
1005		function run_single_filter($filter_num='')
1006		{
1007			if ($this->debug > 0) { echo 'bofilters.run_single_filter('.__LINE__.'): ENTERING, feed  $filter_num : [<code>'.serialize($filter_num).'</code>]<br />'; }
1008			if (count($this->all_filters) == 0)
1009			{
1010				if ($this->debug > 0) { echo 'bofilters.run_single_filter('.__LINE__.'): LEAVING with ERROR, no filters exist<br />'; }
1011			}
1012			$filter_exists = $this->filter_exists($filter_num);
1013			if (!$filter_exists)
1014			{
1015				if ($this->debug > 0) { echo 'bofilters.run_single_filter('.__LINE__.'): LEAVING with ERROR, filter data for $filter_num ['.$filter_num.'] does not exist, return False<br />'; }
1016				return False;
1017			}
1018			$this_filter = $this->all_filters[$filter_num];
1019			if ($this->debug > 2) { echo 'bofilters.run_single_filter('.__LINE__.'): $filter_num ['.$filter_num.'] ; $this_filter DUMP:<pre>'; print_r($this_filter); echo "</pre>\r\n"; }
1020
1021			// WE NEED TO DO THIS FOR EVERY SOURCE ACCOUNT specified in this filter
1022			for ($src_acct_loop_num=0; $src_acct_loop_num < count($this_filter['source_accounts']); $src_acct_loop_num++)
1023			{
1024				if ($this->debug > 1) { echo 'bofilters.run_single_filter('.__LINE__.'): source_accounts loop ['.$src_acct_loop_num.']<br />'; }
1025
1026				// ACCOUNT TO SEARCH (always filter source is INBOX)
1027				$fake_fldball = array();
1028				$fake_fldball['acctnum'] = $this_filter['source_accounts'][$src_acct_loop_num]['acctnum'];
1029				$fake_fldball['folder'] = $this_filter['source_accounts'][$src_acct_loop_num]['folder'];
1030
1031				// GET LIST OF ALL MSGS IN MAILBOX
1032				// only if not already exists
1033				if ((isset($this->inbox_full_msgball_list[$src_acct_loop_num]))
1034				|| (count($this->inbox_full_msgball_list[$src_acct_loop_num]) > 0))
1035				{
1036					if ($this->debug > 1) { echo 'bofilters.run_single_filter('.__LINE__.'): already obtained inbox_full_msgball_list, during a previous filter, for $src_acct_loop_num ['.$src_acct_loop_num.']<br />'; }
1037				}
1038				else
1039				{
1040					// get FULL msgball list for this INBOX (we always filter INBOXs only)
1041					if ($this->debug > 1) { echo 'bofilters.run_single_filter('.__LINE__.'): get_msgball_list for later XOR ing for <code>['.serialize($fake_fldball).']</code><br />'; }
1042					//$this->inbox_full_msgball_list[$src_acct_loop_num] = $GLOBALS['phpgw']->msg->get_msgball_list($fake_fldball['acctnum'], $fake_fldball['folder']);
1043					// FIXME: FOR BACKWARDS COMPAT WE GET AN OLD STYLE MSGBALL LIST
1044					$this->inbox_full_msgball_list[$src_acct_loop_num] = $GLOBALS['phpgw']->msg->get_msgball_list_oldschool($fake_fldball['acctnum'], $fake_fldball['folder']);
1045					//if ($this->debug > 2) { echo 'bofilters.run_single_filter: $this->inbox_full_msgball_list['.$src_acct_loop_num.'] DUMP:<pre>'; print_r($this->inbox_full_msgball_list[$src_acct_loop_num]); echo "</pre>\r\n"; }
1046				}
1047
1048				// FOR EACH MSG, GET IT'S RAW HEADERS
1049				// only if we have not got them yet
1050				if ($this->debug > 1) { echo 'bofilters.run_single_filter('.__LINE__.'): get headers for each msg in $src_acct_loop_num ['.$src_acct_loop_num.']<br />'; }
1051				for ($msg_iteration=0; $msg_iteration < count($this->inbox_full_msgball_list[$src_acct_loop_num]); $msg_iteration++)
1052				{
1053					if ((isset($this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration]['headers_text']))
1054					&& (strlen($this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration]['headers_text']) > 0))
1055					{
1056						// we ALREADY hav the headers
1057						// continue to the next message
1058						if ($this->debug > 1) { echo 'bofilters.run_single_filter('.__LINE__.'): already obtained headers, during a previous filter, for $src_acct_loop_num ['.$src_acct_loop_num.']<br />'; }
1059						continue;
1060					}
1061					// we need to get the headers
1062					// NOTE THIS REQUIRES OLDSCHOOL msgball list, fix this in transition to uri only msgball info
1063					$msgball_this_iteration = $this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration];
1064					$headers_text = $GLOBALS['phpgw']->msg->phpgw_fetchheader($msgball_this_iteration);
1065
1066					// NOTE BUG: there is a bug here when the recieved headers are not contiguous,
1067					//   when an erronious other header intupts the recieved headers block
1068					// EXAMPLE:
1069					// 	Received: (qmail 5000 invoked by uid 38); 27 May 2002 13:48:21 -0000
1070					// 	X-Envelope-Sender: provinsd@telusplanet.net
1071					// 	Received: (qmail 4886 invoked from network); 27 May 2002 13:48:20 -0000
1072					// EXAMPLE:
1073					// 	Received: (qmail 12812 invoked by uid 38); 24 May 2002 12:12:27 -0000
1074					// 	X-Envelope-Sender: lgcdutra@terra.com.br
1075					// 	Received: (qmail 12705 invoked from network); 24 May 2002 12:12:26 -0000
1076
1077					// BRUTE FORCE HACK TO TEMP FIX THIS - rewrite better later
1078					// turn offending "X-Envelope-Sender" into a fake recieved header
1079					//$headers_text = str_replace('X-Envelope-Sender:', 'Received: X-Envelope-Sender', $headers_text);
1080					// UPDATE: better fix for this:
1081
1082
1083					// continue...
1084
1085					// UNFOLD headers
1086					// CRLF WHITESPACE as TAB
1087					$headers_text = str_replace("\r\n".chr(9), ' ', $headers_text);
1088					// CRLF WHITESPACE as SPACE
1089					$headers_text = str_replace("\r\n".chr(32), ' ', $headers_text);
1090					$headers_text = trim($headers_text);
1091					// decode encoded headers (if any)
1092					//$headers_text = $GLOBALS['phpgw']->msg->decode_rfc_header($headers_text);
1093					$headers_text = $GLOBALS['phpgw']->msg->decode_rfc_header_glob($headers_text);
1094					// make all Received headers stripped of their preceeding CRLF,  preg option i = case insensitive; m = multi line
1095					$headers_text = preg_replace('/'."\r\n".'received: /mi', 'CRLF Received: ', $headers_text);
1096					// split the string based on the FIRST CRLF and make only the first Received header have a preceeding "\r\n"
1097					$first_crlf_pos = strpos($headers_text, 'CRLF Received: ');
1098					$headers_part_1 = substr($headers_text, 0, $first_crlf_pos);
1099					$headers_part_2 = substr($headers_text, $first_crlf_pos+4);
1100					$headers_part_2 = trim($headers_part_2);
1101					// this makes the initial received header have it's own line, also the CRLF at the end is so we can search for strings
1102					$headers_text = $headers_part_1."\r\n".$headers_part_2;
1103					// add together TO CC and BCC lines for single pass "recipient" analysis
1104					$headers_array = explode("\r\n", $headers_text);
1105					// start with a faux header token
1106					$recipient_line = 'Recipient: ';
1107					for ($zz=0; $zz < count($headers_array); $zz++)
1108					{
1109						$this_hdr_line = $headers_array[$zz];
1110						if (preg_match("/^To: |^Cc: |^Bcc: /i", $this_hdr_line))
1111						{
1112							$recipient_line .= $this_hdr_line.'  ';
1113						}
1114					}
1115					// add this "recipient" line to the headers
1116					$recipient_line = trim($recipient_line);
1117					// using "\r\n" as an end poing knowing that even the final header line has an "\r\n"
1118					$headers_text .= "\r\n".$recipient_line."\r\n";
1119					if ($this->debug > 2) { echo 'bofilters.run_single_filter: received headers FINAL $headers_text <pre>'.$headers_text.'</pre>'; }
1120					$this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration]['headers_text'] = $headers_text;
1121				}
1122				//if ($this->debug > 2) { echo 'bofilters.run_single_filter: $this->inbox_full_msgball_list['.$src_acct_loop_num.'] DUMP:<pre>'; print_r($this->inbox_full_msgball_list[$src_acct_loop_num]); echo "</pre>\r\n"; }
1123
1124
1125				// iterate thru EACH message's headers, msg by msg
1126				// each message headers gets looked at by each row of criteria for this filter
1127				for ($msg_iteration=0; $msg_iteration < count($this->inbox_full_msgball_list[$src_acct_loop_num]); $msg_iteration++)
1128				{
1129					// messages that have already been acted on and are gone have their "msgnum" replaced with "-1"
1130					if ($this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration]['msgnum'] == $this->not_set)
1131					{
1132						// this message had already been filtered AND MOVED OR DELETED, continue to next loop
1133						if ($this->debug > 1) { echo '<br />bofilters.run_single_filter('.__LINE__.'): skipping... this message has already been moved, deleted by a previous filter, $src_acct_loop_num ['.$src_acct_loop_num.'] $msg_iteration ['.$msg_iteration.']<br /><br />'; }
1134						continue;
1135					}
1136					// we have a message to be filtered...
1137					$headers_text = $this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration]['headers_text'];
1138					// this patiular message has not been looked at yet, initialize it match keeper value
1139					$this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration]['match_keeper'] = 0;
1140					if ($this->debug > 2) { echo 'bofilters.run_single_filter('.__LINE__.'): $this->inbox_full_msgball_list['.$src_acct_loop_num.']['.$msg_iteration.'][headers_text] DUMP:<pre>'; print_r($this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration]['headers_text']); echo "</pre>\r\n"; }
1141
1142					// every header line gets looked at by every row of match criteria
1143					// WE NEED TO DO THIS FOR EVERY MATCH ROW
1144					for ($matches_row=0; $matches_row < count($this_filter['matches']); $matches_row++)
1145					{
1146						if ($this->debug > 1) { echo 'bofilters.run_single_filter('.__LINE__.'): source_accounts loop ['.$src_acct_loop_num.'] ; $msg_iteration ['.$msg_iteration.'] ; $matches_row ['.$matches_row.']<br />'; }
1147						// Note on "RECIPIENT" :  to,cc, bcc  "tri-fecta" all three headers must be considered
1148						// this is why we made a faux header line that contains all three of those in one line
1149						// NOTE: recipient Contains vs. NotContains
1150						// a) recipient contains is an OR statement
1151						// 	contains "boss" = to OR cc OR bcc  contains "boss"
1152						// b) recipient NotContains is an AND statement
1153						//	notcontains "boss" means to AND cc AND bcc  *all* do not contain "boss"
1154						// think about this: recipient does not contain "boss", and CC contains boss,
1155						// wouldn't you be surprised if the filter passes as a "not contains" eventhough CC does, in fact, contain
1156
1157						// SEARCH CRITERIA STRINGS  for this row only
1158						$search_key_sieve = $this_filter['matches'][$matches_row]['examine'];
1159						$search_key_imap = $this->examine_imap_search_keys_map[$search_key_sieve];
1160						$search_for = $this_filter['matches'][$matches_row]['matchthis'];
1161						$comparator = $this_filter['matches'][$matches_row]['comparator'];
1162						$andor = $this_filter['matches'][$matches_row]['andor'];
1163
1164						$inspect_me = '';
1165						// if this is really the 1st word of the header string, it will be preceeded by CRLF
1166						$inspect_me = stristr($headers_text, "\r\n".$search_key_imap);
1167						// inspect_me will be everything to the right of the "neede" INCLUDING the "needle" itself and the REST of the headers
1168						if ($this->debug > 1) { echo 'bofilters.run_single_filter('.__LINE__.'): $search_key_imap  ['.$search_key_imap.'] ; $comparator ['.$comparator.'] ; $search_for ['.$search_for.']<br />'; }
1169						if ($inspect_me)
1170						{
1171							// get rid of that "needle"  search_key_imap (it's included from the stristr above)
1172							$cut_here = strlen($search_key_imap) + 4;
1173							// get everything FROM pos $cut_here on to end of string
1174							$inspect_me = substr($inspect_me, $cut_here);
1175							// get the position of the first CRLF that marks the beginning of the rest of the headers AFTER this line
1176							$cut_here = strpos($inspect_me, "\r\n");
1177							// get everything FROM beginning of string TO  pos $cut_here (the end of the line);
1178							$inspect_me = substr($inspect_me, 0, $cut_here);
1179							if ($this->debug > 1) { echo 'bofilters.run_single_filter('.__LINE__.'): GOT HEADER TO LOOK IN: $inspect_me ['.htmlspecialchars($inspect_me).']<br />'; }
1180							// look for EXISTS or NOT EXISTS our search string
1181							if
1182							(
1183								(($comparator == 'contains')
1184								&& (stristr($inspect_me, $search_for)))
1185							 || (($comparator == 'notcontains')
1186								&& (stristr($inspect_me, $search_for) == False))
1187							)
1188							{
1189								if ($this->debug > 1) { echo 'bofilters.run_single_filter('.__LINE__.'): ** GOT ROW CRITERIA MATCH ** $matches_row '.$matches_row.'<br />'; }
1190								// MATCH: this row matches the search criteria
1191								// i.e. this header line does -or- does not have the seach for text, as requested
1192								if ($matches_row == 0)
1193								{
1194									$this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration]['match_keeper'] |= F_ROW_0_MATCH;
1195								}
1196								elseif ($matches_row == 1)
1197								{
1198									$this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration]['match_keeper'] |= F_ROW_1_MATCH;
1199								}
1200								elseif ($matches_row == 2)
1201								{
1202									$this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration]['match_keeper'] |= F_ROW_2_MATCH;
1203								}
1204								elseif ($matches_row == 3)
1205								{
1206									$this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration]['match_keeper'] |= F_ROW_3_MATCH;
1207								}
1208								else
1209								{
1210									echo 'match keeper error<br />';
1211									$this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration]['match_keeper'] = 'ERROR1';
1212								}
1213
1214							}
1215							else
1216							{
1217								// NO MATCH
1218							}
1219						}
1220						else
1221						{
1222							// header we are looking for does not exist in this messages headers
1223							// probably lookinf for an "X-" header, like "X-Mailer:"
1224							if ($this->debug > 1) { echo 'bofilters.run_single_filter('.__LINE__.'): requested header $search_key_imap  ['.$search_key_imap.'] not in this messages headers<br />'; }
1225						}
1226						// this is the last code that gets run BEFORE we move on to the next row of match criteria
1227						// this code is INSIDE the match criteria rows
1228					}
1229					// this is the last code that gets run BEFORE we move on to the next message, if any
1230					// this code is INSIDE the message by message traversal of the folder's contents
1231					// by now this message has been reviewed by EVERY row of criteria for this filter
1232					// any matches have been recorded in "$this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration][match_keeper]"
1233
1234					// = = = = TIME TO TAKE ACTION ON THIS MESSAGE IF THE MATCHES WARRANT IT = = = =
1235					$this->filter_action_sequence($filter_num, $src_acct_loop_num, $msg_iteration, $this_filter);
1236
1237					// this is the last code that gets run BEFORE we move on to the next message, if any
1238					// this code is INSIDE the message by message traversal of the folder's contents
1239				}
1240				// code here is the last line in this SRC ACCT loop iteration
1241				// this code is INSIDE the source account loop, the outermost loop of the matching system
1242				// to get here, each row or search criteria has been compared to every message in the folder
1243				// THUS, if we are here all criteria for this filter with respect to this folder HAS BEEN RUN
1244				// our "match_keeper" will hold the stamp of F_ROW_MATCHES at this point ONLY if'
1245				// every criteria row's conditions were satisfied
1246				// after this loop has run thru each source account, this filters logic is exhausted
1247				// we may then take the actions requested for any qualified messages
1248			}
1249			// outermose crust of this function
1250
1251			// end of function
1252		}
1253
1254		/*!
1255		@function filter_action_sequence
1256		@abstract private helper for filter matching function, will apply AND and OR logic and do an action
1257		@discussion This example is designed to illustrate the a mail from "boss" about getting a "raise" may be more important
1258		to you than a mail from "your brother" with the same subject, because it is possible your brother does not
1259		control your compensation and he is just making a joke.
1260		You manage this logic by remembering that if you use 3 rows of match criteria, rows one and two have a
1261		parentheses around them.
1262		Why do it this way?
1263		The Sieve concept is to make filters EASY TO UNDERSTAND, studies show people actually use them in such cases
1264		therefor the simple rule that ANDs and ORs are paired together in the first and second row, is consistent and hopefully
1265		easy enough for "Jane / Joe User" to understand.
1266		@author Angles
1267		@example This is how we apply the logic of the "AND" and "OR" that relate the match criteria rows
1268		SIMPLE LOGIC: each "and" "or" is compared with the item before it
1269		* example
1270		ROW-0:   		subject contains "you got a raise"
1271		ROW-1:   AND	sender contains "boss"
1272		ROW-2:  OR	sender contains "your brother"
1273		* translates to:
1274			(ROW-0 "AND" ROW-1) "OR" ROW-2
1275		if both row 0 and row 1 are not satified, then this particular "logic chain" ends, BUT with row 2,
1276		the possible match would be if sender contains "your brother", and this match ALONE triggers the filter action.
1277		REMEMBER THIS: *ROW-2 itself can cause a match* because with "(X1 and X2) or X3", X3 alone causes a match.
1278		thus satisfying that particular filtes's match criteria and triggering action
1279		note: this means this we do *not* have this:
1280			ROW-0 "AND" (ROW-1 "OR" ROW-2)
1281		if the above is really what you want:
1282		I suggest putting the "OR"s first, which puts the openening and closing Parentheses around the "OR" statement
1283		* example
1284		ROW-0:   		sender contains "boss"
1285		ROW-1:  OR	sender contains "your brother"
1286		ROW-0:  AND 	subject contains "you got a raise"
1287		* translates to
1288			(sender contains "boss" -OR- sender contains "your brother") -AND- subject contains "you got a raise"
1289		this is how you get the results you want.
1290		@access private
1291		*/
1292		function filter_action_sequence($filter_num='', $src_acct_loop_num='', $msg_iteration='', $this_filter='')
1293		{
1294			if ($this->debug > 0) { echo 'bofilters.filter_action_sequence: ENTERING <br />'; }
1295			if (((string)$filter_num == '')
1296			|| ((string)$src_acct_loop_num == '')
1297			|| ((string)$msg_iteration == '')
1298			|| ($this_filter == ''))
1299			{
1300				echo 'bofilters.filter_action_sequence: LEAVING, insufficient data in params <br />';
1301				return False;
1302			}
1303
1304			$match_keeper = $this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration]['match_keeper'];
1305			if ($this->debug > 1) { echo 'bofilters.filter_action_sequence: FINAL match results for this message [<code>'.serialize($match_keeper).'</code>] <br />'; }
1306			// test match keeper accuracy
1307			if ($this->debug > 1)
1308			{
1309				if ($match_keeper & F_ROW_0_MATCH) { echo '<b>MATCH</b> row 0 criteria<br />'; }
1310				if ($match_keeper & F_ROW_1_MATCH) { echo '<b>MATCH</b> row 1 criteria<br />'; }
1311				if ($match_keeper & F_ROW_2_MATCH) { echo '<b>MATCH</b> row 2 criteria<br />'; }
1312				if ($match_keeper & F_ROW_3_MATCH) { echo '<b>MATCH</b> row 3 criteria<br />'; }
1313			}
1314
1315			$do_apply_action = False;
1316
1317			// single row handler
1318			if (count($this_filter['matches']) == 1)
1319			{
1320				if ($match_keeper & F_ROW_0_MATCH)
1321				{
1322					if ($this->debug > 1) { echo 'bofilters.filter_action_sequence: single row criteria is a match and DOES trigger action<br />'; }
1323					$do_apply_action = True;
1324				}
1325				else
1326				{
1327					if ($this->debug > 1) { echo 'bofilters.filter_action_sequence: single row criteria Fails<br />'; }
1328				}
1329			}
1330			// 2 rows handler
1331			elseif (count($this_filter['matches']) == 2)
1332			{
1333				// row-0 in multi row does not have "andor"
1334				// but row-0 non-match is NOT a reason to stop if  row-1 is an OR
1335				if (($this_filter['matches'][1]['andor'] == 'and')
1336				&& ($match_keeper & F_ROW_0_MATCH)
1337				&& ($match_keeper & F_ROW_1_MATCH))
1338				{
1339					if ($this->debug > 1) { echo 'bofilters.filter_action_sequence: 2 rows of criteria: "AND" logic chain is satisified, DO APPLY ACTION<br />'; }
1340					$do_apply_action = True;
1341				}
1342				elseif (($this_filter['matches'][1]['andor'] == 'or')
1343				&&  (	($match_keeper & $this->match_keeper_row_values[0])
1344					 ||	($match_keeper & $this->match_keeper_row_values[1])
1345					)
1346				)
1347				{
1348					if ($this->debug > 1) { echo 'bofilters.filter_action_sequence: 2 rows of criteria: "OR" logic chain is satisified, DO APPLY ACTION<br />'; }
1349					$do_apply_action = True;
1350				}
1351				else
1352				{
1353					if ($this->debug > 1) { echo 'bofilters.filter_action_sequence: 2 rows of criteria: logic chain Fails<br />'; }
1354				}
1355			}
1356			// 3 rows handler
1357			elseif (count($this_filter['matches']) == 3)
1358			{
1359				if (($this_filter['matches'][1]['andor'] == 'or')
1360				&& ($this_filter['matches'][2]['andor'] == 'or'))
1361				{
1362					if (($match_keeper & $this->match_keeper_row_values[0])
1363					|| ($match_keeper & $this->match_keeper_row_values[1])
1364					|| ($match_keeper & $this->match_keeper_row_values[2]))
1365					{
1366						if ($this->debug > 1) { echo 'bofilters.filter_action_sequence: 3 rows of criteria: both "andor"s are "OR"s, logic chain is satisified, DO APPLY ACTION<br />'; }
1367						$do_apply_action = True;
1368					}
1369					else
1370					{
1371						if ($this->debug > 1) { echo 'bofilters.filter_action_sequence: 3 rows of criteria: both "andor"s are "OR"s, logic chain Fails<br />'; }
1372					}
1373				}
1374				// after 2 rows of match criteria, we need to handle more complex AND / OR logic
1375				else
1376				{
1377					// EVAL CODE - takes longer to compute but best way to get acurate results here
1378					$andor_code = array();
1379					for ($matches_row=1; $matches_row < count($this_filter['matches']); $matches_row++)
1380					{
1381						if ($this_filter['matches'][$matches_row]['andor'] == 'and')
1382						{
1383							$andor_code[$matches_row] = '&&';
1384						}
1385						elseif ($this_filter['matches'][$matches_row]['andor'] == 'or')
1386						{
1387							$andor_code[$matches_row] = '||';
1388						}
1389					}
1390					$evaled = '';
1391					//$code = '$evaled = ($match_keeper & $this->match_keeper_row_values[0]'
1392					//		.' '.$andor_code[1].' '
1393					//		.'$match_keeper & $this->match_keeper_row_values[1]'
1394					//		.' '.$andor_code[2].' '
1395					//		.'$match_keeper & $this->match_keeper_row_values[2]'
1396					//		.');';
1397					$code = '$evaled = (($match_keeper & $this->match_keeper_row_values[0]'
1398							.' '.$andor_code[1].' '
1399							.'$match_keeper & $this->match_keeper_row_values[1])'
1400							.' '.$andor_code[2].' '
1401							.'$match_keeper & $this->match_keeper_row_values[2]'
1402							.');';
1403					if ($this->debug > 1) { echo ' * $code: '.$code.'<br />'; }
1404					eval($code);
1405					if ($this->debug > 1) { echo ' * $evaled: '.serialize($evaled).'<br />'; }
1406					$do_apply_action = $evaled;
1407				}
1408			}
1409			else
1410			{
1411				echo 'bofilters.filter_action_sequence: ERROR: too many rows<br />';
1412				return False;
1413			}
1414
1415			// = = = ACTION(S) = = =
1416			if ($do_apply_action == True)
1417			{
1418				if ($this->debug > 1) { echo 'bofilters.filter_action_sequence: <strong>### Filter MATCH ###</strong>, now apply the action... <br />'; }
1419				// compile report
1420				if (!isset($this->each_filter_mball_list[$filter_num]))
1421				{
1422					$this->each_filter_mball_list[$filter_num] = array();
1423				}
1424				$next_pos = count($this->each_filter_mball_list[$filter_num]);
1425				$this->each_filter_mball_list[$filter_num][$next_pos] = $this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration];
1426
1427				// = = = = ACTIONS GO HERE = = = =
1428				// = = = = ACTIONS GO HERE = = = =
1429				// = = = = ACTIONS GO HERE = = = =
1430				if ($this->just_testing() == False)
1431				{
1432					// NOT A TEST - APPLY THE ACTION(S)
1433					if ($this->debug > 1) { echo 'bofilters.filter_action_sequence: NOT a Test, *Apply* the Action(s) ; $this_filter[actions][0][judgement] : ['.$this_filter['actions'][0]['judgement'].']<br />'; }
1434					// ACTION: FILEINTO
1435					if ($this_filter['actions'][0]['judgement'] == 'fileinto')
1436					{
1437						$mov_msgball = $this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration];
1438						// clean the msgball of stuff we added to it during the filtering logic, it is no longer needed
1439						if (isset($mov_msgball['headers_text']))
1440						{
1441							$mov_msgball['headers_text'] = '';
1442							unset($mov_msgball['headers_text']);
1443						}
1444						if (isset($mov_msgball['match_keeper']))
1445						{
1446							$mov_msgball['match_keeper'] = '';
1447							unset($mov_msgball['match_keeper']);
1448						}
1449						// get a folder value to use as the target folder and make this into a target_fldball
1450						parse_str($this_filter['actions'][0]['folder'], $target_folder);
1451						// parse_str will add escape slashes to folder names with quotes in them
1452						$target_folder['folder'] = stripslashes($target_folder['folder']);
1453						$target_folder['folder'] = urlencode($target_folder['folder']);
1454						//if ($this->debug > 2) { echo 'bofilters.filter_action_sequence: $target_folder DUMP:<pre>'; print_r($target_folder); echo "</pre>\r\n"; }
1455						$to_fldball = array();
1456						$to_fldball['folder'] = $target_folder['folder'];
1457						$to_fldball['acctnum'] = (int)$target_folder['acctnum'];
1458						if ($this->debug > 2) { echo 'bofilters.filter_action_sequence: $to_fldball DUMP:<pre>'; print_r($to_fldball); echo "</pre>\r\n"; }
1459						if ($this->debug > 1) { echo 'bofilters.filter_action_sequence: pre-move info: $mov_msgball [<code>'.serialize($mov_msgball).'</code>]<br />'; }
1460						//echo 'EXIT NOT READY TO APPLY THE FILTER YET<br />';
1461						$good_to_go = $GLOBALS['phpgw']->msg->industrial_interacct_mail_move($mov_msgball, $to_fldball);
1462
1463						if (!$good_to_go)
1464						{
1465							// ERROR
1466							if ($this->debug > 1) { echo 'bofilters.filter_action_sequence: ERROR: industrial_interacct_mail_move returns FALSE<br />'; }
1467							return False;
1468						}
1469					}
1470					else
1471					{
1472						// not yet coded action
1473						if ($this->debug > 1) { echo 'bofilters.filter_action_sequence: actions not yet coded: $this_filter[actions][0][judgement] : ['.$this_filter['actions'][0]['judgement'].']<br />'; }
1474					}
1475				}
1476
1477
1478
1479				// REMOVE THIS MSGBALL from the "inbox_full_msgball_list" IF we move, delete, etc... the message
1480				// it must remain in sync with the actual mail box folder
1481				if ($this->debug > 1) { echo 'bofilters.filter_action_sequence: action completed, REMOVE msgball from L1 cache class var inbox_full_msgball_list, change msgball["msgnum"] from '.serialize($this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration]['msgnum']).' to not_set "-1"<br />'; }
1482				$this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration]['msgnum'] = $this->not_set;
1483			}
1484
1485
1486			if ($this->debug > 0) { echo 'bofilters.filter_action_sequence: LEAVING, returning True <br />'; }
1487			if ($this->debug > 1) { echo '<br />'; }
1488			// if we get to here, no error kicked us out of this function, so I guess we should retuen True
1489			return True;
1490		}
1491
1492		/*!
1493		@function make_filter_match_report
1494		@abstract ?
1495		@author Angles
1496		*/
1497		function make_filter_match_report($filter_num='')
1498		{
1499			$this_filter = $this->all_filters[$filter_num];
1500			if (($this->just_testing())
1501			&& (count($this->each_filter_mball_list[$filter_num]) > 0))
1502			{
1503				if ($this->debug > 1) { echo 'bofilters.make_filter_match_report: Filter Report Maker<br />'; }
1504				if ($this->debug > 1) { echo 'bofilters.make_filter_match_report: number of matches $this->each_filter_mball_list['.$filter_num.'] = ' .count($this->each_filter_mball_list[$filter_num]).'<br />'."\r\n"; }
1505				// make a "fake" folder_info array to make things simple for get_msg_list_display
1506				$fake_folder_info['is_imap'] = True;
1507				$fake_folder_info['folder_checked'] = 'INBOX';
1508				$fake_folder_info['alert_string'] = 'you have search results';
1509				$fake_folder_info['number_new'] = count($this->each_filter_mball_list[$filter_num]);
1510				$fake_folder_info['number_all'] = count($this->each_filter_mball_list[$filter_num]);
1511				$new_style_msgball_list = array();
1512				// make OLDSCHOOL style msgball_list intoi new URI only msgball_list
1513				for ($mx=0; $mx < count($this->each_filter_mball_list[$filter_num]); $mx++)
1514				{
1515					// make this a URI type msgball_list
1516					$uri_data =
1517						  'msgball[msgnum]='.$this->each_filter_mball_list[$filter_num][$mx]['msgnum']
1518						.'&msgball[folder]='.$this->each_filter_mball_list[$filter_num][$mx]['folder']
1519						.'&msgball[acctnum]='.$this->each_filter_mball_list[$filter_num][$mx]['acctnum'];
1520					$new_style_msgball_list[$mx] = $uri_data;
1521				}
1522				if ($this->debug > 2) { echo 'bofilters.run_single_filter:  $this->each_filter_mball_list['.$filter_num.'] DUMP:<pre>'; print_r($this->each_filter_mball_list[$filter_num]); echo "</pre>\r\n"; }
1523				// retrieve user displayable data for each message in the result set
1524				//$this->result_set_mlist = $GLOBALS['phpgw']->msg->get_msg_list_display($fake_folder_info,$this->each_filter_mball_list[$filter_num]);
1525				$this->result_set_mlist = $GLOBALS['phpgw']->msg->get_msg_list_display($fake_folder_info,$new_style_msgball_list);
1526				// save this report data for later use, add it to any other previous report
1527				parse_str($this_filter['actions'][0]['folder'], $target_folder);
1528				$this->html_matches_table .=
1529					//'<h3>Results: ['.$fake_folder_info['number_all'].'] matches for Filter number ['.$filter_num.'] named: '.$this_filter['filtername'].'</h3>'."\r\n"
1530					'<h4>Test Results: Filter ['.$filter_num.'] had ['.$fake_folder_info['number_all'].'] matches. Filter named: '.$this_filter['filtername'].'</h4>'."\r\n"
1531					.'Action: ['.$this_filter['actions'][0]['judgement'].'] ; Acctnum ['.(string)$target_folder['acctnum'].'] ;  Folder: '.htmlspecialchars($target_folder['folder'])
1532					.'<table>'
1533					.$this->make_mlist_box()
1534					.'</table>'."\r\n";
1535			}
1536
1537		}
1538
1539		/*
1540							$match_keeper = $this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration]['match_keeper'];
1541							// any previous match data to be aware of ?
1542							if ($matches_row == 0)
1543							{
1544								$match_keeper = F_ROW_MATCHES;
1545							}
1546							// we have to compare to previous row, are we still matching all seen criteria ?
1547							else
1548							{
1549								$prev_match_keeper = $this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration-1]['match_keeper'];
1550								if (($prev_match_keeper == F_ROW_MATCHES)
1551								&& ($andor == 'and'))
1552								{
1553									// we are still matching, prev row matched AND this one does too
1554									$match_keeper = F_ROW_MATCHES;
1555								}
1556								elseif ($andor == 'or')
1557								{
1558									// does not matter if prev ro0w was a match, this is an OR statement
1559									$match_keeper = F_ROW_MATCHES;
1560								}
1561								else
1562								{
1563									// if we get to here we are no loger matching the chain of criteria
1564									// of which this row is onlt one "link" in that chain
1565									$match_keeper = '';
1566								}
1567							}
1568							// put match keeper back in its association with this msgball
1569							$this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration]['match_keeper'] = $match_keeper;
1570							// break out of this header line by line traversal now that we've got a match for this row
1571							break;
1572						}
1573						else
1574						{
1575							// NOT a match, keep looking thru the headers
1576						}
1577					}
1578					// this code gets run last thing before moving to the next header line for this message
1579					// this code is last code INSIDE the line by line header traversal loop
1580					// if we found a match already for this message, we "broke" out of this loop and bypassed this code
1581					// if we reach here, there has not yet been a match in these headers for this message
1582					// and we are still looking thru the headers for a match
1583					// HOWEVER if this is the LAST LINE of headers and we STILL HAVE NO MATCH
1584					// then this message has FAILED this row's criteria
1585					// The only hope for this row now is that andor is OR
1586					// that way this row can still preserve the last row's MATCH if there was one
1587					$prev_match_keeper = $this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration-1]['match_keeper'];
1588					if (($hdr_line_num + 1 == count($headers_array))
1589					&& ($prev_match_keeper == F_ROW_MATCHES)
1590					&& ($andor == 'or'))
1591					{
1592						// this row retains its previous MATCH quality
1593						// even though it failed this row's criteria
1594						// because this row is OR'd to the previous row's results
1595						$match_keeper = F_ROW_MATCHES;
1596					}
1597					else
1598					{
1599						// shame, shame! this row looses it's match quality if it had one
1600						$match_keeper = '';
1601					}
1602					// put match keeper back in its association with this msgball
1603					$this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration]['match_keeper'] = $match_keeper;
1604				}
1605				// this is the last code that gets run BEFORE we move on to the next message, if any
1606				// this code is INSIDE the message by message traversal of the folder's contents
1607				// each row of match criteria gets to look at every message in the folder,
1608				// before the next row of criteria gets its chance to look at the mesages
1609			}
1610
1611
1612
1613
1614					// this is the last code that gets run BEFORE we move on to the next row of match criteria
1615					// this code is INSIDE the match criteria rows
1616					// to get here, this match criteria row has looked at all messages
1617					// by now, every message in the folder has either been stamped F_ROW_MATCHES or not
1618					// F_ROW_MATCHES is cumulative, e.i. understands and stamps
1619					// depending on the previous row's stamp and whether this row is being AND's or OR'd
1620					// to that previous row's stamp.
1621				}
1622				// code here is the last line in this SRC ACCT loop iteration
1623				// this code is INSIDE the source account loop, the outermost loop of the matching system
1624				// to get here, each row or search criteria has been compared to every message in the folder
1625				// THUS, if we are here all criteria for this filter with respect to this folder HAS BEEN RUN
1626				// our "match_keeper" will hold the stamp of F_ROW_MATCHES at this point ONLY if'
1627				// every criteria row's conditions were satisfied
1628				// after this loop has run thru each source account, this filters logic is exhausted
1629				// we may then take the actions requested for any qualified messages
1630			}
1631
1632			// WE ARE in the outermost crust of THIS particular FILTER
1633			// each account has had each message compared against any applicable critera
1634			// messages that have a F_ROW_MATCHES need to be acted on according to the "action"
1635			// specified for this filter
1636
1637			// this is for holding report and/or debug data
1638			// ACTION LOOP
1639			// loop again, this time acting on F_ROW_MATCHES stamped messages
1640			$this_filter_matching_msgballs = array();
1641			for ($src_acct_loop_num=0; $src_acct_loop_num < count($this_filter['source_accounts']); $src_acct_loop_num++)
1642			{
1643				if ($this->debug > 1) { echo 'bofilters.run_single_filter: source_accounts ACTION loop ['.$src_acct_loop_num.']<br />'; }
1644				for ($msg_iteration=0; $msg_iteration < count($this->inbox_full_msgball_list[$src_acct_loop_num]); $msg_iteration++)
1645				{
1646					if ($this->debug > 1) { echo 'bofilters.run_single_filter: source_accounts ['.$src_acct_loop_num.'] $msg_iteration iteration ['.$msg_iteration.'] ACTION loop<br />'; }
1647					// do we need to do something with this message?
1648					$match_keeper = $this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration]['match_keeper'];
1649					if ($match_keeper == F_ROW_MATCHES)
1650					{
1651						$positive_msgball = $this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration];
1652						// THIS IS WHERE WE TAKE ACTION
1653						if ($this->just_testing())
1654						{
1655							// just testing, make a list of all matching msgball for later displaying
1656							$next_pos = count($this_filter_matching_msgballs);
1657							$this_filter_matching_msgballs[$next_pos] = $positive_msgball;
1658						}
1659						else
1660						{
1661							// NOT A TEST - APPLY THE ACTION(S)
1662							if ($this->debug > 1) { echo 'bofilters.run_single_filter: NOT a Test, *Apply* the Action(s) ; $this_filter[actions][0][judgement] : ['.$this_filter['actions'][0]['judgement'].']<br />'; }
1663							// ACTION: FILEINTO
1664							if ($this_filter['actions'][0]['judgement'] == 'fileinto')
1665							{
1666								parse_str($this_filter['actions'][0]['folder'], $target_folder);
1667								$target_folder['folder'] = urlencode($target_folder['folder']);
1668								//if ($this->debug > 2) { echo 'bofilters.run_single_filter: $target_folder DUMP:<pre>'; print_r($target_folder); echo "</pre>\r\n"; }
1669								$to_fldball = array();
1670								$to_fldball['folder'] = $target_folder['folder'];
1671								$to_fldball['acctnum'] = (int)$target_folder['acctnum'];
1672								if ($this->debug > 2) { echo 'bofilters.run_single_filter: $to_fldball DUMP:<pre>'; print_r($to_fldball); echo "</pre>\r\n"; }
1673								if ($this->debug > 1) { echo 'bofilters.run_single_filter: pre-move info: $mov_msgball [<code>'.serialize($mov_msgball).'</code>]<br />'; }
1674								//echo 'EXIT NOT READY TO APPLY THE FILTER YET<br />';
1675								$good_to_go = $GLOBALS['phpgw']->msg->industrial_interacct_mail_move($positive_msgball, $to_fldball);
1676
1677								if (!$good_to_go)
1678								{
1679									// ERROR
1680									if ($this->debug > 1) { echo 'bofilters.run_single_filter: ERROR: industrial_interacct_mail_move returns FALSE<br />'; }
1681									return False;
1682								}
1683								// since we acted on this message, since we MOVED this message
1684								// this message is NO LONGER IN THE SOURCE FOLDER
1685								// in order to avoid having to re-fetch all headers, just remove this msgball
1686								// from this list, so we stay in sync with the real folder without having
1687								// to re-fetch all the data again for the next filter
1688								$this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration] = array();
1689								unset($this->inbox_full_msgball_list[$src_acct_loop_num][$msg_iteration]);
1690								// later we will "squash" this array to get rid of these gaps
1691							}
1692							else
1693							{
1694								// not yet coded action
1695								if ($this->debug > 1) { echo 'bofilters.run_single_filter: actions not yet coded: $this_filter[actions][0][judgement] : ['.$this_filter['actions'][0]['judgement'].']<br />'; }
1696							}
1697							// POST ACTION STUFF
1698							// n/a
1699						}
1700
1701					}
1702					// last code before iterating to the next message number
1703				}
1704				// last code before moving to the next source account iteration
1705				// PACK THE ARRAY
1706				$packed_all_messages = array();
1707				while(list($key,$value) = each($this->inbox_full_msgball_list[$src_acct_loop_num]))
1708				{
1709					$next_pos = count($packed_all_messages);
1710					$this_msgball = $this->inbox_full_msgball_list[$src_acct_loop_num][$key];
1711					$packed_all_messages[$next_pos] = $this_msgball;
1712				}
1713				// ok, now we have a compacted list with no gaps
1714				$this->inbox_full_msgball_list[$src_acct_loop_num] = array();
1715				$this->inbox_full_msgball_list[$src_acct_loop_num] = $packed_all_messages;
1716			}
1717
1718			// we are back at the outer crust of this function
1719			// in the first big loop, messges were analysed and tagged
1720			// in the second loop, just above, action was taken on those tagged messages
1721
1722			// only thing left is the report
1723			if (($this->just_testing())
1724			&& (count($this_filter_matching_msgballs) > 0))
1725			{
1726				if ($this->debug > 1) { echo 'bofilters.run_single_filter: Filter Test Run<br />'; }
1727				if ($this->debug > 1) { echo 'bofilters.run_single_filter: number of matches $this_filter_matching_msgballs = ' .count($this_filter_matching_msgballs).'<br />'."\r\n"; }
1728				// make a "fake" folder_info array to make things simple for get_msg_list_display
1729				$fake_folder_info['is_imap'] = True;
1730				$fake_folder_info['folder_checked'] = 'INBOX';
1731				$fake_folder_info['alert_string'] = 'you have search results';
1732				$fake_folder_info['number_new'] = count($this_filter_matching_msgballs);
1733				$fake_folder_info['number_all'] = count($this_filter_matching_msgballs);
1734				if ($this->debug > 2) { echo 'bofilters.run_single_filter:  $this_filter_matching_msgballs DUMP:<pre>'; print_r($this_filter_matching_msgballs); echo "</pre>\r\n"; }
1735				// retrieve user displayable data for each message in the result set
1736				$this->result_set_mlist = $GLOBALS['phpgw']->msg->get_msg_list_display($fake_folder_info,$all_accounts_result_set);
1737				// save this report data for later use, add it to any other previous report
1738				$this->html_matches_table .=
1739					'<h3>Results for Filter ['.$filter_num.'] named: '.$this_filter['filtername'].'</h3>'."\r\n"
1740					.'<table>'
1741					.$this->make_mlist_box()
1742					.'</table>'."\r\n";
1743			}
1744			// cleanup
1745			$this_filter_matching_msgballs = array();
1746
1747			if ($this->debug > 0) { echo 'bofilters.run_single_filter: LEAVING, return True because we made it to the end of the function<br /><br /><br />'; }
1748			return True;
1749
1750		}
1751		*/
1752
1753		// DEPRECIATED
1754		/*!
1755		@function make_imap_search_str
1756		@abstract DEPRECIATED
1757		@author Angles
1758		@syntax RFC2060 says:
1759		search  =  "SEARCH" [SP "CHARSET" SP astring] 1*(SP search-key)
1760		search-key =
1761			"ALL" / "ANSWERED" / "BCC" SP astring /
1762			"BEFORE" SP date / "BODY" SP astring /
1763			"CC" SP astring / "DELETED" / "FLAGGED" /
1764			"FROM" SP astring / "KEYWORD" SP flag-keyword / "NEW" /
1765			"OLD" / "ON" SP date / "RECENT" / "SEEN" /
1766			"SINCE" SP date / "SUBJECT" SP astring /
1767			"TEXT" SP astring / "TO" SP astring /
1768			"UNANSWERED" / "UNDELETED" / "UNFLAGGED" /
1769			"UNKEYWORD" SP flag-keyword / "UNSEEN" /
1770		; Above this line were in [IMAP2]
1771			"DRAFT" / "HEADER" SP header-fld-name SP astring /
1772			"LARGER" SP number / "NOT" SP search-key /
1773			"OR" SP search-key SP search-key /
1774			"SENTBEFORE" SP date / "SENTON" SP date /
1775			"SENTSINCE" SP date / "SMALLER" SP number /
1776			"UID" SP set / "UNDRAFT" / set /
1777			"(" search-key *(SP search-key) ")"
1778		@example Examples of how to construct IMAP4rev1 search strings
1779		"PERFECT WORLD EXAMPLES" meaning the following
1780		examples apply ONLY to servers implementing IMAP4rev1 Search functionality
1781		As of Jan 25, 2002, this is somewhat rare.
1782		From a google search in a "turnpike" newsgroup:
1783
1784		IMAP's [AND] OR and NOT are all prefix operators, i.e. there is no
1785		precedence or hierarchy (I put the [AND] in brackets as it is implied,
1786		there is no AND keyword).
1787
1788		[AND] and OR operate on the next two search-keys.
1789		NOT operates on the next search-key.
1790
1791		Parentheses can be used to group an expression of search-keys into a
1792		single search-key.
1793
1794		Some examples translated into infix notation with "not" "and" "or" as
1795		infix operators, k1, k2 .. are search-keys.  These infix operators are
1796		purely for explanation, they are not part of IMAP.
1797
1798		k1 k2 k3                means (k1 and k2) and k3
1799		OR k1 k2 k3             means (k1 or k2) and k3
1800		OR (OR k1 k2) k3        means (k1 or k2) or k3
1801		NOT k1 k2               means (not k1) and k2
1802		NOT OR k1 k2            means not (k1 or k2)
1803		OR NOT k1 k2            means (not k1) or k2
1804		NOT k1 NOT k2           means (not k1) and (not k2)
1805		*/
1806		function make_imap_search_str($feed_filter)
1807		{
1808			if ($this->debug > 0) { echo 'bofilters.make_imap_search_str: ENTERING<br />'; }
1809			if ($this->debug > 2) { echo 'bofilters.make_imap_search_str: $feed_filter DUMP:<pre>'; print_r($feed_filter); echo "</pre>\r\n"; }
1810			/*
1811			RFC2060:
1812			search  =  "SEARCH" [SP "CHARSET" SP astring] 1*(SP search-key)
1813			search-key =
1814				"ALL" / "ANSWERED" / "BCC" SP astring /
1815				"BEFORE" SP date / "BODY" SP astring /
1816				"CC" SP astring / "DELETED" / "FLAGGED" /
1817				"FROM" SP astring / "KEYWORD" SP flag-keyword / "NEW" /
1818				"OLD" / "ON" SP date / "RECENT" / "SEEN" /
1819				"SINCE" SP date / "SUBJECT" SP astring /
1820				"TEXT" SP astring / "TO" SP astring /
1821				"UNANSWERED" / "UNDELETED" / "UNFLAGGED" /
1822				"UNKEYWORD" SP flag-keyword / "UNSEEN" /
1823			; Above this line were in [IMAP2]
1824				"DRAFT" / "HEADER" SP header-fld-name SP astring /
1825				"LARGER" SP number / "NOT" SP search-key /
1826				"OR" SP search-key SP search-key /
1827				"SENTBEFORE" SP date / "SENTON" SP date /
1828				"SENTSINCE" SP date / "SMALLER" SP number /
1829				"UID" SP set / "UNDRAFT" / set /
1830				"(" search-key *(SP search-key) ")"
1831			*/
1832			/*
1833			Examples of how to construct IMAP4rev1 search strings
1834			"PERFECT WORLD EXAMPLES" meaning the following
1835			examples apply ONLY to servers implementing IMAP4rev1 Search functionality
1836			As of Jan 25, 2002, this is somewhat rare.
1837			From a google search in a "turnpike" newsgroup:
1838
1839			IMAP's [AND] OR and NOT are all prefix operators, i.e. there is no
1840			precedence or hierarchy (I put the [AND] in brackets as it is implied,
1841			there is no AND keyword).
1842
1843			[AND] and OR operate on the next two search-keys.
1844			NOT operates on the next search-key.
1845
1846			Parentheses can be used to group an expression of search-keys into a
1847			single search-key.
1848
1849			Some examples translated into infix notation with "not" "and" "or" as
1850			infix operators, k1, k2 .. are search-keys.  These infix operators are
1851			purely for explanation, they are not part of IMAP.
1852
1853			k1 k2 k3                means (k1 and k2) and k3
1854			OR k1 k2 k3             means (k1 or k2) and k3
1855			OR (OR k1 k2) k3        means (k1 or k2) or k3
1856			NOT k1 k2               means (not k1) and k2
1857			NOT OR k1 k2            means not (k1 or k2)
1858			OR NOT k1 k2            means (not k1) or k2
1859			NOT k1 NOT k2           means (not k1) and (not k2)
1860			*/
1861
1862			if ($this->debug > 2) { echo 'bofilters: make_imap_search_str: mappings are:<pre>'; print_r($this->examine_imap_search_keys_map); echo "</pre>\r\n"; }
1863
1864			// do we have one search or two, or more
1865			$num_search_criteria = count($feed_filter['matches']);
1866			if ($this->debug > 1) { echo 'bofilters.make_imap_search_str: $num_search_criteria: ['.$num_search_criteria.']<br />'; }
1867			// 1st search criteria
1868			// convert form submitted data into usable IMAP search keys
1869			$search_key_sieve = $feed_filter['matches'][0]['examine'];
1870			$search_key_imap = $this->examine_imap_search_keys_map[$search_key_sieve];
1871			// what to learch for
1872			$search_for = $feed_filter['matches'][0]['matchthis'];
1873			// does or does not contain
1874			$comparator = $feed_filter['matches'][0]['comparator'];
1875			$search_str_1_criteria = $search_key_imap.' "'.$search_for.'"';
1876			// DOES NOT CONTAIN - "NOT" is a IMAP4rev1 only key, UWASH doesn;t support it.
1877
1878			// DO ONE LINE AT A TIME FOR NOW
1879			$one_line_only = True;
1880			if ($one_line_only)
1881			{
1882				// skip this
1883			}
1884			else
1885			{
1886				// 2nd Line
1887				if ($num_search_criteria == 1)
1888				{
1889					// no seconnd line, our string is complete
1890					$final_search_str = $search_str_1_criteria;
1891				}
1892				else
1893				{
1894					// convert form submitted data into usable IMAP search keys
1895					$search_key_sieve = $feed_filter['matches'][1]['examine'];
1896					$search_key_imap = $this->examine_imap_search_keys_map[$search_key_sieve];
1897					// what to learch for
1898					$search_for = $feed_filter['matches'][1]['matchthis'];
1899					// does or does not contain
1900					$comparator = $feed_filter['matches'][1]['comparator'];
1901					// DOES NOT CONTAIN - BROKEN - FIXME
1902					$search_str_2_criteria = $search_key_imap.' "'.$search_for.'"';
1903					// preliminary  compound search string
1904					$final_search_str = $search_str_1_criteria .' '.$search_str_2_criteria;
1905					// final syntax of this limited 2 line search
1906					$andor = $feed_filter['matches'][1]['andor'];
1907					// ANDOR - BROKEN - FIXME
1908				}
1909			}
1910			/*
1911			$conv_error = '';
1912			if ((!isset($look_here_sieve))
1913			|| (trim($look_here_sieve) == '')
1914			|| ($look_here_imap == ''))
1915			{
1916				$conv_error = 'invalid or no examine data';
1917				if ($this->debug > 1) { echo '<b> *** error</b>: bofilters.make_imap_search_str: error: '.$conv_error."<br /> \r\n"; }
1918				return '';
1919			}
1920			elseif ((!isset($for_this))
1921			|| (trim($for_this) == ''))
1922			{
1923				$conv_error = 'invalid or no search string data';
1924				if ($this->debug > 1) { echo '<b> *** error</b>: bofilters.make_imap_search_str: error: '.$conv_error."<br /> \r\n"; }
1925				return '';
1926			}
1927			$imap_str = $look_here_imap.' "'.$for_this.'"';
1928			*/
1929			if ($this->debug > 0) { echo 'bofilters.make_imap_search_str: LEAVING, $one_line_only: ['.serialize($one_line_only).'] returning search string: <code>'.$final_search_str.'</code><br />'."\r\n"; }
1930			return $final_search_str;
1931		}
1932
1933
1934		/*!
1935		@function make_mlist_box
1936		@abstract ?
1937		@author Angles
1938		*/
1939		function make_mlist_box()
1940		{
1941			$this->template = CreateObject('phpgwapi.Template',PHPGW_APP_TPL);
1942			$this->template->set_file(array(
1943				'T_index_blocks' => 'index_blocks.tpl'
1944			));
1945			$this->template->set_block('T_index_blocks','B_mlist_form_init','V_mlist_form_init');
1946			$this->template->set_block('T_index_blocks','B_arrows_form_table','V_arrows_form_table');
1947			$this->template->set_block('T_index_blocks','B_mlist_block','V_mlist_block');
1948			$this->template->set_block('T_index_blocks','B_mlist_submit_form','V_mlist_submit_form');
1949
1950			$tpl_vars = Array(
1951				'mlist_font'		=> $GLOBALS['phpgw_info']['theme']['font'],
1952				'mlist_font_size'	=> '2',
1953				'mlist_font_size_sm'	=> '1',
1954				'V_mlist_form_init'	=> ''
1955			);
1956			$this->template->set_var($tpl_vars);
1957
1958			if (count($this->result_set_mlist) == 0)
1959			{
1960				$this->template->set_var('V_mlist_block','');
1961			}
1962			else
1963			{
1964				$this->template->set_var('V_no_messages','');
1965				$this->template->set_var('mlist_attach','&nbsp;');
1966				for ($i=0; $i < count($this->result_set_mlist); $i++)
1967				{
1968					if ($this->result_set_mlist[$i]['is_unseen'])
1969					{
1970						$this->template->set_var('open_newbold','<strong>');
1971						$this->template->set_var('close_newbold','</strong>');
1972					}
1973					else
1974					{
1975						$this->template->set_var('open_newbold','');
1976						$this->template->set_var('close_newbold','');
1977					}
1978					$tpl_vars = Array(
1979						'mlist_msg_num'		=> $this->result_set_mlist[$i]['msg_num'],
1980						'mlist_backcolor'	=> $this->result_set_mlist[$i]['back_color'],
1981						'mlist_subject'		=> $this->result_set_mlist[$i]['subject'],
1982						'mlist_subject_link'	=> $this->result_set_mlist[$i]['subject_link'],
1983						'mlist_from'		=> $this->result_set_mlist[$i]['from_name'],
1984						'mlist_from_extra'	=> $this->result_set_mlist[$i]['display_address_from'],
1985						'mlist_reply_link'	=> $this->result_set_mlist[$i]['from_link'],
1986						'mlist_date'		=> $this->result_set_mlist[$i]['msg_date'],
1987						'mlist_size'		=> $this->result_set_mlist[$i]['size']
1988					);
1989					$this->template->set_var($tpl_vars);
1990					$this->template->parse('V_mlist_block','B_mlist_block',True);
1991				}
1992				$this->finished_mlist = $this->template->get_var('V_mlist_block');
1993
1994				// MAKE SUBMIT TO MLIST FORM
1995				// make the voluminous MLIST hidden vars array
1996				$mlist_hidden_vars = '';
1997				for ($i=0; $i < count($this->result_set); $i++)
1998				{
1999					$this_msg_num = (string)$this->result_set[$i];
2000					$mlist_hidden_vars .= '<input type="hidden" name="mlist_set['.(string)$i.']" value="'.$this_msg_num.'">'."\r\n";
2001				}
2002				// preserve the folder we searched (raw posted source_account was never preped in here, so it's ok to send out as is)
2003				$mlist_hidden_vars .= '<input type="hidden" name="folder" value="'.$this->filters[0]['source_account'].'">'."\r\n";
2004				// make the first prev next last arrows
2005				$this->template->set_var('mlist_submit_form_action', $GLOBALS['phpgw']->link('/index.php','menuaction=email.uiindex.mlist'));
2006				$this->template->set_var('mlist_hidden_vars',$mlist_hidden_vars);
2007				$this->template->parse('V_mlist_submit_form','B_mlist_submit_form');
2008
2009				$this->submit_mlist_to_class_form = $this->template->get_var('V_mlist_submit_form');
2010
2011				return $this->finished_mlist;
2012			}
2013
2014		}
2015
2016		/*!
2017		@function do_imap_search
2018		@abstract DEPRECIATED - commented out
2019		@author Angles
2020		*/
2021		/* // DEPRECIATED
2022		function do_imap_search()
2023		{
2024			$imap_search_str = $this->make_imap_search_str();
2025			if (!$imap_search_str)
2026			{
2027				if ($this->debug > 0) { echo '<b> *** error</b>: bofilters: do_imap_search: make_imap_search_str returned empty<br />'."\r\n"; }
2028				return array();
2029			}
2030
2031			//$attempt_reuse = True;
2032			$attempt_reuse = False;
2033			if (!is_object($GLOBALS['phpgw']->msg))
2034			{
2035				$GLOBALS['phpgw']->msg = CreateObject("email.mail_msg");
2036			}
2037
2038			if ((is_object($GLOBALS['phpgw']->msg))
2039			&& ($attempt_reuse == True))
2040			{
2041				// no not create, we will reuse existing
2042				echo 'bofilters: do_imap_search: reusing existing mail_msg object'.'<br />';
2043				// we need to feed the existing object some params begin_request uses to re-fill the msg->args[] data
2044				$reuse_feed_args = $GLOBALS['phpgw']->msg->get_all_args();
2045				$args_array = Array();
2046				$args_array = $reuse_feed_args;
2047				if ((isset($this->filters[0]['source_account']))
2048				&& ($this->filters[0]['source_account'] != ''))
2049				{
2050					if ($this->debug > 0) { echo 'bofilters: do_imap_search: this->filters[0][source_account] = ' .$this->filters[0]['source_account'].'<br />'."\r\n"; }
2051					$args_array['folder'] = $this->filters[0]['source_account'];
2052				}
2053				else
2054				{
2055					$args_array['folder'] = 'INBOX';
2056				}
2057				// add this to keep the error checking code (below) happy
2058				$args_array['do_login'] = True;
2059			}
2060			else
2061			{
2062				if ($this->debug_index_data == True) { echo 'bofilters: do_imap_search: creating new login email.mail_msg, cannot or not trying to reusing existing'.'<br />'; }
2063				// new login
2064				// (1) folder (if specified) - can be left empty or unset, mail_msg will then assume INBOX
2065				$args_array = Array();
2066				if ((isset($this->filters[0]['source_account']))
2067				&& ($this->filters[0]['source_account'] != ''))
2068				{
2069					if ($this->debug > 0) { echo 'bofilters: do_imap_search: this->filters[0][source_account] = ' .$this->filters[0]['source_account'].'<br />'."\r\n"; }
2070					$args_array['folder'] = $this->filters[0]['source_account'];
2071				}
2072				else
2073				{
2074					$args_array['folder'] = 'INBOX';
2075				}
2076				// (2) should we log in
2077				$args_array['do_login'] = True;
2078			}
2079			//$GLOBALS['phpgw']->msg = CreateObject("email.mail_msg");
2080			//$args_array = Array();
2081			//if ((isset($this->filters[0]['source_account']))
2082			//&& ($this->filters[0]['source_account'] != ''))
2083			//{
2084			//	if ($this->debug > 0) { echo 'bofilters: do_imap_search: this->filters[0][source_account] = ' .$this->filters[0]['source_account'].'<br />'."\r\n"; }
2085			//	$args_array['folder'] = $this->filters[0]['source_account'];
2086			//}
2087			//else
2088			//{
2089			//	$args_array['folder'] = 'INBOX';
2090			//}
2091			//$args_array['do_login'] = True;
2092
2093			$GLOBALS['phpgw']->msg->begin_request($args_array);
2094
2095			$initial_result_set = Array();
2096			$initial_result_set = $GLOBALS['phpgw']->msg->phpgw_search($imap_search_str);
2097			// sanity check on 1 returned hit, is it for real?
2098			if (($initial_result_set == False)
2099			|| (count($initial_result_set) == 0))
2100			{
2101				echo 'bofilters: do_imap_search: no hits or possible search error<br />'."\r\n";
2102				echo 'bofilters: do_imap_search: server_last_error (if any) was: "'.$GLOBALS['phpgw']->msg->phpgw_server_last_error().'"'."\r\n";
2103				// we leave this->result_set_mlist an an empty array, as it was initialized on class creation
2104			}
2105			else
2106			{
2107				$this->result_set = $initial_result_set;
2108				if ($this->debug > 0) { echo 'bofilters: do_imap_search: number of matches = ' .count($this->result_set).'<br />'."\r\n"; }
2109				// make a "fake" folder_info array to make things simple for get_msg_list_display
2110				$this->fake_folder_info['is_imap'] = True;
2111				$this->fake_folder_info['folder_checked'] = $GLOBALS['phpgw']->msg->get_arg_value('folder');
2112				$this->fake_folder_info['alert_string'] = 'you have search results';
2113				$this->fake_folder_info['number_new'] = count($this->result_set);
2114				$this->fake_folder_info['number_all'] = count($this->result_set);
2115				// retrieve user displayable data for each message in the result set
2116				$this->result_set_mlist = $GLOBALS['phpgw']->msg->get_msg_list_display($this->fake_folder_info,$this->result_set);
2117			}
2118			$GLOBALS['phpgw']->msg->end_request();
2119			//echo 'bofilters: do_imap_search: returned:<br />'; var_dump($this->result_set); echo "<br />\r\n";
2120		}
2121		*/
2122
2123
2124	// end of class
2125	}
2126?>
2127