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> </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 .' '.lang('filter name:').' ['.$this_filter['filtername'].']<br />'."\r\n" 977 .' '.lang('number of matches:').' ['.(string)$num_matches.']'.'<br />'."\r\n" 978 979 .' '.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> </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>'.' '.$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',' '); 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