├── .gitignore ├── demo_gmail_imap.php ├── LICENSE.md ├── README.md ├── peeker_layers.php ├── docs ├── peeker_toc.html ├── peeker_file.html ├── peeker_detector.html ├── peeker_body.html ├── peeker_detector_set.html ├── peeker_parts.html ├── peeker_connect.html ├── userguide.css ├── peeker_quickstart.html ├── peeker_header.html └── peeker.html ├── peeker_file.php ├── peeker_autoresponder_methods.php ├── peeker_db_methods.php ├── peeker_detector.php ├── peeker_ci_methods.php ├── peeker_detector_set.php ├── peeker_listserv_methods.php ├── peeker_body.php ├── peeker_connect.php ├── peeker_parts.php ├── peeker_header.php └── peeker.php /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /demo_gmail_imap.php: -------------------------------------------------------------------------------- 1 | get_message_count(); 19 | echo $cnt.' message waiting'; 20 | 21 | // EOF -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2008-2013 Daniel Cummings (https://github.com/sophistry) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Peeker - A framework for building automated email applications. 2 | ================ 3 | Please read the documentation here http://sophistry.github.com/peeker and in the **docs** directory 4 | 5 | Peeker is a wrapper around the IMAP/POP3 extension available in PHP. It helps you avoid learning all the strange things about talking to an email server and concentrate on getting email as objects and properly-decoded attachments onto your filesystem. 6 | 7 | Start with the *Quick Start* in peeker_quickstart.html. 8 | 9 | Basic usage: (gmail IMAP, make sure IMAP is enabled in your gmail account) 10 | 11 | // class files in peeker directory 12 | // change these lines 13 | // the path to the peeker.php class file 14 | include('path/to/peeker.php'); 15 | // this can also be a Google Apps email account 16 | $config['login']='your_gmail_address@gmail.com'; 17 | $config['pass']='your_gmail_password'; 18 | 19 | // do not change these lines 20 | // this should not change unless you are having problems 21 | $config['host']='imap.gmail.com'; 22 | $config['port']='993'; 23 | $config['service_flags'] = '/imap/ssl/novalidate-cert'; 24 | 25 | // you can definitely change these lines! 26 | // because your application code goes here 27 | $peeker = new peeker($config); 28 | $cnt = $peeker->get_message_count(); 29 | echo $cnt.' message waiting'; 30 | 31 | // EOF 32 | 33 | Advanced PHP developers only: Peeker also has a declarative Event programming architecture (Detector-Callback circuit) and "Traits-like" method layering (a simple Dependency Injection - just drop in a custom class and request the new methods be added to the email objects). -------------------------------------------------------------------------------- /peeker_layers.php: -------------------------------------------------------------------------------- 1 | register($this); 18 | 19 | // list the new methods to layer 20 | $methods = get_class_methods(get_class($layer_methods_object)); 21 | 22 | // overwrite any previous methods 23 | // bring in objects by reference 24 | foreach($methods as $method_name) 25 | { 26 | $this->layered_methods[$method_name] = &$layer_methods_object; 27 | } 28 | // return TRUE so it can be used in a detector circuit 29 | return TRUE; 30 | } 31 | 32 | public function __call($method, $args) 33 | { 34 | // make sure the function exists 35 | if(array_key_exists($method, $this->layered_methods)) 36 | { 37 | return call_user_func_array(array($this->layered_methods[$method], $method), $args); 38 | } 39 | throw new Exception ('Call to undefined method/class function: ' . $method); 40 | } 41 | } 42 | 43 | // NOTE: 44 | /* 45 | * layered method classes that implement this method layering 46 | * must have the register() function and $that property that holds 47 | * the object where methods will be added 48 | * To access the object use $this->that->some_method_or_property 49 | */ 50 | 51 | /* 52 | protected $that = null; 53 | 54 | public function register($that) 55 | { 56 | $this->that = $that; 57 | } 58 | */ 59 | 60 | // EOF peeker_layers.php -------------------------------------------------------------------------------- /docs/peeker_toc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Peeker Table of Contents 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Peeker Quickstart 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 |

Peeker

33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 45 | 46 | 47 |
48 | 49 | 50 |
51 | 52 | 53 | 54 |
55 | 56 |

Peeker Table of Contents

57 | 68 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /peeker_file.php: -------------------------------------------------------------------------------- 1 | $value) 32 | { 33 | $this->$key = $value; 34 | } 35 | } 36 | 37 | /** 38 | * access the filename 39 | * 40 | */ 41 | public function get_filename() 42 | { 43 | return $this->filename; 44 | } 45 | 46 | /** 47 | * access the data string 48 | * 49 | */ 50 | public function get_string() 51 | { 52 | return $this->string; 53 | } 54 | 55 | /** 56 | * access 57 | * 58 | */ 59 | public function get_encoding() 60 | { 61 | return $this->encoding; 62 | } 63 | 64 | /** 65 | * access 66 | * 67 | */ 68 | public function get_part_no() 69 | { 70 | return $this->part_no; 71 | } 72 | 73 | /** 74 | * access 75 | * 76 | */ 77 | public function get_cid() 78 | { 79 | return $this->cid; 80 | } 81 | 82 | /** 83 | * access 84 | * 85 | */ 86 | public function get_disposition() 87 | { 88 | return $this->disposition; 89 | } 90 | 91 | /** 92 | * access 93 | * 94 | */ 95 | public function get_bytes() 96 | { 97 | return $this->bytes; 98 | } 99 | 100 | /** 101 | * access 102 | * 103 | */ 104 | public function get_type() 105 | { 106 | return $this->type; 107 | } 108 | 109 | /** 110 | * access 111 | * standardize on lowercase, shortest names 112 | * eg. jpg not jpeg 113 | * could standardize on input... 114 | * 115 | */ 116 | public function get_subtype() 117 | { 118 | $st = strtolower($this->subtype); 119 | // reformat to standardize 120 | switch ($st) 121 | { 122 | case 'jpeg': 123 | $st = 'jpg'; 124 | break; 125 | } 126 | return $st; 127 | } 128 | 129 | // ------- detectors - return boolean ------- // 130 | /** 131 | * return true if file has a cid 132 | * which implies that it is used 133 | * in HTML inline 134 | * 135 | */ 136 | public function has_cid() 137 | { 138 | return (bool)$this->get_cid(); 139 | } 140 | 141 | } 142 | 143 | // EOF -------------------------------------------------------------------------------- /peeker_autoresponder_methods.php: -------------------------------------------------------------------------------- 1 | that = $that; 20 | } 21 | 22 | 23 | // ------- detectors - return boolean ------- // 24 | 25 | /* 26 | * is Return-Path header a bounce address <> ? 27 | * also, probably need to expand this to deal 28 | * with other variations of bounced messages 29 | * 30 | */ 31 | public function is_bounce() 32 | { 33 | // Return-Path header has several issues 34 | // 1) sometimes it is not included in the main properties of the header class 35 | // 2) there are sometimes 2 Return-Path headers! google seems to like to add one 36 | // assumes the header is always init capped like it's supposed to be 37 | //$return_path_string = $this->that->get_header_item('Return-Path'); 38 | // returns the string 39 | $return_path_string = $this->that->get_return_path(); 40 | //p($this->that); 41 | //p($return_path_string); 42 | $bounce = ($return_path_string) === '<>'; 43 | return $bounce; 44 | 45 | } 46 | 47 | 48 | /** 49 | * return true if email has a from address 50 | * and the address is not the no-return <> 51 | */ 52 | public function valid_from_email_for_response() 53 | { 54 | $from_address_valid = 0; 55 | $return_path_address_valid = 0; 56 | $from_string = $this->that->get_from(); 57 | //p($from_string); 58 | //p($return_path_string); 59 | // make sure it is a properly-formatted email address 60 | // and not a <> string indicating terminated bounce 61 | $return_path_address_valid = ($from_string !== '<>' AND !($this->is_bounce()) ); 62 | 63 | return $return_path_address_valid; 64 | } 65 | 66 | // ---------- callbacks - do something ------------// 67 | // send an email response 68 | // determine which email to use if it 69 | // is not obvious 70 | public function send_from($send_from_address='') 71 | { 72 | $to = $this->that->get_header_item('Return-Path'); 73 | p($to); 74 | p('About to send mail from: '.$send_from_address . ' to Return-Path: '. pvh($to)); 75 | // actually send the email using basic mail() function 76 | $subject = 'Thank you - your message was received'; 77 | $message = 'this is an automated reply sent to: '.$to; 78 | $headers = 'From: '.$send_from_address . "\r\n" . 79 | 'Reply-To: '. $send_from_address . "\r\n" . 80 | 'X-Mailer: Peeker AutoResponder'; 81 | mail($to, $subject, $message, $headers); 82 | } 83 | 84 | } 85 | //EOF -------------------------------------------------------------------------------- /docs/peeker_file.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Peeker File Class 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 |

Peeker File

28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 40 | 41 | 42 |
43 | 44 | 45 |
46 | 47 | 48 | 49 |
50 | 51 | 52 |

File Class

53 | 54 |

This class handles the email File data.

55 | 56 |

Important:  All of these functions are available through the message object parts_array property's file objectreturned by the Peeker class. Notated here as: $part

57 | 58 |

In order to access a file object you have to get the email message object (the object $e in the other peeker classes), then get that message object's property called parts_array using the $e->get_parts_array() method. You then loop over the parts_array file objects: 59 | $parts = $e->get_parts_array();
60 | foreach ($parts as $part)
61 | {
62 | $filename = $part->get_filename()
63 | } 64 |

65 | 66 | 67 |

Function Reference

68 | 69 |

get_filename()

70 |

Return the indicated filename of the attached file.

71 | $part->get_filename(); 72 | 73 |

get_string()

74 |

Return the fully-decoded string that IS the file.

75 | $part->get_string() 76 | 77 |

get_encoding()

78 |

Return which encoding was used on the attached file.

79 | $part->get_encoding() 80 | 81 |

get_part_no()

82 |

Return the part number for the attached file.

83 | $part->get_part_no(); 84 | 85 |

get_cid()

86 |

Return the CID (if any) for the attached file.

87 | $part->get_cid(); 88 | 89 |

get_disposition()

90 |

Return the disposition for the attached file.

91 | $part->get_disposition(); 92 | 93 |

get_bytes()

94 |

Return the size of the attached file (in bytes).

95 | $part->get_bytes(); 96 | 97 |

get_type()

98 |

Return the file type.

99 | $part->get_type(); 100 | 101 |

get_subtype()

102 |

Return the file subtype.

103 | $part->get_subtype() 104 | 105 |

Detectors - All Return TRUE or FALSE

106 | 107 |

has_cid()

108 |

TRUE if the file has a CID which indicates that it is carrying images that expect to be rendered as part of the message display.

109 | $part->has_cid(); 110 | 111 | 112 |
113 | 114 | 115 | 116 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /docs/peeker_detector.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Peeker Detector Class 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 |

Peeker Detector

28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 40 | 41 | 42 |
43 | 44 | 45 |
46 | 47 | 48 | 49 |
50 | 51 | 52 |

Detector Class

53 | 54 |

This class handles the email Detector data. In normal use you won't ever deal with this class. It is documented here for the intrepid.

55 | 56 |

Important:  All of these functions are available through the detector object returned by the Peeker Detector Set class. Notated here as: $detector

57 | 58 | 59 |

Function Reference

60 | 61 |

peeker_detector($dm=NULL, $dma=NULL, $cm=NULL, $cma=NULL, &$detector_set_parent=NULL)

62 |

Constructor

63 | $detector->peeker_detector(); 64 | 65 |

set_detector($method)

66 |

Send the name of a function that returns TRUE or FALSE.

67 | $detector->set_detector('ttrue'); 68 | 69 |

get_detector()

70 |

Get the detector function name as string.

71 | $detector->get_detector(); 72 | 73 |

set_detector_arguments(&$array)

74 |

Send the arguments that the detector function needs.

75 | $detector->set_detector_arguments(); 76 | 77 |

get_detector_arguments()

78 |

Get the detector arguments.

79 | $detector->get_detector_arguments(); 80 | 81 |

set_callback($method)

82 |

Set the callback function to be called if the detector is TRUE.

83 | $detector->set_callback(); 84 | 85 |

get_callback()

86 |

Get the callback function name as string.

87 | $detector->get_callback(); 88 | 89 |

set_callback_arguments(&$array)

90 |

Send the arguments that the callback function needs.

91 | $detector->set_callback_arguments(); 92 | 93 |

get_callback_arguments()

94 |

Get the callback arguments.

95 | $detector->get_callback_arguments(); 96 | 97 |

check(&$obj)

98 |

Returns the result of the registered detector function.

99 | $detector->check() 100 | 101 |

trigger(&$obj)

102 |

Calls the registered callback function.

103 | $detector->trigger() 104 | 105 |

set_active($bool)

106 |

Toggle the active state for the detector object. Turns off both check() and trigger() functions - if inactive both functions return NULL.

107 | $detector->set_active() 108 | 109 | 110 |
111 | 112 | 113 | 114 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /peeker_db_methods.php: -------------------------------------------------------------------------------- 1 | that 18 | // class and the base layer 19 | // the class var $that has to be present in every 20 | // set of methods used to layer 21 | // and the register function needs to be 22 | // there too 23 | protected $that = null; 24 | 25 | public function register($that) 26 | { 27 | $this->that = $that; 28 | } 29 | 30 | //---------- detectors ---------// 31 | 32 | /** 33 | * insert one message into one table 34 | * table needs fields named header and body 35 | */ 36 | public function insert_one() 37 | { 38 | $header_string = $this->that->get_header_string(); 39 | $plain = $this->that->get_plain(); 40 | $html = $this->that->get_html(); 41 | $data = array('header'=>$header_string, 'plain'=>$plain, 'html'=>$html); 42 | // assumes the db class is available 43 | $this->CI->db->insert($this->email_table,$data); 44 | } 45 | 46 | /** 47 | * COMPATIBILITY CODE - for migrating from imap_pop class 48 | * Get email array formated like the old imap_pop class 49 | * by calling functions in the message object stack 50 | * this->that allows you to plug this->that new peeker lib into an 51 | * existing set up 52 | * 53 | */ 54 | public function get_email_as_array() 55 | { 56 | // we are going to return this->that 57 | // array with all the email data 58 | // from one email message in it 59 | // hold the email addresses in arrays 60 | // for transport to the related db tables 61 | $email_arrays_array = array(); 62 | // email elements that are not arrays - 63 | // they are in the email table 64 | $email_strings_array = array(); 65 | 66 | // first array item for storage into the assoc array and then the db 67 | $email_strings_array['body'] = $this->that->get_body_string(); 68 | $email_arrays_array['parts'] = $this->that->get_parts_array(); 69 | 70 | // addresses come here as arrays 71 | // rather than strings so they are handled differently 72 | $address_keys_we_need_to_be_set = explode(' ', 'from to cc bcc reply_to sender return_path'); 73 | 74 | // turn each of the arrays of objects into arrays of arrays 75 | // with each address part getting encoding 76 | foreach ($address_keys_we_need_to_be_set as $key) 77 | { 78 | // use the access functions to 79 | // get the address arrays 80 | $fn = 'get_'.$key.'_array'; 81 | $email_arrays_array[$key] = $this->that->$fn(); 82 | } 83 | 84 | // the email_strings_array keys correspond to database fields 85 | // this->that will make it easy to add the data to the email table 86 | $email_strings_array['header'] = $this->that->get_header_string(); 87 | $email_strings_array['message_id'] = $this->that->get_message_id(); 88 | $email_strings_array['subject'] = $this->that->get_subject(); 89 | $email_strings_array['date_string']= $this->that->get_date(); 90 | $email_strings_array['date_sent_stamp'] = date("Y-m-d H:i:s",$this->that->get_udate()); 91 | // this->that is actually the datestamp of 92 | // when the message was put into this->that array 93 | // rather than "received" (which should better 94 | // be the datestamp for when the message 95 | // was accepted to the receiving SMTP server 96 | $email_strings_array['date_received_stamp'] = date("Y-m-d H:i:s"); 97 | $email_strings_array['size'] = $this->that->get_size(); 98 | 99 | $email_strings_array['text'] = $this->that->get_plain(); 100 | $email_strings_array['html'] = $this->that->get_html(); 101 | 102 | // set a temporary array item to enable 103 | // message to be deleted at mailserver to 104 | // sync with db, unset before db insert 105 | $email_strings_array['temp_msg_id']= $this->that->get_msgno(); 106 | $email_strings_array['email_fingerprint_auto'] = $this->that->get_fingerprint(); 107 | 108 | $this->that->log_state('Got email as array, message id: ' . $this->that->get_msgno()); 109 | 110 | // return two arrays in the array 111 | return array('strings'=>$email_strings_array,'arrays'=>$email_arrays_array); 112 | } 113 | 114 | 115 | } 116 | 117 | //EOF -------------------------------------------------------------------------------- /peeker_detector.php: -------------------------------------------------------------------------------- 1 | detector_set_parent =& $detector_set_parent; 46 | //p($this->detector_set_parent); 47 | 48 | if($dm !== NULL) $this->set_detector($dm); 49 | if($dma !== NULL) $this->set_detector_arguments($dma); 50 | 51 | if($cm !== NULL) $this->set_callback($cm); 52 | if($cma !== NULL) $this->set_callback_arguments($cma); 53 | 54 | //$this->detector_set_parent->log_array[] = 'loading detector: '.$dm; 55 | } 56 | 57 | /** 58 | * set the detector method 59 | * 60 | */ 61 | public function set_detector($method) 62 | { 63 | // magic string on detector 64 | // method inverts the boolean 65 | //p($this); 66 | if (strpos($method, $this->detector_set_parent->invert_detector_method_string)===0) 67 | { 68 | // this check will be inverted 69 | $this->invert_detector = TRUE; 70 | // trim off the string indicator 71 | $method = substr($method,$this->detector_set_parent->invert_detector_method_string_length); 72 | } 73 | $this->detector_method = $method; 74 | } 75 | 76 | /** 77 | * get the detector method 78 | * 79 | */ 80 | public function get_detector() 81 | { 82 | return ($this->invert_detector) ? $this->detector_set_parent->invert_detector_method_string.$this->detector_method : $this->detector_method; 83 | } 84 | 85 | /** 86 | * set the detector method arguments 87 | * 88 | */ 89 | public function set_detector_arguments(&$array) 90 | { 91 | $this->detector_method_arguments = $array; 92 | } 93 | 94 | /** 95 | * get the detector method arguments 96 | * 97 | */ 98 | public function get_detector_arguments() 99 | { 100 | return $this->detector_method_arguments; 101 | } 102 | 103 | /** 104 | * set the callback method 105 | * 106 | */ 107 | public function set_callback($method) 108 | { 109 | $this->callback_method = $method; 110 | } 111 | 112 | /** 113 | * get the callback method 114 | * 115 | */ 116 | public function get_callback() 117 | { 118 | return $this->callback_method; 119 | } 120 | 121 | /** 122 | * set the callback method arguments 123 | * 124 | */ 125 | public function set_callback_arguments(&$array) 126 | { 127 | $this->callback_method_arguments = $array; 128 | } 129 | 130 | /** 131 | * get the callback method arguments 132 | * 133 | */ 134 | public function get_callback_arguments() 135 | { 136 | return $this->callback_method_arguments; 137 | } 138 | 139 | /** 140 | * call the detector method 141 | * to get boolean 142 | * override to send detector call 143 | * to an object 144 | */ 145 | public function check(&$obj) 146 | { 147 | if ( ! $this->active) return FALSE; 148 | // this line can target any function 149 | $result = call_user_func_array(array(&$obj, $this->detector_method), array(&$this->detector_method_arguments)); 150 | // XOR works like NOT here, bit 2 inverts bit 1 if bit 2 is TRUE 151 | // 0 XOR 0 = 0 ... normal 152 | // 1 XOR 0 = 1 ... normal 153 | // 0 XOR 1 = 1 ... invert 154 | // 1 XOR 1 = 0 ... invert 155 | return $result XOR $this->invert_detector; 156 | } 157 | 158 | /** 159 | * call the callback method 160 | * override to send callback 161 | * to an object 162 | */ 163 | public function trigger(&$obj) 164 | { 165 | if ( ! $this->active) return FALSE; 166 | // this line can target any function 167 | $result = call_user_func_array(array(&$obj, $this->callback_method), array(&$this->callback_method_arguments)); 168 | return $result; 169 | } 170 | 171 | 172 | /** 173 | * turn it on or off 174 | * 175 | */ 176 | public function set_active($bool) 177 | { 178 | $this->active = $bool; 179 | } 180 | } 181 | 182 | //EOF -------------------------------------------------------------------------------- /docs/peeker_body.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Peeker Body Class 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 |

Peeker Body

28 |
29 | 30 | 31 | 32 | 33 | 34 | 39 | 40 | 41 |
42 | 43 | 44 |
45 | 46 | 47 | 48 |
49 | 50 | 51 |

Body Class

52 | 53 |

This class handles the email Body data.

54 | 55 |

Important:  All of these functions are available through the message object returned by the Peeker class. Notated here as: $e

56 | 57 | 58 | 59 |

Function Reference

60 | 61 |

get_body()

62 |

Pull in the email message body data. Returns true. The message properties are populated. This generally includes the PLAIN and HTML version of an email. If there is a parts class set this function also gets the email parts.

63 | $e->get_body(); 64 | 65 |

get_body_string()

66 |

Get the raw, undecoded body string.

67 | $e->get_body_string(); 68 | 69 |

get_plain()

70 |

Get the PLAIN part of a multipart/alternative email.

71 | $e->get_plain(); 72 | 73 |

get_html()

74 |

Get the HTML part of a multipart/alternative email.

75 | $e->get_html() 76 | 77 |

get_html_filtered()

78 |

Specialized. If you have set up a function to manipulate the HTML, you can put the changed HTML into the html_filtered property and get it with this function. This is for specialized uses.

79 | $e->get_html_filtered(); 80 | 81 |

get_date_pulled()

82 |

Get the date the email was pulled from the server and put into this object.

83 | $e->get_date_pulled(); 84 | 85 |

Detectors - All Return TRUE or FALSE

86 | 87 |

preg_match_PLAIN($pattern)

88 |

TRUE if regular expression matches the fully-decoded PLAIN part of the email message.

89 | $e->preg_match_PLAIN('/spamword/'); 90 | 91 |

preg_match_HTML($pattern)

92 |

TRUE if regular expression matches the fully-decoded HTML part of the email message.

93 | $e->preg_match_HTML('/spamword/'); 94 | 95 |

has_PLAIN_not_HTML()

96 |

TRUE if the email has a PLAIN part but no HTML part.

97 | $e->has_PLAIN_not_HTML(); 98 | 99 |

Callback functions

100 | 101 |

fix_MIME_from_sender($from_str)

102 |

Specialized. Brute force. Sometimes a certain email sender's client will mess up some aspect of the email. This function identifies a sender and 'fixes' a common MIME problem: the parts are incompletely specified. This function looks for an html open tag in the first 25 lines and rebuilds the email object to use the html data in the HTML property.

103 | $e->fix_MIME_from_sender('steve@aol.com'); 104 | 105 |

put_PLAIN_into_HTML()

106 |

Put the PLAIN part data into the HTML property.

107 | $e->put_PLAIN_into_HTML(); 108 | 109 |

wrap_HTML_with_HTML_tags()

110 |

Specialized. Brute force. Sometimes emails that are supposed to be HTML don't have html tags. This wraps those.

111 | $e->wrap_HTML_with_HTML_tags(); 112 | 113 |
114 | 115 | 116 | 117 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /docs/peeker_detector_set.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Peeker Class 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 |

Peeker

28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 40 | 41 |
42 | 43 | 44 |
45 | 46 | 47 | 48 |
49 | 50 | 51 |

Peeker Detector Set Class

52 | 53 |

This is the interface from the main Peeker Class to the Detectors.

54 | 55 |

The Peeker Detector system lets you pull and process email in a declarative programming style. This style can make logic clearer than a bunch of nested if statements. It also helps you modularize reuseable detector components. Declarative programming can help you split tasks among multiple programmers.

56 |

Note: The Peeker Detector system only works if you use the get_message() function or create a custom message class and tell Peeker to use that class: $this->peeker->set_message_class('peeker_header');

57 | 58 |

Detector array

59 | 60 |

This class holds an array of detectors.

61 | 62 |

Function Reference

63 | 64 | 65 |

make_detector($detector_method, $detector_method_arguments, $callback_method, $callback_method_arguments)

66 | 67 |

Create a detector circuit that "detects" an event and triggers a callback when the event is TRUE. Returns the detector object when created.

68 | // in_from is a custom message class method
69 | // that tests the from address with the strpos() function
70 | $detector_method = 'in_from';
71 | $detector_method_arguments = 'spammer@spamtown.us';

72 | // set_delete is a method in the custom header class
73 | $callback_method = 'set_delete';
74 | $callback_method_arguments = TRUE;

75 | $detector = $this->peeker->make_detector_set();
76 | $detector->make_detector($detector_method, $detector_method_arguments, $callback_method, $callback_method_arguments);
77 | 78 |

Once you have a detector_set object you can add detectors. The detector set and its detectors must be created before calling the message() method.

79 | 80 |

A note on detector method naming and the not__ string invertor

81 | 82 |

Detector methods are functions that return TRUE or FALSE. Instead of writing the negated form of every function ( for instance, in_from() and not_in_from() ) this detector set class defines a string that will tell the detector to automatically 'NOT' the returned value. So instead of writing an actual "not_in_from()" function and bloating the class you can just pass the function name as 'not__in_from' (note double-underscore).

83 |

The not__ string can be used on any detector method (any method that returns a boolean). The return value of that function will be inverted.

84 | 85 | // strpos_from is a custom message class method
86 | // that tests the from address with the strpos() function
87 | $detector_method = 'not__in_from'; // using the not__ invertor string
88 | $detector_method_arguments = 'secretaddress@mywordpresssite.us';

89 | // set_delete is a method in the custom header class
90 | $callback_method = 'set_delete';
91 | $callback_method_arguments = TRUE;

92 | $detector = $this->peeker->make_detector_set();
93 | $detector->make_detector($detector_method, $detector_method_arguments, $callback_method, $callback_method_arguments);
94 | 95 |
96 | 97 | 98 | 99 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /docs/peeker_parts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Peeker Parts Class 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 |

Peeker Parts

28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 40 | 41 | 42 |
43 | 44 | 45 |
46 | 47 | 48 | 49 |
50 | 51 | 52 |

Parts Class

53 | 54 |

This class handles the email Parts data.

55 | 56 |

Important:  All of these functions are available through the message object returned by the Peeker class. Notated here as: $e

57 | 58 | 59 |

Function Reference

60 | 61 |

parts()

62 |

Get the email and extract and decode all the parts into a nice array property parts_array. Calls body class body() function which calls the extract_parts() function. Note: you must call this ( or body() ) before any detectors will work.

63 | $e->parts(); 64 | 65 |

get_parts_count()

66 |

Returns the count of parts found - not including meta parts like nodes - just real parts.

67 | $e->get_parts_count(); 68 | 69 |

get_parts_array()

70 |

Returns the array of parts built by the parts() function.

71 | $e->get_parts_array(); 72 | 73 |

Detectors - All Return TRUE or FALSE

74 | 75 |

has_attachment()

76 |

TRUE if the message has an attachment.

77 | $e->has_attachment(); 78 | 79 |

has_at_least_one_attachment_with_disposition($disp)

80 |

TRUE if the message has an attachment with specified disposition. Disposition can be inline or attachment. This is for detecting if an image has been attached to the message and it expects the client to render it inline without calling out to a webserver to get it.

81 | $e->has_at_least_one_attachment_with_disposition(); 82 | 83 |

has_at_least_one_attachment($subtype)

84 |

TRUE if any attachment is of the specified subtype (e.g., jpg, pdf, xls, etc...). Function expects the 3-letter version of the file type.

85 | $e->has_at_least_one_attachment('jpg'); // detector for moblog 86 | 87 |

Callbacks

88 | 89 |

insert_HTML($str)

90 |

Brute force. Looks for marker in HTML and inserts the string so it appears inside the html just before the closing body tags.

91 | $e->insert_HTML('<br />This email is confidential'); 92 | 93 |

insert_PLAIN($str)

94 |

Appends string to the end of the PLAIN text.

95 | $e->insert_PLAIN("--\nThis is my signature"); 96 | 97 |

rewrite_html_transform_img_tags($base_url='')

98 |

Attempts to change the HTML that points to local resources (cid: style img tags) so that the HTML points to remote resources instead. Pass the base_url where the images will be. If it is not passed, the img tags will be re-written as relative paths.

99 | $e->rewrite_html_transform_img_tags(); 100 | 101 |

render_first_jpeg()

102 |

Testing. Show the first jpeg attached to the email. Renders it to the browser (sends jpeg header) so it will not work if other data has already been sent.

103 | $e->render_first_jpeg(); 104 | 105 |

save_header_string($file_name='header_string.txt')

106 |

Save the header string somewhere as a file.

107 | $e->save_header_string(); 108 | 109 |

save_body_string($file_name='body_string.txt')

110 |

Save the raw, undecoded body string somewhere as a file.

111 | $e->save_body_string(); 112 | 113 |

save_PLAIN($file_name='PLAIN.txt')

114 |

Save the PLAIN string somewhere as a text file.

115 | $e->save_PLAIN(); 116 | 117 |

save_HTML($file_name='HTML.html')

118 |

Save the HTML string somewhere as an HTML file.

119 | $e->save_HTML(); 120 | 121 |

save_all_attachments($dir=NULL)

122 |

Dumps all the attachments to the filesystem.

123 | $e->save_all_attachments(); 124 | 125 |

get_file_name_array()

126 |

Returns the array containing all the filenames of the attached files.

127 | $e->get_file_name_array(); 128 | 129 |
130 | 131 | 132 | 133 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /peeker_ci_methods.php: -------------------------------------------------------------------------------- 1 | that = $that; 25 | } 26 | 27 | //---------- detectors ---------// 28 | 29 | //---------- detector-callback short circuit ---------// 30 | 31 | /** 32 | * appeal to list users to allow stranger's message in 33 | * this is usually called right after a check of approved senders 34 | * this is in CI lib so we can talk to CI db stack 35 | */ 36 | public function unknown_sender_appeal($list_name) 37 | { 38 | // $list_name should not be the mailing address, but 39 | 40 | $this->CI =& get_instance(); 41 | // insert or update this contact 42 | // since we are going to send an appeal 43 | // contact type becomes 'pending' = 3 44 | $this->CI->load->model('Contacts_model'); 45 | $this->CI->load->model('Lists_model'); 46 | $this->CI->load->model('Contacts_Lists_model'); 47 | // add the contact as an email type record, 48 | // get contact id 49 | $address_from =$this->that->get_address_from(); 50 | $personal_from =$this->that->get_personal_from(); 51 | $contact_id = $this->CI->Contacts_model->add_contact_email($address_from, $personal_from); 52 | //pe($contact_id); 53 | // get list id 54 | $list_id = $this->CI->Lists_model->get_id_from_name($list_name); 55 | //pe($list_id); 56 | // set the join record to pending state 57 | // returns TRUE if the record exists and 58 | // the type was changed by this call 59 | // (e.g., from no record to 3 or from 0 to 3) 60 | $type_changed = $this->CI->Contacts_Lists_model->insert_or_change_type($contact_id, $list_id, '0', '3'); 61 | // if the type changed, that means we haven't seen this sender before 62 | // send out the appeal 63 | if ($type_changed) 64 | { 65 | // prepare the email to send for the appeal 66 | $this->CI->load->helper('url'); 67 | // appeal URL should be a parameter or property 68 | $is_not_html = TRUE; // not HTML 69 | $file_array = array(); // no attachments 70 | // listname should not be hardcoded 71 | $link_back = site_url().'em/appeal/'.urlencode($list_name) .'/'. urlencode($address_from); 72 | // this should be a view 73 | $body = "$address_from sent a message to the $list_name list. \n\n They are not an approved sender. \n\n Do you recognize the email address? \n\n Click this link to add this person to the approved sender list. \n\n $link_back \n\n Note: By clicking the link you will let their message (and any future messages they send) through to everyone on the list. But, even though they will be able to send messages to the list membership, they will not receive any messages from $list_name." ; 74 | //pe($body); 75 | $this->that->log_state('Sending allow? appeal with link_back.'); 76 | 77 | /* 78 | _sendemail($list_name, $this->that->get_resend_to_array(),array(), 79 | $list_name, 80 | 'Advanced List', 81 | '[Assist - LISTNAME]: '.$this->that->get_subject(), 82 | $body, $is_not_html, $file_array); 83 | */ 84 | 85 | } 86 | } 87 | 88 | //---------- callbacks ---------// 89 | 90 | /* 91 | * remove any address that has a tag 92 | * 93 | */ 94 | public function remove_registered_from_resend_to($tag='') 95 | { 96 | // because of the way the resend_to stripping works 97 | // it was easier just to load up two independent detectors 98 | // than to try to fix the remove_from_resend_to_array() method 99 | // should get these from a db table after searching on tag 100 | $adds = array('d+nopolitics@polysense.com', 'd+remove@polysense.com'); 101 | foreach ($adds as $add) 102 | { 103 | // use the layered method in the listserv code 104 | // this shows that the layers can work together 105 | $this->that->remove_from_resend_to($add); 106 | } 107 | } 108 | 109 | 110 | /** 111 | * taken/modified from part class: 112 | * save the HTML part to the view folder 113 | * this will overwrite any file 114 | * returns TRUE if file was created 115 | */ 116 | public function save_HTML_as_view_file($view_file='HTML.html') 117 | { 118 | // put the file into the views dir 119 | // make sure the directory exists 120 | $dir = APPPATH . 'views/emails/' . $this->that->get_fingerprint() . '/'; 121 | $dir = $this->that->_make_dir($dir); 122 | $fn = $dir . $view_file; 123 | //pe($fn); 124 | 125 | $template = ($this->that->HTML === '') ? $this->that->PLAIN : $this->that->HTML; 126 | // make sure we don't write a blank file 127 | if (trim($template) !== '') $this->that->_save_file($fn, $template); 128 | // call the parent function to do the "real" thing 129 | // and write the file to the attachments dir 130 | //parent::save_HTML($file_name); 131 | // should determine if the file was written 132 | // and if not, return FALSE 133 | // this will report the file is there unless 134 | // the file is deleted after use 135 | return is_file($fn); 136 | } 137 | 138 | 139 | 140 | /** 141 | * treat the email message as if it were 142 | * a view file by loading the HTML from 143 | * the file 144 | * HTML must have been saved to file 145 | * for this to work! 146 | * loads up a bunch of standard vars that 147 | * email messages can use to get rewritten 148 | * Also, you can use this->load->vars() to get 149 | * data into the view here 150 | * 151 | */ 152 | public function load_email_as_view($view_file='HTML.html') 153 | { 154 | // connect to the CI stack 155 | $this->CI =& get_instance(); 156 | $this->CI->load->library('parser'); 157 | 158 | // construct the whole filename - for view file 159 | // should store this in class var 160 | $view_file = 'emails/' . $this->that->get_fingerprint() . '/' .$view_file; 161 | $data['from'] = $this->that->get_address_from(); 162 | //$data['to'] = $this->that->get_address_to(); 163 | 164 | // build up an array that the view can understand 165 | $em_arr = array(); 166 | $arr = $this->that->get_resend_to_array(); 167 | foreach($arr as $em) 168 | { 169 | $em_arr[] = array('email'=>$em); 170 | } 171 | 172 | $data['subscribers'] = $em_arr; 173 | // have to link up the email address with the birthdate 174 | //$data['my_birthday'] = $this->get_address_from(); 175 | //$this->CI->load->vars($data); 176 | //$rendered_view = $this->CI->load->view($view_file,$data,TRUE); 177 | $rendered_view = $this->CI->parser->parse($view_file,$data,TRUE); 178 | //p('exiting inside load_email_as_view() function...');pe($rendered_view); 179 | } 180 | 181 | 182 | 183 | } 184 | 185 | //EOF -------------------------------------------------------------------------------- /docs/peeker_connect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Peeker Connect Class 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 |

Peeker Connect

28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 40 | 41 |
42 | 43 | 44 |
45 | 46 | 47 | 48 |
49 | 50 | 51 |

Peeker Connect Class

52 | 53 |

This class connects to IMAP or POP servers and tells you if there are messages waiting.

54 | 55 |

Features:

56 | 57 | 63 | 64 |

Note:  This class can be used alone as a lightweight way to check if an email account has messages waiting, but for most email retrieval tasks, it is designed to work with the main peeker class; it is an ancestor class. This class only connects to the IMAP or POP server and detects if there are any messages waiting. You need to use the main Peeker class to actually count and get the messages.

65 | 66 |

Connecting to IMAP or POP server

67 | 68 |

Enter your mailserver connection data and run a few lines of code.

69 | 70 |

There are many different types of mail servers. Each one has its own requirements.

71 | 72 |

Example POP connection

73 | 74 |

Connect to a password-protected POP server using the Peeker Connect class:

75 | 76 | 77 | $config['login']='your_username';
78 | $config['pass']='your_password';
79 | $config['host']='mail.example.com';
80 | $config['port']='110';
81 | $config['service_flags'] = '/pop3/notls';
82 |
83 | // you can pass config as second parameter or use initialize()
84 | // $this->load->library('peeker_connect', $config);
85 |
86 | $this->load->library('peeker_connect');
87 | $this->peeker_connect->initialize($config);
88 | if ($this->peeker_connect->message_waiting())
89 | {
90 | echo "You've got mail!";
91 | }
92 |
93 | $this->peeker_connect->close();
94 |
95 | // now tell us the story of the connection
96 | print_r($this->peeker_connect->trace()); 97 |
98 | 99 | 100 |

Config items

101 | 102 | 103 |

IMAP and POP server data. Star* indicates config item is required to be set to something other than default.

104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 |
ConfigDefault ValueOptionsDescription
loginNULL*any valid login stringIMAP or POP server login name.
passNULL*any valid password stringIMAP or POP server password.
hostNULL*the fully-qualified domain name for a mailservere.g., mail.example.com
portNULL*the port number, typically 110, 143, 443, 993, etc...e.g., 110 (for POP3)
service_flagsNULL*any valid service flag string (see http://php.net/imap-open)Change these to fit your server settings. For instance, some servers require SSL and Certificates and some don't.
mailboxNULLany valid mailbox name (used only for IMAP, not POP)POP servers don't use a mailbox name, use 'INBOX' for IMAP or another mailbox name in the IMAP account
149 | 150 |

The config array can be sent on library load, by passing to the initialize() method (as shown on this page), or by using the Code Igniter config file convention (see the CI manual for details).

151 | 152 |

Function Reference

153 | 154 |

$this->peeker_connect->initialize($config_array)

155 |

After loading the library, call initialize() to establish a connection.

156 | $this->peeker_connect->initialize($config_array); 157 | 158 |

$this->peeker_connect->is_connected()

159 |

After initialize is called, you can check the connection. Returns TRUE or FALSE.

160 | $this->peeker_connect->is_connected(); 161 | 162 |

$this->peeker_connect->message_waiting()

163 |

Check if there is at least one message waiting. Returns TRUE or FALSE.

NOTE: Only returns the messages waiting on the initial connection with the server.

164 |

This is useful for a "You've got mail!" indicator.

165 | $this->peeker_connect->message_waiting(); 166 |

Note: The connect class does not count messages or get messages (you need the main peeker class for that).

167 | 168 |

$this->peeker_connect->close()

169 |

Close the IMAP or POP connection.

170 | $this->peeker_connect->close(); 171 |
172 | 173 | 174 | 175 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /peeker_detector_set.php: -------------------------------------------------------------------------------- 1 | invert_detector_method_string_length = strlen($this->invert_detector_method_string); 38 | 39 | $this->log_array[] = 'LOADING detector-set class'; 40 | } 41 | 42 | /** 43 | * run the check() method for detectors 44 | * in parallel with the trigger() method 45 | * for methods that returned TRUE on check() 46 | * Receive $em_message_object by reference 47 | * 48 | */ 49 | public function run(&$em_message_obj) 50 | { 51 | // reset the abort state for detectors 52 | // so that if we are in a multi-message 53 | // iteration each message can have its 54 | // own abort determination procedure. 55 | // abort is used inside the detector loop 56 | // on a per-message basis so that one 57 | // detector can bail and tell the rest 58 | // not to execute 59 | $this->detectors_abort(FALSE); 60 | // NOTE: this design keeps detector checks 61 | // and callbacks together running in parallel. 62 | // check the registered detectors to see if the 63 | // detector method returns TRUE 64 | 65 | // run the check() method and record 66 | // the result for every detector 67 | $this->detector_trigger_array = array(); 68 | foreach ($this->detector_array as $detector) 69 | { 70 | if ($this->detectors_abort) 71 | { 72 | $this->log_array[] = 'detectors_abort is TRUE, aborting.'; 73 | break; 74 | } 75 | //p($detector); 76 | // $em_message_obj is received by reference 77 | // record the result in detector_trigger_array 78 | $trigger_it = $this->detector_trigger_array[] = $detector->check($em_message_obj); 79 | 80 | $this->log_array[] = (($detector->invert_detector)?$this->invert_detector_method_string:'') . $detector->detector_method . ' Trigger? '. var_export($trigger_it,TRUE) . ' args: ' . var_export($detector->detector_method_arguments, TRUE); 81 | // run the corresponding callback 82 | // for every check() that returned TRUE 83 | if ($trigger_it) 84 | { 85 | $detector->trigger($em_message_obj); 86 | $this->log_array[] = 'triggered callback: ' . $detector->callback_method . ' with: ' . var_export($detector->callback_method_arguments,TRUE); 87 | } 88 | 89 | } 90 | } 91 | 92 | 93 | /** 94 | * just run the triggers 95 | * testing just triggers 96 | */ 97 | public function run_triggers(&$em_message_obj) 98 | { 99 | $this->detector_trigger_array = array(); 100 | foreach ($this->detector_array as $detector) 101 | { 102 | if ($this->detectors_abort) break; 103 | //p($detector); 104 | // $em_message_obj is received by reference 105 | $this->detector_trigger_array[] = $detector->check($em_message_obj); 106 | } 107 | } 108 | 109 | /** 110 | * just run the callbacks 111 | * with optional culling 112 | * send TRUE to run all callbacks 113 | * for testing or reporting 114 | */ 115 | public function run_callbacks(&$em_message_obj, $all=FALSE) 116 | { 117 | if ($all===FALSE) 118 | { 119 | // optimize, remove the FALSE detectors 120 | $this->detector_trigger_array = array_filter($this->detector_trigger_array); 121 | } 122 | // if there is a match or matches, trigger the 123 | // registered callback(s) to make something happen 124 | // generally, this would be to delete the message 125 | // at the mail server before downloading 126 | // or it could trigger db insertion, string 127 | // cleanup or routing routines per message 128 | foreach ($this->detector_trigger_array as $key => $trigger) 129 | { 130 | // $em_message_obj is received as reference so 131 | // the functions can operate on the data 132 | // if detectors aborted in detect phase, don't run callbacks 133 | if ($this->detectors_abort) break; 134 | $this->detector_array[$key]->trigger($em_message_obj); 135 | } 136 | } 137 | 138 | 139 | //------DETECTOR-CALLBACK methods------// 140 | 141 | /** 142 | * add a detector to be checked on every 143 | * message in message() 144 | * 145 | */ 146 | public function _add_detector($detector_obj) 147 | { 148 | $this->detector_array[] = $detector_obj; 149 | //p($this->detector_array); 150 | $this->log_array[] = 'Added detector: ' . $detector_obj->get_detector(); 151 | $this->log_array[] = 'Detector args: ' . var_export($detector_obj->get_detector_arguments(), TRUE); 152 | $this->log_array[] = 'Added callback: ' . $detector_obj->get_callback(); 153 | $this->log_array[] = 'Callback args: ' . var_export($detector_obj->get_callback_arguments(),TRUE); 154 | } 155 | 156 | /** 157 | * wrapper to add a detector 158 | * also, turns on detector checking 159 | * 160 | */ 161 | public function detector($dm, $dma, $cm, $cma) 162 | { 163 | include_once('peeker_detector.php'); 164 | $detector = new peeker_detector($dm, $dma, $cm, $cma, $this); 165 | $this->_add_detector($detector); 166 | $this->set_detectors_state(TRUE); 167 | return $detector; 168 | } 169 | 170 | /** 171 | * wrapper to set up detector phase 172 | * method that will be called on every 173 | * message iteration - at detect stage 174 | * must send method/function name or array 175 | * just like for detector() method 176 | * should fix this so that it doesn't use 177 | * ttrue() method 178 | * 179 | */ 180 | public function detect_phase($dm, $dma='') 181 | { 182 | return $this->detector($dm, $dma, 'ttrue', ''); 183 | } 184 | 185 | /** 186 | * wrapper to set up callback phase 187 | * method that will be called on every 188 | * message iteration - at callback stage 189 | * after detector has been checked 190 | * must send method/function name or array 191 | * just like for detector() method 192 | * should fix this so that it doesn't use 193 | * ttrue() method 194 | * 195 | */ 196 | public function callback_phase($cm, $cma='') 197 | { 198 | return $this->detector('ttrue', '', $cm, $cma); 199 | } 200 | 201 | 202 | /** 203 | * turn on/off detectors "globally" 204 | * but keep them around 205 | * 206 | */ 207 | public function set_detectors_state($state) 208 | { 209 | $this->detectors_on = $state; 210 | } 211 | 212 | /** 213 | * abort the detector loop (do not trigger or check) 214 | * in messages() method 215 | */ 216 | public function detectors_abort($state) 217 | { 218 | $this->log_array[] = 'Setting detectors abort state: ' . var_export($state,TRUE); 219 | $this->detectors_abort = $state; 220 | } 221 | 222 | /** 223 | * accessor method: get the state array 224 | */ 225 | public function get_log_array() 226 | { 227 | return $this->log_array; 228 | } 229 | 230 | /** 231 | * settor method: set the state array 232 | */ 233 | public function set_log_array($in) 234 | { 235 | $this->log_array = $in; 236 | } 237 | 238 | /** 239 | * get most recent detector firing state 240 | * allows detectors to query previous detector 241 | * used by detectors / message objects to 242 | * do something special (e.g., abort the 243 | * detector loop) if any arbitrary detector 244 | * returns true - check right after in detector stack 245 | * 246 | */ 247 | public function _get_previous_detector_state() 248 | { 249 | return end($this->detector_trigger_array); 250 | } 251 | 252 | } 253 | 254 | //EOF -------------------------------------------------------------------------------- /docs/userguide.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: Lucida Grande, Verdana, Geneva, Sans-serif; 5 | font-size: 14px; 6 | color: #333; 7 | background-color: #fff; 8 | } 9 | 10 | a { 11 | color: #0134c5; 12 | background-color: transparent; 13 | text-decoration: none; 14 | font-weight: normal; 15 | } 16 | a:visited { 17 | color: #0134c5; 18 | background-color: transparent; 19 | text-decoration: none; 20 | } 21 | a:hover { 22 | color: #000; 23 | text-decoration: none; 24 | background-color: transparent; 25 | } 26 | 27 | #breadcrumb { 28 | float: left; 29 | background-color: transparent; 30 | margin: 10px 0 0 42px; 31 | padding: 0; 32 | font-size: 10px; 33 | color: #666; 34 | } 35 | #breadcrumb_right { 36 | float: right; 37 | width: 175px; 38 | background-color: transparent; 39 | padding: 8px 8px 3px 0; 40 | text-align: right; 41 | font-size: 10px; 42 | color: #666; 43 | } 44 | #nav { 45 | background-color: #494949; 46 | margin: 0; 47 | padding: 0; 48 | } 49 | #nav2 { 50 | background: #fff url(images/nav_bg_darker.jpg) repeat-x left top; 51 | padding: 0 310px 0 0; 52 | margin: 0; 53 | text-align: right; 54 | } 55 | #nav_inner { 56 | background-color: transparent; 57 | padding: 8px 12px 0 20px; 58 | margin: 0; 59 | font-family: Lucida Grande, Verdana, Geneva, Sans-serif; 60 | font-size: 11px; 61 | } 62 | 63 | #nav_inner h3 { 64 | font-size: 12px; 65 | color: #fff; 66 | margin: 0; 67 | padding: 0; 68 | } 69 | 70 | #nav_inner .td_sep { 71 | background: transparent url(images/nav_separator_darker.jpg) repeat-y left top; 72 | width: 25%; 73 | padding: 0 0 0 20px; 74 | } 75 | #nav_inner .td { 76 | width: 25%; 77 | } 78 | #nav_inner p { 79 | color: #eee; 80 | background-color: transparent; 81 | padding:0; 82 | margin: 0 0 10px 0; 83 | } 84 | #nav_inner ul { 85 | list-style-image: url(images/arrow.gif); 86 | padding: 0 0 0 18px; 87 | margin: 8px 0 12px 0; 88 | } 89 | #nav_inner li { 90 | padding: 0; 91 | margin: 0 0 4px 0; 92 | } 93 | 94 | #nav_inner a { 95 | color: #eee; 96 | background-color: transparent; 97 | text-decoration: none; 98 | font-weight: normal; 99 | } 100 | 101 | #nav_inner a:visited { 102 | color: #eee; 103 | background-color: transparent; 104 | text-decoration: none; 105 | } 106 | 107 | #nav_inner a:hover { 108 | color: #ccc; 109 | text-decoration: none; 110 | background-color: transparent; 111 | } 112 | 113 | #masthead { 114 | margin: 0 40px 0 35px; 115 | padding: 0 0 0 6px; 116 | border-bottom: 1px solid #999; 117 | } 118 | 119 | #masthead h1 { 120 | background-color: transparent; 121 | color: #e13300; 122 | font-size: 18px; 123 | font-weight: normal; 124 | margin: 0; 125 | padding: 0 0 6px 0; 126 | } 127 | 128 | #searchbox { 129 | background-color: transparent; 130 | padding: 6px 40px 0 0; 131 | text-align: right; 132 | font-size: 10px; 133 | color: #666; 134 | } 135 | 136 | #img_welcome { 137 | border-bottom: 1px solid #D0D0D0; 138 | margin: 0 40px 0 40px; 139 | padding: 0; 140 | text-align: center; 141 | } 142 | 143 | #content { 144 | margin: 20px 40px 0 40px; 145 | padding: 0; 146 | } 147 | 148 | #content p { 149 | margin: 12px 20px 12px 0; 150 | } 151 | 152 | #content h1 { 153 | color: #e13300; 154 | border-bottom: 1px solid #666; 155 | background-color: transparent; 156 | font-weight: normal; 157 | font-size: 24px; 158 | margin: 0 0 20px 0; 159 | padding: 3px 0 7px 3px; 160 | } 161 | 162 | #content h2 { 163 | background-color: transparent; 164 | border-bottom: 1px solid #999; 165 | color: #000; 166 | font-size: 18px; 167 | font-weight: bold; 168 | margin: 28px 0 16px 0; 169 | padding: 5px 0 6px 0; 170 | } 171 | 172 | #content h3 { 173 | background-color: transparent; 174 | color: #333; 175 | font-size: 16px; 176 | font-weight: bold; 177 | margin: 16px 0 15px 0; 178 | padding: 0 0 0 0; 179 | } 180 | 181 | #content h4 { 182 | background-color: transparent; 183 | color: #444; 184 | font-size: 14px; 185 | font-weight: bold; 186 | margin: 22px 0 0 0; 187 | padding: 0 0 0 0; 188 | } 189 | 190 | #content img { 191 | margin: auto; 192 | padding: 0; 193 | } 194 | 195 | #content code { 196 | font-family: Monaco, Verdana, Sans-serif; 197 | font-size: 12px; 198 | background-color: #f9f9f9; 199 | border: 1px solid #D0D0D0; 200 | color: #002166; 201 | display: block; 202 | margin: 14px 0 14px 0; 203 | padding: 12px 10px 12px 10px; 204 | } 205 | 206 | #content pre { 207 | font-family: Monaco, Verdana, Sans-serif; 208 | font-size: 12px; 209 | background-color: #f9f9f9; 210 | border: 1px solid #D0D0D0; 211 | color: #002166; 212 | display: block; 213 | margin: 14px 0 14px 0; 214 | padding: 12px 10px 12px 10px; 215 | } 216 | 217 | #content .path { 218 | background-color: #EBF3EC; 219 | border: 1px solid #99BC99; 220 | color: #005702; 221 | text-align: center; 222 | margin: 0 0 14px 0; 223 | padding: 5px 10px 5px 8px; 224 | } 225 | 226 | #content dfn { 227 | font-family: Lucida Grande, Verdana, Geneva, Sans-serif; 228 | color: #00620C; 229 | font-weight: bold; 230 | font-style: normal; 231 | } 232 | #content var { 233 | font-family: Lucida Grande, Verdana, Geneva, Sans-serif; 234 | color: #8F5B00; 235 | font-weight: bold; 236 | font-style: normal; 237 | } 238 | #content samp { 239 | font-family: Lucida Grande, Verdana, Geneva, Sans-serif; 240 | color: #480091; 241 | font-weight: bold; 242 | font-style: normal; 243 | } 244 | #content kbd { 245 | font-family: Lucida Grande, Verdana, Geneva, Sans-serif; 246 | color: #A70000; 247 | font-weight: bold; 248 | font-style: normal; 249 | } 250 | 251 | #content ul { 252 | list-style-image: url(images/arrow.gif); 253 | margin: 10px 0 12px 0; 254 | } 255 | 256 | #content li { 257 | margin-bottom: 9px; 258 | } 259 | 260 | #content li p { 261 | margin-left: 0; 262 | margin-right: 0; 263 | } 264 | 265 | #content .tableborder { 266 | border: 1px solid #999; 267 | } 268 | #content th { 269 | font-weight: bold; 270 | text-align: left; 271 | font-size: 12px; 272 | background-color: #666; 273 | color: #fff; 274 | padding: 4px; 275 | } 276 | 277 | #content .td { 278 | font-weight: normal; 279 | font-size: 12px; 280 | padding: 6px; 281 | background-color: #f3f3f3; 282 | } 283 | 284 | #content .tdpackage { 285 | font-weight: normal; 286 | font-size: 12px; 287 | } 288 | 289 | #content .important { 290 | background: #FBE6F2; 291 | border: 1px solid #D893A1; 292 | color: #333; 293 | margin: 10px 0 5px 0; 294 | padding: 10px; 295 | } 296 | 297 | #content .important p { 298 | margin: 6px 0 8px 0; 299 | padding: 0; 300 | } 301 | 302 | #content .important .leftpad { 303 | margin: 6px 0 8px 0; 304 | padding-left: 20px; 305 | } 306 | 307 | #content .critical { 308 | background: #FBE6F2; 309 | border: 1px solid #E68F8F; 310 | color: #333; 311 | margin: 10px 0 5px 0; 312 | padding: 10px; 313 | } 314 | 315 | #content .critical p { 316 | margin: 5px 0 6px 0; 317 | padding: 0; 318 | } 319 | 320 | 321 | #footer { 322 | background-color: transparent; 323 | font-size: 10px; 324 | padding: 16px 0 15px 0; 325 | margin: 20px 0 0 0; 326 | text-align: center; 327 | } 328 | 329 | #footer p { 330 | font-size: 10px; 331 | color: #999; 332 | text-align: center; 333 | } 334 | #footer address { 335 | font-style: normal; 336 | } 337 | 338 | .center { 339 | text-align: center; 340 | } 341 | 342 | img { 343 | padding:0; 344 | border: 0; 345 | margin: 0; 346 | } 347 | 348 | .nopad { 349 | padding:0; 350 | border: 0; 351 | margin: 0; 352 | } 353 | 354 | 355 | form { 356 | margin: 0; 357 | padding: 0; 358 | } 359 | 360 | .input { 361 | font-family: Lucida Grande, Verdana, Geneva, Sans-serif; 362 | font-size: 11px; 363 | color: #333; 364 | border: 1px solid #B3B4BD; 365 | width: 100%; 366 | font-size: 11px; 367 | height: 1.5em; 368 | padding: 0; 369 | margin: 0; 370 | } 371 | 372 | .textarea { 373 | font-family: Lucida Grande, Verdana, Geneva, Sans-serif; 374 | font-size: 14px; 375 | color: #143270; 376 | background-color: #f9f9f9; 377 | border: 1px solid #B3B4BD; 378 | width: 100%; 379 | padding: 6px; 380 | margin: 0; 381 | } 382 | 383 | .select { 384 | background-color: #fff; 385 | font-size: 11px; 386 | font-weight: normal; 387 | color: #333; 388 | padding: 0; 389 | margin: 0 0 3px 0; 390 | } 391 | 392 | .checkbox { 393 | background-color: transparent; 394 | padding: 0; 395 | border: 0; 396 | } 397 | 398 | .submit { 399 | background-color: #000; 400 | color: #fff; 401 | font-weight: normal; 402 | font-size: 10px; 403 | border: 1px solid #fff; 404 | margin: 0; 405 | padding: 1px 5px 2px 5px; 406 | } -------------------------------------------------------------------------------- /peeker_listserv_methods.php: -------------------------------------------------------------------------------- 1 | that = $that; 34 | } 35 | 36 | //---------- access ----------// 37 | 38 | /** 39 | * set the email address for this list resender 40 | * 41 | */ 42 | public function set_list_address($add) 43 | { 44 | $this->list_address = $add; 45 | return TRUE; 46 | } 47 | 48 | /** 49 | * return the list address 50 | * 51 | */ 52 | public function get_list_address() 53 | { 54 | return $this->list_address; 55 | } 56 | 57 | /** 58 | * set the array 59 | * 60 | */ 61 | public function set_resend_to($arr) 62 | { 63 | $this->resend_to_array = $arr; 64 | return TRUE; 65 | } 66 | 67 | /** 68 | * return the array of to addresses 69 | * this class will resend to 70 | * 71 | */ 72 | public function get_resend_to() 73 | { 74 | return $this->resend_to_array; 75 | } 76 | 77 | /** 78 | * set the array 79 | * 80 | */ 81 | public function set_resend_cc($arr) 82 | { 83 | $this->resend_cc_array = $arr; 84 | return TRUE; 85 | } 86 | 87 | /** 88 | * set the cc array to the same as 89 | * the one in the message object 90 | * 91 | */ 92 | public function carryover_cc() 93 | { 94 | //p($this->cc); 95 | $cc_array = $this->that->get_cc_array(); 96 | $this->set_resend_cc($cc_array); 97 | $this->log_state('CC carryover: '. var_export($cc_array,TRUE)); 98 | 99 | return TRUE; 100 | } 101 | 102 | /** 103 | * return the array of cc addresses 104 | * this class will resend to 105 | * 106 | */ 107 | public function get_resend_cc() 108 | { 109 | return $this->resend_cc_array; 110 | } 111 | 112 | /** 113 | * append an address to the resent_to_array 114 | * 115 | */ 116 | public function append_to_resend_to($address) 117 | { 118 | $this->resend_to_array[] = $address; 119 | } 120 | 121 | 122 | /* 123 | * remove an address from the resend_to_array 124 | * 125 | */ 126 | public function remove_from_resend_to($address) 127 | { 128 | $stripped_resend_to_array = array(); 129 | $rsta = $this->get_resend_to(); 130 | foreach($rsta as $email) 131 | { 132 | if ($address != $email) 133 | { 134 | $stripped_resend_to_array[] = $email; 135 | } 136 | else 137 | { 138 | $this->log_state('Resender class. Stripped address from resend_to_array: '.$email); 139 | } 140 | } 141 | $this->set_resend_to($stripped_resend_to_array); 142 | } 143 | 144 | /** 145 | * set the array 146 | * 147 | */ 148 | public function set_approved($arr) 149 | { 150 | $this->approved_array = $arr; 151 | return TRUE; 152 | } 153 | 154 | /** 155 | * return the array of to addresses 156 | * this class will resend to 157 | * 158 | */ 159 | public function get_approved() 160 | { 161 | return $this->approved_array; 162 | } 163 | 164 | /** 165 | * append an address to the approved_array 166 | * 167 | */ 168 | public function append_to_approved($address) 169 | { 170 | $this->approved_array[] = $address; 171 | } 172 | 173 | 174 | //---------- detectors ---------// 175 | /** 176 | * return TRUE on approved sender 177 | * test just address, not personal 178 | * also just checks first address 179 | * NB. has a cache (checks is_null()) 180 | * that avoids the in_array() check 181 | * and the log_state() call 182 | */ 183 | public function approved_sender() 184 | { 185 | //pe($this->that); 186 | if (is_null($this->approved_sender)) 187 | { 188 | $add = strtolower($this->get_address_from()); 189 | $approved_arr = $this->get_approved(); 190 | foreach($approved_arr as $check_add) 191 | { 192 | if ($add == strtolower($check_add)) 193 | { 194 | $this->approved_sender = TRUE; 195 | break; 196 | }; 197 | } 198 | $this->that->log_state('Sender '. $add .' approved? : ' . var_export($this->approved_sender,TRUE)); 199 | } 200 | return $this->approved_sender; 201 | } 202 | 203 | 204 | public function not_approved_sender_and_is_bounce_message() 205 | { 206 | return (! $this->approved_sender() && $this->is_bounce_message()); 207 | } 208 | 209 | /** 210 | * test various header params against bounced message 211 | * patterns to figure out if this is a bounce 212 | * relies on preg_match_subject() detector in header 213 | * class 214 | * This is not complete, needs lots of work to be a full 215 | * bounce detector 216 | * 217 | */ 218 | public function is_bounce_message() 219 | { 220 | // check Return-Path header line 221 | // for "no return path" indicator <> 222 | // but, Return-Path is changed 223 | // if message is Redirected... hmmmn 224 | // also should check the Delivery-Status part 225 | // if there is one to determine if it is a bounce 226 | $rp = $this->preg_match_header_array_key(array('Return-Path','<>')); 227 | //pe($this->header_string); 228 | // order subject matches in order of likelihood 229 | $subject_patterns = array('/Returned mail:/i'); 230 | foreach ($subject_patterns as $p) 231 | { 232 | // return on the first match 233 | if ($this->preg_match_subject($p)) 234 | { 235 | return TRUE; 236 | } 237 | } 238 | return FALSE; 239 | } 240 | 241 | 242 | /** 243 | * determine if the text to be appended to the 244 | * resent email has already been appended 245 | * to PLAIN or HTML properties 246 | */ 247 | 248 | public function already_appended($pattern) 249 | { 250 | return (bool)($this->that->preg_match_PLAIN($pattern) OR $this->that->preg_match_HTML($pattern)); 251 | } 252 | 253 | //---------- callbacks ---------// 254 | /** 255 | * resend the email to the array of addresses 256 | * just grabs the raw email from string to use 257 | * but if a rewritten version of the HTML or PLAIN is 258 | * available, then use that 259 | * should make this have accessor fns and set bits to say 260 | * also, should follow the order of the email precedence 261 | * meaning, usually HTML supersedes PLAIN, but it might 262 | * be different, and this would override that order 263 | * 264 | */ 265 | public function resend_email() 266 | { 267 | // figure out how to compose the body 268 | if ($this->that->HTML!='') 269 | { 270 | $body = (isset($this->that->HTML_rewritten)) ? $this->that->HTML_rewritten : $this->that->HTML; 271 | } 272 | else 273 | { 274 | $body = (isset($this->that->PLAIN_rewritten)) ? $this->that->PLAIN_rewritten : $this->that->PLAIN; 275 | } 276 | 277 | $is_not_html = $this->that->has_PLAIN_not_HTML(); 278 | 279 | // get the attachment filenames 280 | $file_array = $this->that->get_file_name_array(); 281 | 282 | // this function is in the email_send_helper 283 | // and requires a modified email library 284 | // settings for using google as the smtp machine are 285 | // embedded in the helper 286 | // you can replace this with a simpler email sender 287 | send_email_by_google($this->get_list_address(), 288 | $this->get_resend_to(), 289 | $this->get_resend_cc(), 290 | $this->that->get_address_from(), 291 | $this->that->get_personal_from(), 292 | $this->that->get_subject(), 293 | $body, $is_not_html, $file_array); 294 | } 295 | 296 | /* 297 | * if an address in the CC field 298 | * is also in the recipient list for this 299 | * resend_to_array then strip it 300 | * so the list member doesn't get 2 copies 301 | * of the message - relies on the CC 302 | * for the message to get through 303 | * 304 | */ 305 | public function strip_resend_to_email_if_also_in_cc() 306 | { 307 | // NOTE: only deals with first CC! 308 | $add = $this->that->get_address_cc(); 309 | $this->remove_from_resend_to($add); 310 | } 311 | 312 | // wrapper 313 | public function get_address_from() 314 | { 315 | $arr = $this->that->get_from_array('mailbox@host'); 316 | return $arr[0]; 317 | } 318 | 319 | // wrapper 320 | public function get_personal_from() 321 | { 322 | $arr = $this->that->get_from_array('personal'); 323 | return $arr[0]; 324 | } 325 | 326 | /** 327 | * specialized function to act on string 328 | * in subject and add a recipient if the 329 | * string is there 330 | * this should be run before the resend_ 331 | * method itself so that both the subject 332 | * and the recipient list are complete 333 | * before the email gets sent out 334 | * 335 | */ 336 | public function strip_subject_add_recipient($arr) 337 | { 338 | $this->subject = str_replace($arr['strip'], '',$this->get_subject()); 339 | //p($arr['to']);pe($this->subject); 340 | // should make sure it is a 'valid' email address 341 | $this->append_to_resend_to($arr['resend_to']); 342 | //p($this->get_resend_to_array());pe($this->PLAIN); 343 | } 344 | 345 | 346 | 347 | } 348 | 349 | //EOF -------------------------------------------------------------------------------- /peeker_body.php: -------------------------------------------------------------------------------- 1 | peek_parent->resource property (resource) 16 | * 17 | * 18 | */ 19 | 20 | include_once('peeker_header.php'); 21 | 22 | class peeker_body extends peeker_header{ 23 | 24 | // define the class that body will use for parts 25 | public $parts_class = 'peeker_parts'; 26 | 27 | // the whole raw body string 28 | public $body_string; 29 | 30 | // store the plain and/or html body 31 | // if not a multipart message 32 | // if this class is extended 33 | // by the parts class, these 34 | // properties could have been filled by 35 | // data from a multipart message 36 | // should figure better way for this 37 | public $PLAIN=''; 38 | public $HTML=''; 39 | 40 | // the UNIX timestamp when the message 41 | // came into this class from mail server 42 | public $timestamp_pulled; 43 | 44 | /** 45 | * Constructor, connect to parent class 46 | * 47 | */ 48 | public function __construct(&$peek_parent, $imap_h_obj) 49 | { 50 | // pass the resource on to the header class 51 | parent::__construct($peek_parent, $imap_h_obj); 52 | $this->log_state('LOADING body class'); 53 | } 54 | 55 | 56 | /** 57 | * get all the email parts in the body 58 | * this causes gmail POP server to archive or delete 59 | * (if account is set to do archive or delete on POP access) 60 | * 61 | */ 62 | public function get_body() 63 | { 64 | // headers are retrieved first so body() is decoupled 65 | // and messages() in peek class might have deleted this message 66 | // but it is still in the object tree, check if it has been marked 67 | if ($this->get_mark_delete()) 68 | { 69 | return FALSE; 70 | } 71 | else 72 | { 73 | // NOTE: calling this function removes message from 74 | // gmail's POP3 INBOX - not by deleting it, but making 75 | // it effectively invisible (depending on gmail account's POP3 settings) 76 | $this->log_state('Fetching structure for email #'.$this->Msgno); 77 | $structure = @imap_fetchstructure($this->peek_parent->resource, $this->Msgno); 78 | 79 | // make sure $structure is not null - can happen if passed MsgNo 0 80 | // log state 81 | // TODO: build error handling, exception 82 | if ($structure===NULL) $this->log_state('get_body() method $structure is NULL. MsgNo is: '.(int)$this->MsgNo); 83 | 84 | // check for mail server errors here to clear the 85 | // error stack and prevent it from posting 86 | // PHP errors about badly formatted emails 87 | // should probably store the errors with the email in a db 88 | $this->_check_imap_errors('imap_fetchstructure'); 89 | 90 | // pull out the raw email body here for 91 | // storage/export potential eg allowing mbox export 92 | $this->log_state('Getting body for email #'.$this->Msgno); 93 | $this->body_string = @imap_body($this->peek_parent->resource,$this->Msgno); 94 | $this->_check_imap_errors('imap_body'); 95 | 96 | // see if it is a multipart messsage 97 | // fill $this->parts_array with the parts 98 | // could handle both these cases in the extract_parts function 99 | if (isset($structure->parts) && count($structure->parts)) 100 | { 101 | // extract every part of the email into the parts_array var 102 | // this is a custom array with objects to help unify what we need from the parts 103 | // extract this part of email, stores the data in properties 104 | // recurses if necessary to get all the parts into the array 105 | // this is a little weird here since the method is in a sub class 106 | // and you have to make sure the class is loaded before extracting 107 | if (class_exists($this->parts_class)) 108 | { 109 | $this->extract_parts($structure->parts); 110 | } 111 | else 112 | { 113 | $this->log_state( 114 | 'No parts class defined in body. imap_fetchstructure() parsing ( method extract_parts() ) failed.'); 115 | } 116 | } 117 | else 118 | { 119 | // not a multipart message 120 | // get the body of message 121 | // decode if quoted-printable or base64 122 | if ($structure->encoding==3) 123 | { 124 | $body=base64_decode($this->body_string); 125 | } 126 | elseif ($structure->encoding==4) 127 | { 128 | $body=quoted_printable_decode($this->body_string); 129 | } 130 | else 131 | { 132 | $body = $this->body_string; 133 | } 134 | // if this is a PLAIN or HTML part it will 135 | // be written to the respective property 136 | // create a var for $this->PLAIN or $this->HTML 137 | $sub_type = strtoupper($structure->subtype); 138 | if ($sub_type === 'PLAIN') 139 | { 140 | $this->PLAIN = $body; 141 | // see comment below in the HTML part 142 | // uncomment this to convert all PLAIN parts to utf8 143 | if (0) $this->PLAIN = $this->peek_parent->decode_mime($this->PLAIN); 144 | } 145 | 146 | if ($sub_type === 'HTML') 147 | { 148 | $this->HTML = $body; 149 | // DEC 20101210 turn off this line until needed 150 | // deals with encoded HTML iso-8859-1 that needs 151 | // to get inserted as UTF-8 into db but insert fails 152 | // this should fix it, insert only inserts HTML 153 | // up to encoded char and then silently drops the rest 154 | // uncomment this to convert all HTML parts to utf8 155 | if (0) $this->HTML = $this->peek_parent->decode_mime($this->HTML); 156 | } 157 | } 158 | // parts_array filled by peek_mail_parts class 159 | 160 | // represent internal date as UNIX timestamp 161 | // this is actually the timestamp of 162 | // when the message was put into this class 163 | // rather than "received" (which should better 164 | // be the datestamp for when the message 165 | // was accepted to the receiving SMTP server) 166 | $this->timestamp_pulled = time(); 167 | 168 | // return TRUE to allow this to function as a 169 | // kind of default detector if needed 170 | return TRUE; 171 | } 172 | 173 | } 174 | 175 | 176 | /** 177 | * get the body part (raw text undecoded) 178 | * 179 | */ 180 | public function get_body_string() 181 | { 182 | return $this->body_string; 183 | } 184 | 185 | /** 186 | * get the PLAIN part (text-only) 187 | * 188 | */ 189 | public function get_plain() 190 | { 191 | return $this->PLAIN; 192 | } 193 | 194 | /** 195 | * get the HTML part 196 | * or if there is a rewritten part, send that 197 | * 198 | */ 199 | public function get_html() 200 | { 201 | return $this->HTML; 202 | } 203 | 204 | /** 205 | * get the HTML part 206 | * or if there is a rewritten part, send that 207 | * 208 | */ 209 | public function get_html_filtered() 210 | { 211 | $html = (isset($this->HTML_rewritten))?$this->HTML_rewritten:$this->HTML; 212 | return $html; 213 | } 214 | 215 | /** 216 | * get the date pulled timestamp 217 | * 218 | */ 219 | public function get_timestamp_pulled() 220 | { 221 | return $this->timestamp_pulled; 222 | } 223 | 224 | /** 225 | * get the date pulled stamp 226 | * converts internal timestamp 227 | * to Y-m-d H:i:s mysql datetime string 228 | * 229 | */ 230 | public function get_date_pulled() 231 | { 232 | return date('Y-m-d H:i:s', $this->timestamp_pulled); 233 | } 234 | 235 | // ------- detectors - return boolean ------- // 236 | 237 | /** 238 | * true if pattern matches the PLAIN part (text-only) 239 | * 240 | * 241 | */ 242 | public function preg_match_PLAIN($pattern) 243 | { 244 | return (bool)preg_match($pattern,$this->PLAIN); 245 | } 246 | 247 | /** 248 | * true if pattern matches the HTML part 249 | * 250 | * 251 | */ 252 | public function preg_match_HTML($pattern) 253 | { 254 | return (bool)preg_match($pattern,$this->HTML); 255 | } 256 | 257 | /** 258 | * true if PLAIN part but not HTML 259 | * 260 | * 261 | */ 262 | public function has_PLAIN_not_HTML() 263 | { 264 | return $this->PLAIN != '' && $this->HTML == ''; 265 | } 266 | 267 | 268 | /** 269 | * true if string is in from address 270 | * and other conditions regarding 271 | * PLAIN and HTML message parts fit 272 | * test if we need to fix stupid people's email that they try to send as HTML 273 | * but without the proper MIME types and boundaries specified 274 | * bascially, a brute force check on the text to see if it "starts" 275 | * with the tag (which is how a rudimentary html doc can start) 276 | * and also make sure that we don't overwrite an existing HTML property 277 | * if it exists 278 | * NOTE: some HTML comes in through the body 279 | * with =3D style MIME encoded equals chars, etc... 280 | * need to look into fixing those too 281 | * 282 | */ 283 | public function fix_MIME_from_sender($from_str) 284 | { 285 | // use a detector that is in the parent class 286 | if ($this->in_from($from_str)) 287 | { 288 | if ( $this->PLAIN !== '' ) 289 | { 290 | if (strpos($this->PLAIN,'')<25) 291 | { 292 | if ($this->HTML ==='' ) 293 | { 294 | return TRUE; 295 | } 296 | } 297 | } 298 | } 299 | return FALSE; 300 | } 301 | 302 | //------- callbacks -------// 303 | 304 | /** 305 | * takes the PLAIN property 306 | * and stuffs the data into the 307 | * HTML property - 308 | * can be used to fix badly written 309 | * emails and also force all emails 310 | * to be HTML 311 | */ 312 | public function put_PLAIN_into_HTML() 313 | { 314 | // see loreal email for example 315 | // this should be handled in a 316 | // better way, perhaps known defects in email 317 | // could be tracked in a table and fixes 318 | // applied before the data is stored. 319 | // also force quoted printable decoding 320 | // NOTE: shouldn't have to do this decoding... 321 | // is it vestigal behavior? 322 | // in case the message is encoded 323 | $this->HTML = quoted_printable_decode($this->PLAIN); 324 | } 325 | 326 | /** 327 | * wrap html and body tags 328 | * around HTML part 329 | * this lets other functions 330 | * deal with tagged html 331 | * 332 | */ 333 | public function wrap_HTML_with_HTML_tags() 334 | { 335 | $this->HTML = ''.$this->HTML.''; 336 | } 337 | } 338 | // EOF 339 | -------------------------------------------------------------------------------- /peeker_connect.php: -------------------------------------------------------------------------------- 1 | initialize($init_array); 43 | } 44 | 45 | /** 46 | * Set the server, login, pass, service_flags, and mailbox 47 | * 48 | */ 49 | public function initialize($init_array) 50 | { 51 | // connect to the specified account 52 | // these array items need to be the 53 | // same names as the config items OR 54 | // the db table fields that store the account info 55 | $this->host = $init_array['host']; 56 | $this->port = $init_array['port']; 57 | // get the port and server combined 58 | $this->server = $this->host .':'. $this->port; 59 | 60 | $this->login = $init_array['login']; 61 | $this->pass = $init_array['pass']; 62 | 63 | $this->service_flags = $init_array['service_flags']; 64 | // default to INBOX mailbox since POP3 doesn't require it 65 | // and IMAP always has an INBOX 66 | $this->mailbox = (isset($init_array['mailbox'])) ? $init_array['mailbox'] : 'INBOX'; 67 | 68 | // grab the resource returned by imap_open() 69 | // concatenate the IMAP connect spec string 70 | // expects server, flags, and mailbox to be set already 71 | $this->server_spec_string = $this->_generate_server_spec_string(); 72 | // suppress warning with @ so we can handle it internally 73 | // which is the way that imap_errors() works 74 | $this->resource = @imap_open($this->server_spec_string, $this->login, $this->pass); 75 | 76 | // check for errors in the connection 77 | // calling imap_errors() clears all errors in the stack 78 | $err = imap_errors(); 79 | 80 | // clear the message count in case this is a re-initialization 81 | $this->message_count = NULL; 82 | 83 | if($this->resource) 84 | { 85 | $this->log_state('Connected to: '.$this->server_spec_string); 86 | // when connection is good but the mailbox is empty, 87 | // the php imap c-libs report POP server empty mailbox as 88 | // "Mailbox is empty" (as a PHP error in the imap_errors() stack) 89 | // in case we are using IMAP, we also check get_message_count() 90 | if($err[0] === 'Mailbox is empty' OR $this->get_message_count() === 0) 91 | { 92 | // we now know there are zero messages 93 | $this->message_waiting = FALSE; 94 | $this->log_state('Mailbox is empty.'); 95 | } 96 | else 97 | { 98 | // there is at least one message 99 | $this->message_waiting = TRUE; 100 | $this->log_state('At least one message available.'); 101 | } 102 | 103 | $this->connected = TRUE; 104 | } 105 | else 106 | { 107 | // determine the specific reason for rejection/no connection 108 | $this->_handle_rejection($err); 109 | $this->log_state('Not connected. No email resource at: '. $this->server_spec_string); 110 | $this->connected = FALSE; 111 | } 112 | 113 | } 114 | 115 | /** 116 | * Concatenate the IMAP connect spec string 117 | * Expects server, flags, and mailbox to be set already 118 | * 119 | */ 120 | private function _generate_server_spec_string() 121 | { 122 | return '{'. $this->server . $this->service_flags.'}'.$this->mailbox; 123 | } 124 | 125 | /** 126 | * Get count of messages returned by latest 127 | * call to the imap_num_msg() function that 128 | * was stored in the property 129 | * it does not call imap_num_msg() 130 | * method unless needed and available 131 | * use $force_recount = TRUE to recount 132 | * 133 | * Call this with $force_recount = TRUE to get most 134 | * current message_count and store it in the 135 | * message_count property 136 | * 137 | * This is necessary to deal with different 138 | * ways that each mailserver reports the 139 | * presence of messages waiting 140 | * (i.e., some don't so you have to count them) 141 | */ 142 | public function get_message_count($force_recount=FALSE) 143 | { 144 | if (is_null($this->message_count) OR $force_recount) 145 | { 146 | if(is_resource($this->resource)) 147 | { 148 | $this->message_count = imap_num_msg($this->resource); 149 | } 150 | else 151 | { 152 | $this->log_state('Not connected. Cannot get message count.'); 153 | } 154 | } 155 | return $this->message_count; 156 | } 157 | 158 | /** 159 | * 160 | * Get an array of mailboxes - IMAP 161 | * returns array of server specification 162 | * strings for the mailboxes 163 | */ 164 | public function get_mailboxes() 165 | { 166 | // star pattern says get all mailboxes 167 | // imap_list() gets array of full mailbox names 168 | // strip the mailbox off of the server_spec_string 169 | $sss_no_mb = '{'.$this->server . $this->service_flags.'}'; 170 | $this->mailboxes = imap_list($this->resource,$sss_no_mb,'*'); 171 | return $this->mailboxes; 172 | } 173 | 174 | /** 175 | * Reopens the IMAP connection pointing at a different mailbox 176 | * Accepts name or full server spec string 177 | * (like the one that is returned by imap_list()) 178 | * 179 | */ 180 | public function change_to_mailbox($mailbox_name_or_full_server_spec_string) 181 | { 182 | // should check if it is a valid mailbox 183 | // but for now, just see if it starts with curly bracket 184 | if (strncmp('{',$mailbox_name_or_full_server_spec_string,1)===0) 185 | { 186 | // it is (probably) a spec string 187 | // but, maybe mailbox names can start with {? 188 | // set the local mailbox property by splitting on curly brackets 189 | // use strtok twice to get the end of the string because it is surper fast 190 | strtok($mailbox_name_or_full_server_spec_string,'}'); 191 | $this->mailbox = strtok($mailbox_name_or_full_server_spec_string); 192 | // reopen the connection using the new server spec string 193 | $bool = imap_reopen($this->resource, $mailbox_name_or_full_server_spec_string); 194 | } 195 | else 196 | { 197 | $this->mailbox = $mailbox_name_or_full_server_spec_string; 198 | $new_sss_with_mb = $this->_generate_server_spec_string(); 199 | $bool = imap_reopen($this->resource, $new_sss_with_mb); 200 | } 201 | } 202 | 203 | 204 | /** 205 | * Handle the various reasons for rejection 206 | * bad username, password, host, POP not enabled 207 | * NOTE: this is pretty brittle because it does a lot 208 | * of string matching and those could easily change 209 | * on new imap lib or PHP version or mail server change 210 | * It is also far from complete. 211 | * NOTE: 212 | * the underlying pop lib allows 3 login attempts, in PHP >= 5.2.0 it can be changed 213 | * but, that will change some of the rejection handler code. 214 | */ 215 | private function _handle_rejection($err) 216 | { 217 | $reject_reason = 'Unknown reason for rejection.'; 218 | if (isset($err[0])) 219 | { 220 | // handle the server responses, wrong hostname 221 | if ($err[0] === 'No such host as ' . $this->host) 222 | { 223 | $reject_reason = $err[0]; 224 | } 225 | // is it a username or a password wrong? 226 | elseif ($err[0] === '[AUTH] Username and password not accepted.') 227 | { 228 | $reject_reason = 'Username seems OK. Password not accepted.'; 229 | } 230 | elseif ($err[0] === 'Password failed for ' . $this->login) 231 | { 232 | $reject_reason = 'Username and/or Password not accepted.'; 233 | } 234 | elseif ($err[0] === '[SYS/PERM] Your account is not enabled for POP access. Please visit your Gmail settings page and enable your account for POP access.') 235 | { 236 | $reject_reason = 'Username and Password OK. No POP access on this account: '. $this->login; 237 | } 238 | elseif ($err[0] === "Can't open mailbox ". $this->server_spec_string . ": invalid remote specification") 239 | { 240 | $reject_reason = "Can't open mailbox ". $this->server_spec_string; 241 | } 242 | elseif (strpos('Certificate failure', $err[0]) !== FALSE) 243 | { 244 | $reject_reason = 'Using Gmail or other SSL login? Try adding novalidate-cert to the service flags.'; 245 | } 246 | elseif ($err[0] === "login allowed only every 15 minutes") 247 | { 248 | $reject_reason = 'Using Live.com or Hotmail? One POP login each 15 mins. Login after: ' . date('H:i:s',time()+(15*60)); 249 | } 250 | else // if we don't have a slot for it, just stuff the error in the log 251 | { 252 | $reject_reason = $err[0]; 253 | } 254 | 255 | } 256 | // special case where gmail doesn't recognize the username 257 | if (isset($err[1])) 258 | { 259 | if ($err[1] === 'POP3 connection broken in response') 260 | { 261 | $reject_reason = 'Username not accepted: '. $this->login; 262 | } 263 | } 264 | 265 | $this->log_state($reject_reason); 266 | } 267 | 268 | 269 | /** 270 | * Log the states the connection has gone through 271 | * 272 | */ 273 | public function log_state($str) 274 | { 275 | $this->state_array[] = $str; 276 | } 277 | 278 | 279 | /** 280 | * Get the array of states the connection has gone through 281 | * 282 | */ 283 | public function trace() 284 | { 285 | return $this->state_array; 286 | } 287 | 288 | /** 289 | * Get the login name 290 | * 291 | */ 292 | public function get_login() 293 | { 294 | return $this->login; 295 | } 296 | 297 | 298 | /** 299 | * See if the class connected to the server 300 | * 301 | */ 302 | public function is_connected() 303 | { 304 | return $this->connected; 305 | } 306 | 307 | 308 | /** 309 | * See if there are messages waiting 310 | * 311 | */ 312 | public function message_waiting() 313 | { 314 | return $this->message_waiting; 315 | } 316 | 317 | 318 | /** 319 | * Wrapper to close the IMAP connection 320 | * Returns TRUE if closed with no errors 321 | * FALSE if imap_close() fails or if 322 | * there is no resource 323 | * 324 | */ 325 | public function close() 326 | { 327 | if (is_resource($this->resource)) 328 | { 329 | $closed = imap_close($this->resource); 330 | if ($closed) 331 | { 332 | $this->log_state('Connection closed OK.'); 333 | } 334 | else 335 | { 336 | $this->log_state('Mail resource OK, but connection could not be closed.'); 337 | } 338 | } 339 | else 340 | { 341 | $closed = FALSE; 342 | $this->log_state('Connection could not be closed. No POP resource.'); 343 | } 344 | $this->connected = FALSE; 345 | return $closed; 346 | } 347 | 348 | } 349 | // EOF -------------------------------------------------------------------------------- /docs/peeker_quickstart.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Peeker Quickstart 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 |

Peeker

28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 40 | 41 | 42 |
43 | 44 | 45 |
46 | 47 | 48 | 49 |
50 | 51 | 52 |

Peeker Quickstart

53 | 54 |

IMAP examples

55 |

First, make sure your PHP install has the imap extension (run phpinfo() and search for imap).

56 |

Note: this Quick Start assumes you are using Peeker as a CodeIgniter library. If you are using Codeigniter, just place all the Peeker files in the application/libraries directory and load the library using CI's $this->load->library('peeker') method.

57 |

However, Peeker can work as a standalone set of classes. Just use the PHP "include" command like this: include('peeker.php'); and modify the syntax for the examples below to use regular PHP Object Oriented coding.

58 |

So, instead of $this->load->library('peeker', $config);
59 | // call a method on the CI peeker object
60 | $this->peeker->somefunction();
just do 61 | $peeker = new peeker($config);
62 | // call method on the regular PHP peeker object
63 | $peeker->somefunction(); 64 |

65 |

First, create a new controller function with this code (change login info to match your IMAP login).

66 | 67 | 68 | // this shows basic IMAP, no TLS required
69 | $config['login']='IMAP_username';
70 | $config['pass']='IMAP_password';
71 | $config['host']='imap.domain.com';
72 | $config['port']='143';
73 | $config['service_flags'] = '/imap/notls'; 74 |
75 |
76 | $this->load->library('peeker', $config);
77 |
78 | if ($this->peeker->message_waiting())
79 | {
80 | echo 'Message count:' . $this->peeker->get_message_count();
81 | }
82 | else
83 | {
84 | echo 'No messages waiting.';
85 | }
86 |
87 | $this->peeker->close();
88 |
89 | // tell the story of the connection
90 | print_r($this->peeker->trace());
91 |
92 | 93 |

If you have unread email in the account, you should see a message with the number of unread emails waiting at the server.

94 | 95 |

You can connect to any POP or IMAP server. This next config shows how to connect to the gmail.com IMAP server. This example requires that PHP be compiled with SSL. You can verify this with phpinfo().

96 | 97 |

Important:  You must enable IMAP on your gmail account. Go to your gmail account and click 'Settings'. Then click 'Forwarding and POP/IMAP'. Scroll down to 'IMAP Access' and check 'Enable IMAP' and click Save Changes

98 | 99 | 100 | // gmail IMAP
101 | $config['login']='your_username@gmail.com';
102 | $config['pass']='your_password';
103 | $config['host']='imap.gmail.com';
104 | $config['port']='993';
105 | $config['service_flags'] = '/imap/ssl/novalidate-cert'; 106 |
107 | 108 | 109 |

POP Examples

110 | 111 |

Basic POP

112 | 113 | // Basic POP
114 | $config['login']='POP_username';
115 | $config['pass']='POP_password';
116 | $config['host']='mail.domain.com';
117 | $config['port']='110';
118 | $config['service_flags'] = '/pop3/notls'; 119 |
120 | 121 |

This next example shows settings that will connect to gmail's POP server. Just like in the IMAP example, PHP must be configured with SSL for this to work.

122 | 123 |

Important:  You must enable POP on your gmail account. Go to your gmail account and click 'Settings'. Then click 'Forwarding and POP/IMAP'. Scroll down to 'POP Download' and check 'Enable POP for all mail' (if this is a new gmail account with no messages) OR 'Enable POP for mail that arrives from now on' (if this is an existing gmail account with lots of messages) and click Save Changes

124 | 125 |

gmail POP

126 | 127 | // gmail POP
128 | $config['login']='your_username@gmail.com';
129 | $config['pass']='your_password';
130 | $config['host']='pop.gmail.com';
131 | $config['port']='995';
132 | $config['service_flags'] = '/pop3/ssl/novalidate-cert'; 133 |
134 | 135 |

Some Peeker Functions

136 | 137 |

This is a selection of Peeker Class functions - it is not complete, but should get you started exploring.

138 | 139 |

$this->peeker->get_message(message_number)

140 |

Get a message in the queue. Returns a message object. The example below uses 1 as the parameter so the code gets the first message waiting. NOTE: Message number index starts at 1. Sending 0 to this method will result in a stream of notices and print a message in the log trace.

141 | $e = $this->peeker->get_message(1); 142 | 143 |

$this->peeker->delete_and_expunge(message_number)

144 |

Delete and Expunge the first message in the queue.

145 | $this->peeker->delete_and_expunge(1); 146 | 147 |

$this->peeker->close([TRUE])

148 |

Close the connection and (optionally) Expunge any messages that have been marked to delete. (see $e->set_delete() below)

149 | // sending TRUE as first parameter
150 | // tells the server to expunge on close
151 | $this->peeker->close(TRUE);
152 | 153 |

Some Message Object Functions

154 | 155 |

This is a selection of Peeker Message Object functions (available in the message object) - it is not a complete listing, but it shows enough to let you get a taste of what's possible. A complete listing is available in the Peeker Header, the Peeker Body, the Peeker Parts, or the Peeker File documentation.

156 | 157 |

Important:  Call the following functions using the message object returned by $this->peeker->get_message(). In these examples it is called $e.

158 | 159 |

$e->get_subject()

160 |

Get the subject of the message.

161 | 162 | // get the first message in the queue
163 | $e = $this->peeker->get_message(1);
164 | echo $e->get_subject();
165 |
166 | 167 |

$e->get_date()

168 |

Get the date of the message.

169 | 170 | // get the first message in the queue
171 | $e = $this->peeker->get_message(1);
172 | echo $e->get_date();
173 |
174 | 175 |

$e->get_from()

176 |

Get the from string of the message.

177 | 178 | // get the first message in the queue
179 | $e = $this->peeker->get_message(1);
180 | echo $e->get_from();
181 |
182 | 183 |

$e->get_to()

184 |

Get the to string of the message.

185 | 186 | // get the first message in the queue
187 | $e = $this->peeker->get_message(1);
188 | echo $e->get_to();
189 |
190 | 191 |

$e->get_size()

192 |

Get the size (in bytes) of the message.

193 | 194 | // get the first message in the queue
195 | $e = $this->peeker->get_message(1);
196 | echo $e->get_size();
197 |
198 | 199 |

$e->get_header_array()

200 |

Get the entire header (as associative array) of the message.

201 | 202 | // get the first message in the queue
203 | $e = $this->peeker->get_message(1);
204 | echo $e->get_header_array();
205 |
206 | 207 |

$e->set_delete()

208 |

Mark a message in the queue for deletion. Does not automatically delete. You must call $this->peeker->close(TRUE) and send TRUE as the first parameter.

209 | 210 | // mark the first message for deletion
211 | $e = $this->peeker->get_message(1);
212 | $e->set_delete();
213 | $this->peeker->close(TRUE);
214 |
215 | 216 |

$e->get_plain()

217 |

Get the plain part (plain text of the email body) of the message.

218 | 219 | // get the first message in the queue
220 | $e = $this->peeker->get_message(1);
221 | echo $e->get_plain();
222 |
223 | 224 |

$e->get_html()

225 |

Get the HTML part (HTML text of the email body) of the message.

226 | 227 | // get the first message in the queue
228 | $e = $this->peeker->get_message(1);
229 | echo $e->get_html();
230 |
231 | 232 |

$e->get_parts_array()

233 |

Get the parts array (all attachments, including PLAIN and HTML attachments) of the message. Each array item is a peeker_file object. The file object has information about each body part and attachment. Dump it to see the structure.

234 | 235 | // get the first message in the queue
236 | $e = $this->peeker->get_message(1);
237 | print_r($e->get_parts_array());
238 |
239 | 240 |

That is a short summary of some useful functions in Peeker. There are many, many more functions available. Look at the class documentation to learn.

241 | 242 |

This guide focused on functions; there are other capabilities in the class that were not covered at all in this quickstart: using detectors to write declarative programs, peek at headers (don't disturb read state of message) by setting message class to peeker_header, extend message object class to create your own detectors.

243 | 244 | Requirements: 245 | 250 | 251 |
252 | 253 | 254 | 255 | 262 | 263 | 264 | -------------------------------------------------------------------------------- /docs/peeker_header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Peeker Connect Class 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 |

Peeker Header

28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 40 | 41 | 42 |
43 | 44 | 45 |
46 | 47 | 48 | 49 |
50 | 51 | 52 |

Header Class

53 | 54 |

This class handles the email header data.

55 | 56 |

Important:  All of these functions are available through the message object returned by the Peeker class. Notated here as: $e

57 | 58 | 59 | 60 |

Message Object Property Access Functions

61 | 62 |

get_fingerprint()

63 |

Returns MD5 hash of several basic email message properties (default: fromaddress, toaddress, subject, date). This allows you to determine if an email is unique (like a fingerprint). The fingerprint can also be used to generate a unique directory name for storing attachments. The fingerprint is not the Message-ID.

64 | $e->get_fingerprint(); 65 | 66 |

get_date()

67 |

Returns the raw date string from the email message.

68 | $e->get_date(); 69 | 70 |

get_subject()

71 |

Return the fully-decoded subject of the email message.

72 | $e->get_subject(); 73 | 74 |

get_msgno()

75 |

Return the message number of the email. This is a temporary number given by the server to the message. The message number changes when the email queue is changed. E.g., msgno 1 is the first message in the queue.

76 | $e->get_msgno(); 77 | 78 |

get_message_id()

79 |

Returns the Message-ID header (if there is one).

80 | $e->get_message_id(); 81 | 82 |

get_to()

83 |

Returns the fully-decoded To address string.

84 | $e->get_to(); 85 | 86 |

get_from()

87 |

Returns the fully-decoded From address string.

88 | $e->get_from(); 89 | 90 |

get_reply_to()

91 |

Returns the fully-decoded Reply-To address string.

92 | $e->get_reply_to(); 93 | 94 |

get_sender()

95 |

Returns the fully-decoded Sender address string.

96 | $e->get_sender(); 97 | 98 |

get_cc()

99 |

Returns the fully-decoded CC address string.

100 | $e->get_cc(); 101 | 102 |

get_bcc()

103 |

Returns the fully-decoded BCC address string. Note: the BCC field generally only appears in outgoing messages (such as those in an IMAP Drafts folder).

104 | $e->get_bcc(); 105 | 106 |

get_return_path()

107 |

Returns the fully-decoded Return-Path address string.

108 | $e->get_return_path(); 109 | 110 |

get_to_array([$format])

111 |

Returns the fully-decoded To address array. By default, the array holds multiple addresses, each with its own personal, mailbox, and host part. This is the same address array returned by the imap_headerinfo() function except that this array is fully-decoded.

112 |

Send the optional $format argument to get a flat array. The array values will be formatted with string replacement on these three keywords: personal, mailbox, host. So if you just want the email address without the personal part (e.g., me@example.com) then send the string 'mailbox@host' as the format argument. The $format string can contain any other strings.

113 | $e->get_to_array(); // no argument, returns nested associative array. 114 | $e->get_to_array('"personal" <mailbox@host>'); // returns 'standard' email format. 115 | 116 |

get_from_array([$format])

117 |

See get_to_array() function.

118 | $e->get_from_array(); 119 | 120 |

get_reply_to_array([$format])

121 |

See get_to_array() function.

122 | $e->get_reply_to_array(); 123 | 124 |

get_sender_array([$format])

125 |

See get_to_array() function.

126 | $e->get_sender_array(); 127 | 128 |

get_cc_array([$format])

129 |

See get_to_array() function.

130 | $e->get_cc_array(); 131 | 132 |

get_bcc_array([$format])

133 |

See get_to_array() function.

134 | $e->get_bcc_array(); 135 | 136 |

get_return_path_array([$format])

137 |

See get_to_array() function.

138 | $e->get_return_path_array(); 139 | 140 |

get_size()

141 |

Returns the size in bytes of the entire message - including any attachments.

142 | $e->get_size(); 143 | 144 |

get_timestamp()

145 |

Returns the UNIX timestamp when the message was sent.

146 | $e->get_timestamp(); 147 | 148 |

get_header_string()

149 |

Returns the raw header string - undecoded.

150 | $e->get_header_string(); 151 | 152 |

get_header_array()

153 |

Returns the raw, undecoded header fields in a tidy, nested, associative array.

154 | $e->get_header_array(); 155 | 156 |

get_mark_delete()

157 |

Returns the delete mark. If the message has been marked to delete, returns TRUE

158 | $e->get_mark_delete(); 159 | 160 |

set_mark_delete($delete)

161 |

Mark the message for deletion. You must call expunge() (in the peeker class) in the same connection to actually delete the message after marking it.

162 | $e->set_mark_delete(); 163 | $e->expunge(); 164 | 165 | 166 |

Detectors - All Return TRUE or FALSE

167 | 168 |

in_from($from_str)

169 |

For string detection in the From header. If the string passed to this function is in the 'From:' header string of the message, return TRUE. Wraps the strpos function and applies it to the undecoded 'From:' header string. This function is case-insensitive.

170 | $e->strpos_from(); 171 | 172 |

in_to($to_str)

173 |

For string detection in the To header. If the string passed to this function is in the 'To:' header string of the message, return TRUE. Wraps the strpos function and applies it to the undecoded 'To:' header string.This function is case-insensitive.

174 | $e->strpos_to(); 175 | 176 |

empty_property($property)

177 |

Checks the message for data in a property. If the property name (e.g., 'cc') passed to this function is empty, return TRUE.

178 |

Here is the list of useful (and potentially empty) properties for a message object: date, subject, message_id, to, from, reply_to, sender, cc, bcc, return_path. This function covers the native properties of the message object. If you need to test whether an arbitrary email message header is empty see the preg_match_header_array_key() detector function below.

179 | $e->empty_property('cc'); 180 | 181 |

is_msgno($msgno)

182 |

TRUE if current message's msgno equals the argument $msgno.

183 | $e->is_msgno(1); // check if this is the first message 184 | 185 |

preg_match_field($arr)

186 |

TRUE if the field data matches the regex pattern. Send array with field/property name and pattern. Note: this function only handles the native properties of the message object. If you need to test whether an arbitrary email message header matches a regular expression pattern see the preg_match_header_array_key() detector function below.

187 | $arr = array('To','/spam_magnet/i');
188 | if ($e->preg_match_field($arr)) $e->set_delete();
189 | 190 |

isset_header_array_key($key)

191 |

TRUE if any email header is listed in the email message. Note: header value could be empty.

192 | $e->isset_header_array_key(); 193 | 194 |

preg_match_header_array_key($array)

195 |

TRUE if the email header value matches the regular expression. Takes an array as input. Value 1 is the Header name, Value 2 is the regex pattern.

196 | $arr = array('X-Sender','/CodeIgniter/i');
197 | if ($e->preg_match_header_array_key($arr)) $e->undelete();
198 | 199 |

ttrue($arg)

200 |

Call this when you need a function to return TRUE. Useful for declarative programming and testing.

201 | $e->ttrue(); 202 | 203 |

ffalse($arg)

204 |

Call this when you need a function to return FALSE. Useful for declarative programming and testing.

205 | $e->ffalse(); 206 | 207 |

reflect($arg)

208 |

Call this when you need a function to return the argument you sent it (boolean). Useful for declarative programming and testing.

209 | $e->reflect(); 210 | 211 |

Callback Functions

212 | 213 |

set_delete($abort=FALSE)

214 |

Mark a message for deletion. Note: you must call expunge() in the same connection (usually at the end of the connection and not inside a message acquisition loop) to actually delete the message.

215 | $e->set_delete(); 216 | 217 |

undelete()

218 |

If a message has been marked to delete during this connection it will be marked normal again.

219 | $e->undelete(); 220 | 221 |

_print($d)

222 |

Convenience function to print data to the screen.

223 | $e->_print(); 224 | 225 |

pr($data)

226 |

Print data to the screen using the _print() convenience function.

227 | $e->pr(); 228 | 229 |

print_field($fn)

230 |

Print to screen just the value in a message object property.

231 | $e->print_field('date'); 232 | 233 |

print_array($arr)

234 |

Print the array in a nice way. Uses 'pre' tags to render the array so it is readable.

235 | $e->print_array(); 236 | 237 |

prepend_subject($prepend_string)

238 |

Add a string to the beginning of the subject property. This function changes the data in the message object.

239 | if ($e->in_to('spammagnet')) $e->prepend_subject('SPAM '); 240 | 241 |

Detector Meta Functions

242 | 243 |

abort($action_array=FALSE)

244 |

Stop running any subsequent detectors. Pass an optional $action_array with a command to run on aborting detectors. The only currently supported command for the action array is 'delete' - pass it as an array (e.g., array('delete'=>TRUE) ).

245 | $e->abort(); 246 | 247 |

abort_if_previous($action_array=FALSE)

248 |

If the previous detector method returned TRUE then abort any subsequent detectors. See abort() method for information on the $action_array.

249 | $e->abort_if_previous(); 250 | 251 |

Utility Functions

252 | 253 |

message_count()

254 |

Return the count of messages for the current connection. This is a wrapper function that actually calls the peeker parent class to get the data.

255 | $e->message_count(); 256 | 257 |

log_state($str)

258 |

Write a string to the internal peeker log. This is a wrapper function that passes the string to the Peeker parent.

259 | $e->log_state(); 260 | 261 | 262 |
263 | 264 | 265 | 266 | 274 | 275 | 276 | -------------------------------------------------------------------------------- /docs/peeker.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Peeker Class 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 |

Peeker

28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 40 | 41 | 42 |
43 | 44 | 45 |
46 | 47 | 48 | 49 |
50 | 51 | 52 |

Peeker Class

53 |

Please look at the Quickstart Guide first. This page documents the entire Peeker main class and shows advanced techniques. The Quickstart Guide is a better place to start if you are using Peeker for the first time.

54 |

This is the main Peeker class. It extends the Peeker Connect class to connect to a POP or IMAP server and get emails.

55 |

Once connected, you can get header information for any email messages. This class lets you "peek" at email headers without disturbing the "read" state of the message and without downloading any of the email body or attachments.

56 |

Note: The message handling can be changed so this class will get only message headers (saves memory and time by not getting bodies and attachments). See the set_message_class() method below.

57 | 58 |

Features:

59 | 68 | 69 |

Count the new messages waiting at IMAP or POP server

70 | 71 |

Enter your mailserver connection data and run a few lines of code.

72 | 73 |

Example IMAP connection

74 | 75 |

Connect to the gMail.com IMAP server using the Peeker class (remember to enable IMAP on your gmail account):

76 | 77 | 78 | $this->load->library('peeker');
79 |
80 | $config['login']='your_username@gmail.com';
81 | $config['pass']='your_password';
82 | $config['host']='imap.gmail.com';
83 | $config['port']='993';
84 | $config['service_flags'] = '/imap/ssl/novalidate-cert';
85 |
86 | $this->peeker->initialize($config);
87 | if ($this->peeker->message_waiting())
88 | {
89 | echo 'Message count:' . $this->peeker->get_message_count();
90 | }
91 | else
92 | {
93 | echo 'No messages waiting.';
94 | }
95 |
96 | $this->peeker->close();
97 |
98 | // now tell us the story of the connection
99 | print_r($this->peeker->trace()); 100 |
101 | 102 |

Get first email and pull headers from the message object

103 | 104 |

Assuming you've already connected and initialized as above:

105 |

Note: This code shows only a few of the headers available.

106 | 107 | 108 | 109 | if ($this->peeker->message_waiting())
110 | {
111 | // get the first message
112 | $email = $this->peeker->message(1);
113 | echo $email->Msgno;
114 | echo $email->date;
115 | echo $email->subject;
116 | echo $email->toaddress;
117 | echo $email->fromaddress;
118 | }
119 | 120 |
121 | 122 | 123 |

Header fields in message object

124 | 125 |

This class brings all email header data into one convenient object. See the PHP manual pages for documentation on the two PHP IMAP class header functions: imap_headerinfo() and imap_fetch_header()

126 |

Peeker combines the output of these two header functions. This is because the raw email header always holds more header fields than those returned by imap_headerinfo(). Peeker makes it easy to gather, decode, and access all of those header fields.

127 |

The headers returned by the PHP function imap_headerinfo() are put directly into the email message object. The header fields are mapped directly to message object properties. See the PHP manual page imap_headerinfo() function for a list of headers (and Peeker Message Object properties) that are available after calling message().

128 |

Additionally, two other properties are added to the message object. These are two versions of the raw header returned by imap_fetch_header(): header_string and header_array. The first is the raw header string and the second is the raw header parsed into a multi-dimensional, associative array of header fields. They both contain the same header data.

129 | // an example of the header_array property of the message object
[header_array] => Array
130 | (
131 | 	[Delivered-To] => you@domain.com
132 | 	[Received] => Array
133 | 	(
134 | 		[0] => by 123.123.123.123 with SMTP id 8; 
135 | 		Sat, 27 Jun 2009 06:44:39 -0700 (PDT)
136 | 		[1] => by 123.123.123.123 with SMTP id 6; 
137 | 		Sat, 27 Jun 2009 06:44:38 -0700 (PDT)
138 | 		[2] => from abc.def.ghi.jkl (abc.def.ghi.jkl [1.1.1.1]) 
139 | 		by mx.google.com with ESMTP id 9; 
140 | 		Sat, 27 Jun 2009 06:44:38 -0700 (PDT)
141 | 	)
142 | 
143 | 	[Return-Path] => bounce@domain.com
144 | 	[Received-SPF] => neutral (google.com: 1.2.3.4 is neither 
145 | 	permitted nor denied by best guess record for domain of 
146 | 	me@domain.com) client-ip=1.2.3.4;
147 | 	[Authentication-Results] => mx.google.com; spf=neutral 
148 | 	(google.com: 1.2.3.4 is neither permitted nor denied by 
149 | 	best guess record for domain of me@domain.com) 
150 | 	smtp.mail=me@domain.com
151 | 	[Date] => Sat, 27 Jun 2009 09:44:36 -0400
152 | 	[From] => Full Name <me@domain.com>
153 | 	[Subject] => text file attached
154 | 	[To] => you@domain.com
155 | 	[Message-id] => mxylplyx
156 | 	[MIME-version] => 1.0
157 | 	[X-Mailer] => Apple Mail (2.935.3)
158 | 	[Content-type] => multipart/mixed; boundary="Boundary_(ID_iS)"
159 | )
160 |

The raw header string is from PHP's imap_fetch_header() function and Peeker creates the multi-dimensional array.

161 | 162 |

Also, you can always dump the message object to see what's in there. Every message has a different set of headers so it's good practice to get to know email headers.

163 | 164 |

Function Reference

165 | 166 |

get_message($start [,$end])

167 |

This is the function if you simply want the Peeker class to get entire email messages from a POP or IMAP server.

168 |

After loading the library, call get_message() to get a message. The get_message() function gets the whole message (header, body and attachments/parts). It is a wrapper function for the message() function below. However, it does not just "peek" at the messages, it "pulls" them (headers, body, and attachments) and they will be downloaded and/or marked as read. NOTE: Message number index starts at 1. Sending 0 to this method will result in a stream of notices and print a message in the log trace.

169 | 170 |

You can pass 1 or 2 parameters. 1 parameter means "get one message" indicated by the integer passed, and 2 parameters means "get a range of messages."

171 |

Returns one object (if only $start sent) or an array of message objects (if $start and $end sent). This method spawns message objects as either stdClass objects or custom class objects (see set_message_class() method below to define your own objects).

172 | // get the whole first email message
173 | $email_object = $this->peeker->get_message(1);
174 | // get the first 5 email messages - all data in the email
175 | $email_array = $this->peeker->get_message(1,5);
176 | 177 |

Technical note: The get_message() function loads up the peeker_parts class and the detector class. It then creates a single detector that runs the acquisition function that "pulls" the email.

178 | 179 |

message($start [,$end])

180 |

After loading the library, call message() to just peek at message headers. Note: message() will download and mark as read if the message class is peeker_body, peeker_parts, or an extension of either.

181 |

Just like get_message(), you can pass 1 or 2 parameters. 1 parameter means "get one message" indicated by the integer passed, and 2 parameters means "get a range of messages." NOTE: Message number index starts at 1. Sending 0 to this method will result in a stream of notices and print a message in the log trace.

182 |

Returns one object (if one parameter sent) or an array of message objects (if 2 parameters sent). This method spawns either stdClass objects or custom class objects (see set_message_class() method below to define your own objects).

183 | // get just the headers of the first email message
184 | $email_object = $this->peeker->message(1);
185 | // get just the headers of the first 5 email messages
186 | $email_array = $this->peeker->message(1,5);
187 | 188 |

set_search($search_string)

189 |

Set an IMAP search string. See the PHP Manual Page for imap_search() to see the types of searches you can do.

190 | // set the IMAP search terms
191 | $this->peeker->set_search('TO "name@email.com"');
192 | 193 |

search_and_count_messages()

194 |

Sends the search to the IMAP server. Returns the number of messages found.

195 | // do the IMAP search, return count of messages found.
196 | $this->peeker->search_and_count_messages();
197 | 198 |

get_ids_from_search([$index])

199 |

Get the list of Msgnos found by the search (note: these are not message IDs). Returns an array of Msgnos of messages found by the search.

200 |

Loop over the array and get each message by calling the message() method with the Msgno as the parameter.

201 |

Send optional $index parameter to get just one Msgno (e.g., get_ids_from_search(0) returns the first Msgno found).

202 | // set the IMAP search terms
203 | $this->peeker->set_search('FROM "othername@email.com"');
204 | // do the IMAP search, return count of messages found.
205 | $search_count = $this->peeker->search_and_count_messages();
206 | // do the IMAP search, return count of messages found.
207 | $id_array = $this->peeker->get_ids_from_search();
208 | 209 |

decode_mime($encoded_string)

210 |

Email message headers and parts are often encoded using the MIME standard (Wikipedia Article on MIME). This function helps you to decode MIME-encoded strings.

211 | 212 |

Returns a MIME-decoded string.

213 | 214 |

Note: you can send an unencoded string and you will get that string back.

215 | 216 | $email = $this->peeker->message(1);
217 | $decoded_subject = $this->peeker->decode_mime($email->subject);
218 | 219 |

The Peeker class can automatically decode individual MIME-encoded header fields but does not do so unless you

  1. use the get_message() function or...
  2. set your own message class.
You can use the decode_mime() method on a message header string or you can use set_message_class() to set a custom message class that does the decoding.

220 |

The custom class can be set up to automatically handle MIME decoding tasks per message. See the Peeker Header class for an example custom class that does automatic MIME decoding. Or, just use get_message().

221 | 222 |

set_message_class($classname=filename minus extension)

223 |

Before getting messages from the mailserver you can tell the Peeker Class which class you want to use to hold and process the messages. This is useful for creating custom message handling methods.

224 |

For example, the Peeker Header "spawn" class automatically MIME-decodes header parts. It also provides methods to access all the header fields.

225 |

Note: if you set a custom class, there must be a file with the name and a .php extension located in the same directory with the Peeker class.

226 | //peeker_header.php file must be in same directory
227 | $this->peeker->set_message_class('peeker_header');
228 |

If you don't set the message class (and you don't use the get_message() function), Peeker uses PHP's native stdClass object for incoming emails.

229 | 230 | 231 |

delete_and_expunge($message_number)

232 |

Delete the message and expunge it. Deleting mail from an IMAP or POP mailserver happens in two stages: deleting and expunging. This method lets you do both at the same time.

233 |

Call it only when you are sure you want to delete the message from the mailserver. There is no undo.

234 | // delete the first message
235 | $this->peeker->delete_and_expunge(1);
236 |

Note: the gMail POP and IMAP interfaces ignore this command and do not delete the message. gMail has its own internal email handling system that works different from normal POP and IMAP servers. You can change how gMail handles IMAP- and POP-accessed messages in the Settings tab at gmail.com.

237 | 238 |

set_attachment_dir($writeable_directory)

239 |

Set a directory to hold email attachments. This is a utility function that custom classes can use to create file handling methods. Returns TRUE on success.

240 | $bool = $this->peeker->set_attachment_dir(APPPATH.'attachments'); 241 |

Note: Directory must be writeable.

242 | 243 |

get_attachment_dir()

244 |

Get the directory that holds email attachments. This is a utility function that custom classes can use for file handling methods. Returns the directory path that was set.

245 | $dir = $this->peeker->get_attachment_dir(); 246 | 247 |

Detectors

248 | 249 |

The Peeker Detector system processes email in a declarative programming style. This style helps you modularize reuseable detector components. Declarative programming can also help you split tasks among multiple programmers and make your program logic clear.

250 |

Note: The Peeker Detector system only works if:

  1. you use the get_message() function or...
  2. you create a custom message class and tell Peeker to use it
$this->peeker->set_message_class('peeker_header'); 251 | Consider it an advanced, powerful and completely optional technique.

252 | 253 |

make_detector_set()

254 | 255 |

Create and return a detector set object that will hold all the detectors.

256 | 257 | $detector = $this->peeker->make_detector_set(); 258 | 259 | 260 |

The detector set and its detectors must be created before calling the get_message() or message() method. See peeker_detector class documentation for complete examples.

261 | 262 |

detectors_abort($state)

263 | 264 |

Tell the detector loop to abort immediately. Send TRUE or FALSE.

265 | 266 | $this->peeker->detectors_abort(TRUE); 267 | 268 | 269 |
270 | 271 | 272 | 273 | 281 | 282 | 283 | -------------------------------------------------------------------------------- /peeker_parts.php: -------------------------------------------------------------------------------- 1 | log_state('LOADING parts class'); 38 | } 39 | 40 | /** 41 | * Wrapper, pass on to parent class 42 | * 43 | */ 44 | public function get_parts() 45 | { 46 | $this->get_body(); 47 | } 48 | 49 | /** 50 | * 51 | * Parse e-mail structure into array var 52 | * this will handle nested parts properly 53 | * it will recurse and use the initial part number 54 | * concatenating it with nested parts using dot . 55 | * Useful information on type numbers and encodings 56 | * from http://php.net 57 | * 58 | */ 59 | public function extract_parts($structure_parts_array, $part_no = NULL, $recurse_part_no=NULL) 60 | { 61 | //pe($structure_parts_array); 62 | // we are in a recursion section 63 | if($part_no!==NULL) 64 | { 65 | $base_part_no = $part_no; 66 | } 67 | 68 | foreach ($structure_parts_array as $part_no => $part_def_obj) 69 | { 70 | //p($part_no);p($base_part_no);p($part_def_obj); 71 | // check if we are at root of tree 72 | if($recurse_part_no===NULL) 73 | { 74 | $part_no++; 75 | } 76 | else 77 | { 78 | $part_no = ($base_part_no).'.'.($recurse_part_no++); 79 | } 80 | 81 | // start with part as number 1 - will this always work? 82 | // get just one part as a string, it's probably encoded 83 | $part_string=imap_fetchbody($this->peek_parent->resource, $this->Msgno, $part_no); 84 | 85 | // DECODE the part if it's encoded 86 | if ($part_def_obj->encoding==3) // base64 87 | { 88 | $part_string=base64_decode($part_string); 89 | } 90 | elseif ($part_def_obj->encoding==4) // quoted printable 91 | { 92 | $part_string=quoted_printable_decode($part_string); 93 | } 94 | // If binary or 8bit - we don't need to decode 95 | 96 | // attachments, multipart types are 1-9 97 | $sub_type = strtoupper($part_def_obj->subtype); 98 | 99 | // type 0 is text 100 | // handle everything else or XML files (which are type 0, but subtype XML) 101 | if ($part_def_obj->type || $sub_type == 'XML') 102 | { 103 | // first try dparameter value for the filename 104 | // get an attachment, set filename to dparameter value 105 | $filename=''; 106 | if ($part_def_obj->ifdparameters && count($part_def_obj->dparameters)) 107 | { 108 | foreach ($part_def_obj->dparameters as $dp) 109 | { 110 | $attr = strtoupper($dp->attribute); 111 | if (($attr=='NAME') OR ($attr=='FILENAME')) 112 | { 113 | //p('dparams');p($dp); 114 | $filename=$dp->value; 115 | break; 116 | } 117 | } 118 | } 119 | 120 | // if no filename yet, try the parameter value, maybe it's there 121 | if ($filename=='') 122 | { 123 | if ($part_def_obj->ifparameters && count($part_def_obj->parameters)) 124 | { 125 | foreach ($part_def_obj->parameters as $p) 126 | { 127 | $attr = strtoupper($p->attribute); 128 | if (($attr=='NAME') OR ($attr=='FILENAME')) 129 | { 130 | $filename=$p->value; 131 | break; 132 | } 133 | } 134 | } 135 | } 136 | 137 | // store the part_def_obj id value 138 | // is used as the "cid" for inline attachment display 139 | $cid = (isset($part_def_obj->id)) ? $part_def_obj->id: ''; 140 | //trim the tag chars to prepare it for storage 141 | $cid = trim($cid,'<>'); 142 | 143 | // still no filename, last attempt 144 | // check the cid, otherwise make up a filename 145 | // and give it an extension from the subtype 146 | if ($filename=='') 147 | { 148 | if ($cid=='') 149 | { 150 | // changed to uppercase above 151 | if ($sub_type === 'DELIVERY-STATUS') 152 | { 153 | // handle the DSN message see RFC 1894 for details 154 | $filename = 'Delivery-Status-Notification.txt'; 155 | } 156 | else 157 | { 158 | //$filename = 'no_filename_probably_RELATED_or_ALTERNATIVE_part_node'.'.'.$sub_type; 159 | $filename = ''; 160 | } 161 | $part_string = ''; 162 | } 163 | else 164 | { 165 | $filename = $cid.'.'.$sub_type; 166 | } 167 | } 168 | 169 | // don't rely on ifdisposition, check it here 170 | $disposition = (isset($part_def_obj->disposition)) ? $part_def_obj->disposition: ''; 171 | $bytes = (isset($part_def_obj->bytes)) ? $part_def_obj->bytes: ''; 172 | 173 | // this is a little heavy handed 174 | // is there a better design for this? 175 | $assoc_array = array('filename'=>$filename, 176 | 'string'=>$part_string, 177 | 'encoding'=>$part_def_obj->encoding, 178 | 'part_no'=>$part_no, 179 | 'cid'=>$cid, 180 | 'disposition'=>$disposition, 181 | 'bytes'=>$bytes, 182 | 'type'=>$part_def_obj->type, 183 | 'subtype'=>$sub_type); 184 | // only create the object if 185 | // there is a filename 186 | if ($filename !== '') 187 | { 188 | $this->parts_array[] = new peeker_file($assoc_array); 189 | } 190 | } 191 | 192 | // detect text file and HTML attachments 193 | // type 0, disposition ATTACHMENT 194 | // if text file has no extension 195 | // it is reported as type application, subtype OCTET-STREAM 196 | // NOTE: text files with improper extension are not handled here! 197 | // they are handled above and saved with their filename 198 | elseif($part_def_obj->ifdisposition AND strtoupper($part_def_obj->disposition) == 'ATTACHMENT') 199 | { 200 | // the filename is assumed to be in the first array item value 201 | // this may be incorrect 202 | $filename = $part_def_obj->dparameters[0]->value; 203 | 204 | // fill in the parts array, this is a bit redundant 205 | // with the code a few lines above that fills in same info 206 | $assoc_array = array('filename'=>$filename, 207 | 'string'=>$part_string, 208 | 'encoding'=>$part_def_obj->encoding, 209 | 'part_no'=>$part_no, 210 | 'disposition'=>$part_def_obj->disposition, 211 | 'type'=>$part_def_obj->type, 212 | 'subtype'=>$sub_type); 213 | 214 | $this->parts_array[$part_no] = new peeker_file($assoc_array); 215 | } 216 | 217 | // Text or HTML email INLINE (not ATTACHMENT), type is 0 218 | else 219 | { 220 | // creates an instance var for $this->HTML or $this->PLAIN 221 | // NOTE: only works for the last part or sub-part extracted 222 | // but, are there ever more than two parts sent in type == 0? 223 | // yes, sometimes attachments break up the plain parts 224 | // 225 | // do not fill the parts_array with this multipart email 226 | // because it maps directly to the more accessible PLAIN and HTML 227 | // properties in the body class 228 | // reconstruct the text by concatenating it 229 | if ($sub_type === 'PLAIN') 230 | { 231 | $this->PLAIN .= $part_string; 232 | } 233 | 234 | if ($sub_type === 'HTML') 235 | { 236 | $this->HTML .= $part_string; 237 | } 238 | } 239 | 240 | // if there are subparts call this function recursively 241 | // adding the dot and number to traverse the object 242 | if (isset($part_def_obj->parts) && count($part_def_obj->parts)) 243 | { 244 | // start at sub index 1 for the next lower level on the tree 245 | $this->extract_parts($part_def_obj->parts, $part_no, 1); 246 | } 247 | } 248 | // store the count, helps determine if we have an attachment 249 | $this->parts_count = count($this->parts_array); 250 | return TRUE; 251 | } 252 | 253 | /** 254 | * access the count 255 | * 256 | */ 257 | public function get_parts_count() 258 | { 259 | return $this->parts_count; 260 | } 261 | 262 | /** 263 | * access the array 264 | * 265 | */ 266 | public function get_parts_array() 267 | { 268 | return $this->parts_array; 269 | } 270 | 271 | // ------- detectors - return boolean ------- // 272 | /** 273 | * returns true if the message has 274 | * at least one attachment 275 | * looks at the parts_array to determine 276 | * 277 | */ 278 | public function has_attachment() 279 | { 280 | return (bool)$this->parts_count; 281 | } 282 | 283 | /** 284 | * returns true if the message has 285 | * at least one attachment of dispostion 286 | * looks at the parts_array to determine 287 | * 288 | */ 289 | public function has_at_least_one_attachment_with_disposition($disp) 290 | { 291 | if ($this->has_attachment()) 292 | { 293 | foreach ($this->parts_array as $p) 294 | { 295 | if ($p->get_disposition() === $disp) 296 | { 297 | return TRUE; 298 | } 299 | } 300 | } 301 | return FALSE; 302 | } 303 | 304 | /** 305 | * the 'moblog' detector 306 | * returns true if the message has one file 307 | * attached or inline - only one file of subtype 308 | * looks at the parts_array to determine 309 | * subtype uses shortest indicator eg. jpg not jpeg 310 | * 311 | */ 312 | public function has_at_least_one_attachment($subtype) 313 | { 314 | if ($this->has_attachment()) 315 | { 316 | foreach ($this->parts_array as $p) 317 | { 318 | if ($p->get_subtype() === $subtype) 319 | { 320 | return TRUE; 321 | } 322 | } 323 | } 324 | return FALSE; 325 | } 326 | 327 | //---------- callbacks ---------// 328 | 329 | //---------TRANSFORM DATA---------// 330 | 331 | /** 332 | * rewrites the specified string with new appended text 333 | * 334 | */ 335 | public function insert_HTML($str) 336 | { 337 | // going to have to fix some broken HTML here 338 | // to be able to insert the HTML where we want to 339 | // have to get_html_filtered() because other 340 | // filtering operations could be going on 341 | $html_f = $this->get_html_filtered(); 342 | // make sure there are body tags around the HTML 343 | // before trying to replace them 344 | if (strpos('',$html_f)===FALSE) 345 | { 346 | $this->HTML_rewritten = $html_f.$str; 347 | } 348 | else 349 | { 350 | $this->HTML_rewritten = str_replace('', $str.'', $html_f); 351 | } 352 | //pe($this->HTML_rewritten); 353 | } 354 | 355 | /** 356 | * rewrites the specified string with new appended text 357 | * 358 | */ 359 | public function insert_PLAIN($str) 360 | { 361 | $this->PLAIN_rewritten = $this->PLAIN . $str; 362 | //pe($this->PLAIN_rewritten); 363 | } 364 | 365 | 366 | /** 367 | * rewrites the HTML string in the body HTML property 368 | * to point to imgs via img src URL rather than cid: 369 | * 370 | */ 371 | public function rewrite_html_transform_img_tags($base_url='') 372 | { 373 | $cid_array = array(); 374 | $file_path_array = array(); 375 | foreach ($this->parts_array as $file) 376 | { 377 | // if we don't have an inline image, skip 378 | if (($file->get_disposition() !== 'INLINE') OR ($file->get_type() != 5)) continue; 379 | // add in the other text that surround the inline tag 380 | $cid_string = 'cid:'.$file->get_cid(); 381 | $fn = $file->get_filename(); 382 | // make a better filename for URL - could just encode it here 383 | $file_name = preg_replace('/[^a-z0-9_\-\.]/i', '_', $fn); 384 | // construct the path 385 | // gather together into arrays so we can do one str_replace 386 | $cid_array[] = $cid_string; 387 | $file_path_array[] = $base_url . $this->fingerprint .DIRECTORY_SEPARATOR.$file_name; 388 | } 389 | 390 | $this->HTML_rewritten = str_replace($cid_array, $file_path_array, $this->get_html_filtered()); 391 | //p($this->HTML_rewritten); 392 | } 393 | 394 | //---------RENDER to BROWSER---------// 395 | 396 | /** 397 | * send one raw jpeg image to the browser 398 | * with its own header, only send the first one 399 | * 400 | */ 401 | public function render_first_jpeg() 402 | { 403 | foreach ($this->parts_array as $p) 404 | { 405 | $sub_t = strtolower($p->subtype); 406 | if ( $sub_t =='jpg' || $sub_t =='jpeg' ) 407 | { 408 | header("Content-Type: image/jpeg;"); 409 | echo $p['string']; 410 | // only send out the first one 411 | exit(); 412 | } 413 | } 414 | } 415 | 416 | 417 | //---------SAVE to FILE SYSTEM---------// 418 | // 20090415 - these could be broken out 419 | // alongside the functions to prepare the data 420 | // for table insertion. so, file writing and db 421 | // calls can be packaged out nicely 422 | // TODO: 20110809 break these out into layer? 423 | // maybe peeker_file_methods.php? 424 | 425 | /** 426 | * save the header string 427 | */ 428 | public function save_header_string($file_name='header_string.txt') 429 | { 430 | // if there is no immediate dir, make one from the fingerprint 431 | $dir = $this->_make_dir($this->peek_parent->attachment_dir . $this->get_fingerprint()); 432 | $fn = $dir.$file_name; 433 | $this->_save_file($fn, $this->header_string); 434 | } 435 | 436 | /** 437 | * save the body string 438 | */ 439 | public function save_body_string($file_name='body_string.txt') 440 | { 441 | // if there is no immediate dir, make one from the fingerprint 442 | $dir = $this->_make_dir($this->peek_parent->attachment_dir . $this->get_fingerprint()); 443 | $fn = $dir.$file_name; 444 | $this->_save_file($fn, $this->body_string); 445 | } 446 | 447 | /** 448 | * save the PLAIN part 449 | */ 450 | public function save_PLAIN($file_name='PLAIN.txt') 451 | { 452 | // if there is no immediate dir, make one from the fingerprint 453 | $dir = $this->_make_dir($this->peek_parent->attachment_dir . $this->get_fingerprint()); 454 | $fn = $dir.$file_name; 455 | $this->_save_file($fn, $this->PLAIN); 456 | } 457 | 458 | /** 459 | * save the HTML part 460 | */ 461 | public function save_HTML($file_name='HTML.html') 462 | { 463 | // if there is no immediate dir, make one from the fingerprint 464 | $dir = $this->_make_dir($this->peek_parent->attachment_dir . $this->get_fingerprint()); 465 | $fn = $dir.$file_name; 466 | $this->_save_file($fn, $this->HTML); 467 | } 468 | 469 | /** 470 | * save all real parts to files on the filesystem 471 | * iterate the parts_array ignoring the junk 472 | * $dir should end in slash 473 | */ 474 | public function save_all_attachments($dir=NULL) 475 | { 476 | // if there is no immediate dir, make one from the fingerprint 477 | if ($dir===NULL) 478 | { 479 | // make sure we have someplace to write the files first 480 | if ($att_dir = $this->peek_parent->get_attachment_dir()) 481 | { 482 | $dir = $this->_make_dir($att_dir . $this->get_fingerprint()); 483 | } 484 | else 485 | { 486 | $this->log_state('attachment_dir not set yet: '.$filename . ' not written to disk.'); 487 | return FALSE; 488 | } 489 | } 490 | else 491 | { 492 | $dir = $this->_make_dir($dir); 493 | } 494 | 495 | // define the pattern that is going to make a nice filename 496 | $pattern_for_filename = '/[^a-z0-9_\-\.]/i'; 497 | // Save file attachments to disk 498 | foreach ($this->parts_array as $attach) 499 | { 500 | // don't bother saving the 'node' MIME parts 501 | // there may be other parts that need to be listed here 502 | if ($attach->get_subtype() === 'ALTERNATIVE' OR 503 | $attach->get_subtype() === 'RELATED') continue; 504 | // at this point should mainly be dealing with 505 | // image or text or HTML files 506 | // make sure the filename is not MIME encoded 507 | $a_f = $this->peek_parent->decode_mime($attach->filename); 508 | //if ($a_f === '') continue; 509 | $a_f = preg_replace($pattern_for_filename, '_', $a_f); 510 | $local_rewrtten_filepath = $dir.$a_f; 511 | // add this rewritten filename to the list 512 | $this->local_file_name_array[] = $local_rewrtten_filepath; 513 | 514 | $this->log_state('Saving attachment to file: '.$local_rewrtten_filepath); 515 | $this->_save_file($local_rewrtten_filepath, $attach->get_string()); 516 | } 517 | $this->local_file_name_count = count($this->local_file_name_array); 518 | 519 | return TRUE; 520 | } 521 | 522 | /** 523 | * from php.net comments on unlink() function 524 | * recursive in case there are directories 525 | * uses array_map and glob 526 | * added 20110809 527 | */ 528 | public function delete_all_attachments($path=NULL) 529 | { 530 | $path = ($path===NULL) ? $this->peek_parent->get_attachment_dir() . $this->get_fingerprint() : $path; 531 | 532 | if (is_file($path)) 533 | { 534 | @unlink($path); 535 | } 536 | else 537 | { 538 | // target this object's method to make a recursive function 539 | // using array_map() 540 | // glob does not find any dot dirs . or .. 541 | // nor does it find 'hidden' files starting with . 542 | array_map(array(&$this, 'delete_all_attachments'), glob($path.DIRECTORY_SEPARATOR.'*')); 543 | @rmdir($path); 544 | } 545 | return TRUE; 546 | } 547 | 548 | /** 549 | * returns the array of file names written 550 | * by the save_all_attachments() function 551 | * the filenames have been changed from the original 552 | * to allow them to live comfortably on a generic filesystem 553 | * use get_parts_array() method to get original filenames 554 | * 555 | */ 556 | public function get_local_file_name_array() 557 | { 558 | return $this->local_file_name_array; 559 | } 560 | 561 | //-------- file utilities ---------// 562 | /** 563 | * 564 | * if it doesn't exist already 565 | * create a writeable directory 566 | * where we can store stuff 567 | * 568 | */ 569 | public function _make_dir($potential_name='') 570 | { 571 | if (!is_dir($potential_name)) mkdir($potential_name, 0777); 572 | return rtrim($potential_name, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; 573 | } 574 | 575 | /** 576 | * Save messages on local disc, potential 577 | * name and file lock collision here 578 | */ 579 | public function _save_file($filename, $data) 580 | { 581 | $fp=fopen($filename,"w+"); 582 | $wrote = fwrite($fp,$data); 583 | fclose($fp); 584 | } 585 | } 586 | 587 | //EOF 588 | -------------------------------------------------------------------------------- /peeker_header.php: -------------------------------------------------------------------------------- 1 | peek_parent =& $peek_parent; 111 | 112 | // handle the stdClass $imap_h_obj 113 | // populating builtin vars 114 | $this->_set_class_vars($imap_h_obj); 115 | 116 | // handle special cases, clean up, 117 | // or default conversions 118 | $this->Msgno = trim($this->Msgno); 119 | // decode the MIME representation 120 | // keep the undecoded subject for encoding detection 121 | $this->raw_subject = $this->subject; 122 | $this->subject = $this->peek_parent->decode_mime($this->subject); 123 | // create a hash for dupe detections 124 | // and other things (e.g., directory name) 125 | $this->_generate_email_fingerprint(); 126 | 127 | $this->log_state('LOADING header class'); 128 | } 129 | 130 | /* 131 | * Utility function to make sure all incoming 132 | * data has a place to go inside this object 133 | * 134 | * 135 | */ 136 | 137 | public function _set_class_vars($obj) 138 | { 139 | $class = get_class($this); 140 | $class_vars = get_class_vars($class); 141 | 142 | // check that each of the passed parameters 143 | // are valid before setting the class variable 144 | foreach ( $obj as $var => $value ) 145 | { 146 | if ( array_key_exists( $var, $class_vars ) ) 147 | { 148 | $this->$var = $value; 149 | } 150 | else 151 | { 152 | //log_message('DEBUG','setClassVars: class var "'.$var.'" not in class "' .$class.'"'); 153 | } 154 | } 155 | } 156 | 157 | /** 158 | * do fingerprint 159 | * so we can use it to check email dupes 160 | * (among other things) 161 | * compose from a string with data points 162 | * that are pulled from the email header 163 | * NOTE: these are raw headers before 164 | * applying any decoding 165 | * could "salt" this... 166 | */ 167 | public function _generate_email_fingerprint() 168 | { 169 | foreach ($this->fingerprint_sources as $prop) 170 | { 171 | $this->fingerprint .= $this->$prop; 172 | } 173 | $this->fingerprint = md5($this->fingerprint); 174 | } 175 | 176 | 177 | /** 178 | * run the error handler and store the error array 179 | * inside this object 180 | * 181 | */ 182 | public function _check_imap_errors($func) 183 | { 184 | $err = imap_errors(); 185 | if ($err !== FALSE) $this->error_array[$func] = $err; 186 | } 187 | 188 | 189 | /** 190 | * Get the fingerprint hash 191 | * 192 | */ 193 | public function get_fingerprint() 194 | { 195 | return $this->fingerprint; 196 | } 197 | 198 | /** 199 | * Get the RFC822 date 200 | * just returns the raw date as sent with the message 201 | */ 202 | public function get_date() 203 | { 204 | return $this->date; 205 | } 206 | 207 | 208 | /** 209 | * Get the subject 210 | * Subject is sometimes encoded 211 | */ 212 | public function get_subject() 213 | { 214 | return $this->subject; 215 | } 216 | 217 | /** 218 | * Get the temp message id assigned by the mail server 219 | * 220 | */ 221 | public function get_msgno() 222 | { 223 | return $this->Msgno; 224 | } 225 | 226 | /** 227 | * Get the message id 228 | * 229 | */ 230 | public function get_message_id() 231 | { 232 | return $this->message_id; 233 | } 234 | 235 | /** 236 | * these Accessor functions return the string 237 | * which may contain multiple addresses in string 238 | * 239 | */ 240 | public function get_to() {return $this->_get_address_string('to');} 241 | public function get_from() {return $this->_get_address_string('from');} 242 | public function get_reply_to() {return $this->_get_address_string('reply_to');} 243 | public function get_sender() {return $this->_get_address_string('sender');} 244 | public function get_cc() {return $this->_get_address_string('cc');} 245 | public function get_bcc() {return $this->_get_address_string('bcc');} 246 | public function get_return_path() {return $this->_get_address_string('return_path');} 247 | 248 | /** 249 | * Get the address 250 | * Send types: to, from, reply_to, sender, cc, bcc, return_path 251 | * Like subject, this is sometimes encoded 252 | * returns FALSE if there is no address type 253 | * This is like the get_address_array() fn 254 | * but, it returns the raw string (mime decoded) 255 | */ 256 | public function _get_address_string($in_type) 257 | { 258 | $type = strtolower($in_type).'address'; 259 | if (isset($this->$type)) 260 | { 261 | $data = $this->$type; 262 | } 263 | else 264 | { 265 | return FALSE; 266 | } 267 | // something there, decode it 268 | $data = $this->peek_parent->decode_mime($data); 269 | return $data; 270 | } 271 | 272 | /** 273 | * these Accessor functions return the array 274 | * which may contain multiple addresses as items 275 | * 276 | */ 277 | public function get_to_array($format=NULL) {return $this->_get_address_array('to',$format);} 278 | public function get_from_array($format=NULL) {return $this->_get_address_array('from',$format);} 279 | public function get_reply_to_array($format=NULL) {return $this->_get_address_array('reply_to',$format);} 280 | public function get_sender_array($format=NULL) {return $this->_get_address_array('sender',$format);} 281 | public function get_cc_array($format=NULL) {return $this->_get_address_array('cc',$format);} 282 | public function get_bcc_array($format=NULL) {return $this->_get_address_array('bcc',$format);} 283 | public function get_return_path_array($format=NULL) {return $this->_get_address_array('return_path',$format);} 284 | 285 | /** 286 | * Get the address array for the message id and type 287 | * Send types: to, from, reply_to, sender, cc, bcc, return_path 288 | * Like subject, this is sometimes encoded 289 | * so we decode each part here 290 | * returns FALSE if there is no address type 291 | */ 292 | public function _get_address_array($in_type, $format) 293 | { 294 | $address_array = array(); 295 | $type = strtolower($in_type); 296 | if (isset($this->$type)) 297 | { 298 | $address_array = $this->$type; 299 | } 300 | else 301 | { 302 | return $address_array; // empty array 303 | } 304 | 305 | $addr_part_names = array('personal','mailbox','host'); 306 | 307 | // something's there, decode each array item 308 | foreach ($address_array as $key => $item) 309 | { 310 | // decode each address part - probably overkill 311 | foreach ($addr_part_names as $addr_part) 312 | { 313 | if (isset ($item->$addr_part)) 314 | { 315 | // stuff it back into the array 316 | $address_array[$key]->$addr_part = $this->peek_parent->decode_mime($item->$addr_part); 317 | } 318 | } 319 | //p($address_array); 320 | // replace the format strings with the properties 321 | if ($format !== NULL) 322 | { 323 | $formatted = str_replace($addr_part_names, array('$item->personal', '$item->mailbox', '$item->host'), $format); 324 | // ugly eval lets us do formatting 325 | // trick without double-replacing (ie str_replace in a loop) 326 | // suppress errors from missing addr_part 327 | @eval("\$evaled = \"$formatted\";"); 328 | $address_array[$key] = $evaled; 329 | } 330 | } 331 | return $address_array; 332 | } 333 | 334 | /** 335 | * Get the size of the message 336 | * converted from Size 337 | */ 338 | public function get_size() 339 | { 340 | return $this->Size; 341 | } 342 | 343 | 344 | /** 345 | * Get the unix timestamp on the message 346 | * converted from udate field 347 | */ 348 | public function get_udate() 349 | { 350 | return $this->udate; 351 | } 352 | 353 | /** 354 | * alias to get_udate 355 | */ 356 | public function get_timestamp() 357 | { 358 | return $this->get_udate(); 359 | } 360 | 361 | /** 362 | * Get the raw header string 363 | * all kinds of good data in here 364 | * that is not available in imap_fetchheader() 365 | */ 366 | public function get_header_string() 367 | { 368 | return $this->header_string; 369 | } 370 | 371 | /** 372 | * Get the array that holds the 373 | * converted raw header string 374 | * all kinds of good data in here 375 | * that is not available in imap_fetchheader() 376 | */ 377 | public function get_header_array() 378 | { 379 | return $this->header_array; 380 | } 381 | 382 | 383 | /** 384 | * A generic header getter. 385 | * Get one item from the header_array 386 | * Pass the header name, if not there 387 | * function returns FALSE 388 | * Note: sometimes header items will be arrays 389 | * eg. Received header is usually an array 390 | */ 391 | public function get_header_item($header_key) 392 | { 393 | $h = FALSE; 394 | if (isset($this->header_array[$header_key])) 395 | { 396 | $h = $this->header_array[$header_key]; 397 | } 398 | else 399 | { 400 | $this->log_state('header_item not set. key not in header array: '.$header_key); 401 | } 402 | return $h; 403 | } 404 | 405 | /** 406 | * Get the array that holds the 407 | * error message arrays with keys 408 | * for the function name that 409 | * triggered the error 410 | */ 411 | public function get_error_array() 412 | { 413 | return $this->error_array; 414 | } 415 | 416 | /** 417 | * Return mark_delete 418 | * 419 | */ 420 | public function get_mark_delete() 421 | { 422 | return $this->mark_delete; 423 | } 424 | 425 | /** 426 | * Mark this object as deleted 427 | * 428 | */ 429 | public function set_mark_delete($delete) 430 | { 431 | $this->mark_delete = $delete; 432 | return $this->mark_delete; 433 | } 434 | 435 | 436 | /*------Wrappers--------*/ 437 | 438 | /** 439 | * wrapper to pipe the detectors_abort 440 | * message to the peek_parent 441 | * 442 | */ 443 | public function detectors_abort($state=TRUE) 444 | { 445 | $this->peek_parent->detectors_abort($state); 446 | } 447 | 448 | /** 449 | * abort, do action array items if called for 450 | * right now only delete is possible 451 | */ 452 | public function abort($action_array=FALSE) 453 | { 454 | $del = FALSE; 455 | if (isset($action_array['delete']) && $action_array['delete'] === TRUE) 456 | { 457 | $del = $this->set_mark_delete(TRUE); 458 | } 459 | $this->detectors_abort(TRUE); 460 | $this->log_state('» ABORTING detectors #'.$this->Msgno . ' Delete? :' . strtoupper(var_export($del,TRUE))); 461 | } 462 | 463 | 464 | /** 465 | * if most recent detector fired TRUE make all the 466 | * other detectors abort, pass action_array argument 467 | * 468 | * 469 | */ 470 | public function abort_if_previous($action_array=FALSE) 471 | { 472 | if ($this->peek_parent->_get_previous_detector_state()) 473 | { 474 | $this->abort($action_array); 475 | } 476 | } 477 | 478 | 479 | // ------- detectors - return boolean ------- // 480 | 481 | /** 482 | * return TRUE all the time 483 | * for testing 484 | */ 485 | public function ttrue($arg) 486 | { 487 | return TRUE; 488 | } 489 | 490 | /** 491 | * return FALSE all the time 492 | * for testing 493 | */ 494 | public function ffalse($arg) 495 | { 496 | return FALSE; 497 | } 498 | 499 | /** 500 | * return whatever was sent in 501 | * for testing 502 | */ 503 | public function reflect($arg) 504 | { 505 | return $arg; 506 | } 507 | 508 | /** 509 | * true if Msgno property is equal to arg 510 | * 511 | */ 512 | public function is_msgno($msgno) 513 | { 514 | return $this->Msgno == $msgno; 515 | } 516 | 517 | /** 518 | * true if regex $pattern matches the field 519 | * return TRUE or FALSE, not int like preg_match 520 | */ 521 | public function preg_match_field($arr) 522 | { 523 | list($field,$pattern) = $arr; 524 | return (bool)preg_match($pattern, $this->$field); 525 | } 526 | 527 | /** 528 | * true if header array key is set 529 | * the key exists, but could be NULL 530 | * cf array_key_exists() if you need 531 | * a key exists test without the NULL check 532 | */ 533 | public function isset_header_array_key($key) 534 | { 535 | return isset($this->header_array[$key]); 536 | } 537 | 538 | /** 539 | * true if regex $pattern matches the header entry 540 | * return TRUE or FALSE, not int like preg_match 541 | */ 542 | public function preg_match_header_array_key($array) 543 | { 544 | list($key, $pattern) = $array; 545 | if ($this->isset_header_array_key($key)) 546 | { 547 | return (bool)preg_match($pattern, implode(' ',(array)$this->header_array[$key])); 548 | } 549 | else 550 | { 551 | return FALSE; 552 | } 553 | } 554 | 555 | 556 | /** 557 | * true if header property is empty 558 | * 559 | */ 560 | public function empty_property($property) 561 | { 562 | $e = empty($this->$property); 563 | if ($e) $this->log_state('EMPTY '.$property .' in msg #'.$this->Msgno); 564 | return $e; 565 | } 566 | 567 | 568 | /** 569 | * true if undecoded fromaddress has a given string in it 570 | * Case-insensitive. 571 | */ 572 | public function in_from($from_str) 573 | { 574 | return strpos(strtolower($this->fromaddress),strtolower($from_str))!==FALSE; 575 | } 576 | 577 | /** 578 | * true if undecoded toaddress has a given string in it 579 | * Case-insensitive. 580 | */ 581 | public function in_to($to_str) 582 | { 583 | return strpos(strtolower($this->toaddress),strtolower($to_str))!==FALSE; 584 | } 585 | 586 | 587 | 588 | //--------- callbacks ----------// 589 | 590 | /** 591 | * mark message for deletion 592 | * NOTE: in POP, must call imap_expunge() 593 | * before closing the connection 594 | * to actually delete the message 595 | * optional parameter lets detectors abort 596 | * on this delete as well 597 | */ 598 | public function set_delete($abort=FALSE) 599 | { 600 | if($this->Msgno > 0) 601 | { 602 | $this->log_state('Mark DELETE #'.$this->Msgno); 603 | $this->set_mark_delete(TRUE); 604 | imap_delete($this->peek_parent->resource, $this->Msgno); 605 | // pass the action_array with delete as FALSE 606 | if ($abort) $this->abort(array('delete'=>FALSE)); 607 | } 608 | else 609 | { 610 | $this->log_state('Cannot DELETE zero or negative #'.$this->Msgno); 611 | } 612 | } 613 | 614 | /** 615 | * unmark message for deletion 616 | * allows removing messages from 617 | * the delete state 618 | */ 619 | public function undelete() 620 | { 621 | $this->set_mark_delete(FALSE); 622 | imap_undelete($this->peek_parent->resource, $this->Msgno); 623 | $this->log_state('Undeleted message #'.$this->Msgno); 624 | } 625 | 626 | /** 627 | * IMAP specific - wraps flag and move functions 628 | * flag seen, then move message to another mailbox 629 | * helps us remember to flag, then move 630 | */ 631 | public function flag_seen() 632 | { 633 | $this->flag_mail('\Seen'); 634 | } 635 | 636 | /** 637 | * move a message to another mailbox 638 | * 639 | */ 640 | public function move_mail($mailbox_name) 641 | { 642 | $this->peek_parent->move_mail($this->Msgno, $mailbox_name); 643 | $this->log_state('Moved message #'.$this->Msgno . ' to '. $mailbox_name); 644 | } 645 | 646 | /** 647 | * IMAP specific - wraps flag and move functions 648 | * flag seen, then move message to another mailbox 649 | * helps us remember to flag, then move 650 | */ 651 | public function flag_seen_move_mail($mailbox_name) 652 | { 653 | $this->flag_seen(); 654 | $this->move_mail($mailbox_name); 655 | } 656 | 657 | /** 658 | * flag a message 659 | * see http://php.net/imap_setflag_full 660 | * for which flags you can use: 661 | * \Seen, \Answered, \Flagged, \Deleted, 662 | * and \Draft as defined by RFC2060 663 | * Default to TRUE flag_state, set FALSE 664 | * to remove the flag 665 | */ 666 | public function flag_mail($flag_string,$flag_state=TRUE) 667 | { 668 | $this->peek_parent->flag_mail($this->Msgno, $flag_string, $flag_state); 669 | $this->log_state('Flagged message #'.$this->Msgno . ' as '. $flag_string . (string)$flag_state); 670 | } 671 | 672 | 673 | 674 | 675 | /** 676 | * display utility 677 | * removes peek_parent property 678 | * 679 | */ 680 | public function _print($d=NULL) 681 | { 682 | if ($d===NULL) $d = $this; 683 | // don't display the peek parent property 684 | if (isset($d->peek_parent)) unset($d->peek_parent); 685 | echo '
';print_r($d);echo '
'; 686 | } 687 | 688 | /** 689 | * print the argument 690 | * 691 | */ 692 | public function pr($data) 693 | { 694 | $this->_print('PRINT: '.$data.' in message #'.$this->Msgno); 695 | } 696 | 697 | /** 698 | * print the field_name 699 | * assumes the property name 700 | * has an accessor function 701 | */ 702 | public function print_field($fn, $use_html_entities=TRUE) 703 | { 704 | $func = 'get_'.$fn; 705 | $data = ($use_html_entities) ? htmlentities($this->$func()) : $this->$func(); 706 | $this->_print('PRINT field: '.$fn.' in message #'.$this->Msgno . ' : '.$data); 707 | } 708 | 709 | /** 710 | * print the array 711 | * and optional nested sub-item 712 | * 713 | */ 714 | public function print_array($arr) 715 | { 716 | list($fn,$sub_fn) = $arr; 717 | $func = 'get_'.$fn; 718 | $data = $this->$func(); 719 | $data = ($sub_fn!=='') ? $data[$sub_fn]: $data; 720 | $this->_print('Message #'.$this->Msgno. ' ' .$fn. '...'); echo '
'; 721 | $this->_print($data); 722 | 723 | } 724 | 725 | 726 | /** 727 | * prepend a string to the subject 728 | * useful for tagging messages 729 | * before storing or re-sending 730 | * only prepend if there is no string 731 | * already in the subject 732 | * 733 | */ 734 | public function prepend_subject($prepend_string) 735 | { 736 | $subj = $this->get_subject(); 737 | if (strpos($subj,$prepend_string)===FALSE) 738 | { 739 | $this->subject = $prepend_string . $subj; 740 | $this->log_state('Subject prepended to message #'.$this->Msgno . ': '. $prepend_string); 741 | } 742 | else 743 | { 744 | $this->log_state('Subject for message #'.$this->Msgno . ' already prepended: '.$prepend_string); 745 | } 746 | } 747 | 748 | 749 | /* ------ Wrappers to talk to peek_parent ------ */ 750 | 751 | /** 752 | * wrapper to get the output from 753 | * get_message_count() 754 | * allows access to the parent 755 | * from the message class while 756 | * inside the detector loop 757 | * 758 | */ 759 | public function message_count() 760 | { 761 | return $this->peek_parent->get_message_count(); 762 | } 763 | 764 | /** 765 | * wrapper to pipe log state messages 766 | * up to the parent class, allows it 767 | * to be used inside a message acquisition 768 | * loop 769 | * 770 | */ 771 | public function log_state($str) 772 | { 773 | $this->peek_parent->log_state($str); 774 | } 775 | 776 | /** 777 | * wrapper to pipe expunge calls 778 | * up to the parent class, allows it 779 | * to be used by an individual email or 780 | * inside a message acquisition loop 781 | * 782 | */ 783 | public function expunge() 784 | { 785 | $this->peek_parent->expunge(); 786 | } 787 | } 788 | 789 | // EOF -------------------------------------------------------------------------------- /peeker.php: -------------------------------------------------------------------------------- 1 | valid_mb_encoding_array = array_flip(array_change_key_case(array_flip(mb_list_encodings()))); 88 | // call the parent constructor 89 | parent::__construct($init_array); 90 | } 91 | 92 | /** 93 | * Wrapper to make assigning message classes 94 | * as easy as possible. Set the message class 95 | * to the peek_mail_parts class and get 96 | * everything using the detector method parts() 97 | * in the peek_mail_parts class 98 | */ 99 | public function get_message($start, $end=NULL) 100 | { 101 | // set the message class to the parts class 102 | // that just gets everything, this disturbs 103 | // the read state on gmail POP accounts 104 | //$this->set_message_class('peeker_parts'); 105 | // set up a detector that calls 106 | // the email acquistion method 107 | // remove the detector_set so it is recreated new each time 108 | // in case get_message is called in a loop and detector 109 | // just stacks up get_parts calls 110 | unset($this->detector_set); 111 | $this->make_detector_set(); 112 | // the most complete email "pulling" method 113 | // but, only works with peek_mail_parts class 114 | $this->detector_set->detect_phase('get_parts'); 115 | // run the acquisition loop 116 | return $this->message($start, $end); 117 | 118 | } 119 | 120 | /** 121 | * Loop over the messages 122 | * calling to the mail server each time. 123 | * Sets up the internal array 124 | * with ids as keys that the message() 125 | * function can use to get the right message 126 | * Also calls any detectors set up for messages 127 | * $start required, $end is not 128 | */ 129 | public function message($start, $end=NULL) 130 | { 131 | // protect the command from 132 | // using empty string like NULL or 0 133 | // because messages start at MsgNo 1, not 0 134 | // TODO: add exception, better error handling 135 | if ($start===0) $this->log_state('ERROR: MsgNo cannot be 0. Mail server MsgNo starts at 1.'); 136 | // force it to message 1 137 | // should change this to error 138 | $start = ($start==='') ? 1 : $start; 139 | // load the class specified in the property 140 | // there must be a better way to do this 141 | // default to the peeker_header class 142 | include_once($this->message_class.'.php'); 143 | 144 | // set start_id, end_id, and current_id 145 | $this->_set_start_and_end_ids($start, $end); 146 | 147 | // defaults to handling a continuous sequence of messages 148 | // if multiple message requested, stores the messages 149 | // as objects in an array - could be memory intensive 150 | // get all the emails requested, run detectors 151 | while ($this->current_id++ < $this->end_id) 152 | { 153 | $this->log_state('Fetching headers for email #' . $this->current_id); 154 | 155 | // the imap_fetchheader() string/array overlaps 156 | // the imap_headerinfo() object data 157 | // because both functions get some of the same data but 158 | // imap_fetchheader() gets all of the raw header data and 159 | // imap_headerinfo() gets some other data like Msgno, Size, etc... 160 | // get the basic header data object 161 | // supress errors so we can track them with the message object 162 | $imap_h_obj = @imap_headerinfo($this->resource, $this->current_id); 163 | 164 | // calling imap_errors() clears all errors in the stack 165 | // stuff the errors into the message object so they get 166 | // stored (keyed to function) per message with the object 167 | $err = imap_errors(); 168 | if (!empty($err)) 169 | { 170 | $imap_h_obj->error_array['imap_headerinfo'] = $err; 171 | } 172 | 173 | // get the header using imap_fetchheader() 174 | // to acquire additional header fields 175 | $header_string = @imap_fetchheader($this->resource, $this->current_id); 176 | $err = imap_errors(); 177 | if (!empty($err)) 178 | { 179 | $imap_h_obj->error_array['imap_fetchheader'] = $err; 180 | } 181 | // tuck the header string in the object and 182 | // tuck the array in the object, there is some overlap 183 | $imap_h_obj->header_string = $header_string; 184 | $imap_h_obj->header_array = $this->_extract_headers_to_array($header_string); 185 | 186 | //pe($imap_h_obj); 187 | 188 | // create an email header object for each message 189 | // this needs to be able to be created using a sub-class 190 | // to allow devs to customize the detector rules 191 | // send $this to the spawned object to link it to the parent class 192 | $em_message_obj = new $this->message_class($this, $imap_h_obj); 193 | 194 | // load any layers registered onto this message object 195 | // this expects an empty, initialized array (see var declaration) 196 | // if there isn't a layer added already 197 | foreach ($this->layer_object_array as $layer) 198 | { 199 | $em_message_obj->layer_methods($layer); 200 | } 201 | 202 | // check 'global' detector state for each message 203 | // detectors can change the message object 204 | if ($this->detectors_on) 205 | { 206 | $this->detector_set->run($em_message_obj); 207 | // pull the detector log into this email object log 208 | $this->log_state($this->detector_set->get_log_array()); 209 | // reset the detector log 210 | $this->detector_set->set_log_array(array()); 211 | $this->log_state('finished detectors for '. $this->current_id ); 212 | } 213 | 214 | // make the message number (same as Msgno) the key 215 | $this->message_object_array[$this->current_id] = $em_message_obj; 216 | $this->log_state('Message end, email #' . $this->current_id); 217 | 218 | } 219 | // return one object if one message requested (only $start sent) 220 | // otherwise return the whole array of objects ($start and $end) 221 | // modified this so that it returns what it is sent 222 | // one parameter in = one email out 223 | // multiple parameters in = array out 224 | //if ($this->start_id === $this->end_id) 225 | if ( empty ($end) ) 226 | { 227 | return $this->message_object_array[$this->start_id]; 228 | } 229 | else 230 | { 231 | return $this->message_object_array; 232 | } 233 | } 234 | 235 | 236 | /** 237 | * figure out which messages are being requested 238 | * and make sure they are not out of bounds 239 | * 240 | */ 241 | private function _set_start_and_end_ids($start=NULL,$end=NULL) 242 | { 243 | $this->log_state("Requested _set_start_and_end_ids($start,$end)"); 244 | 245 | $msg_count = $this->get_message_count(); 246 | 247 | if ($start === NULL) 248 | { 249 | $this->start_id = 1; 250 | $this->current_id = 0; 251 | $this->end_id = $msg_count; 252 | } 253 | else 254 | { 255 | // if only one parameter sent 256 | // set end = start to get one msg 257 | // but make sure it doesn't ask 258 | // for a message it doesn't have 259 | // current_id immediately increments 260 | // in the loop 261 | if ($end === NULL) 262 | { 263 | // make sure start_id is not too high 264 | $this->end_id = min($start, $msg_count); 265 | $this->current_id = $this->end_id-1; 266 | $this->start_id = $this->end_id; 267 | } 268 | else 269 | { 270 | //p('two params'); 271 | // no negatives on start 272 | $this->start_id = max(1,$start); 273 | // check if start_id is too high 274 | $this->start_id = min($this->start_id, $msg_count); 275 | $this->end_id = min($end, $msg_count); 276 | $this->current_id = $this->start_id-1; 277 | } 278 | } 279 | $this->log_state("Getting _set_start_and_end_ids($this->start_id,$this->end_id)"); 280 | } 281 | 282 | 283 | // ------- IMAP Search ------- // 284 | // search returns an array of msgids that match 285 | // handle them internally 286 | 287 | /** 288 | * 289 | * set the IMAP search string 290 | * 291 | * 292 | */ 293 | public function set_search($search='') 294 | { 295 | $this->search = $search; 296 | $this->log_state('Setting search of (' . $this->mailbox . ') - to query: '.$this->search); 297 | } 298 | 299 | 300 | /** 301 | * Get the number of emails at server based on search params 302 | * Calling this function updates msg_count var 303 | * and if there is a message found 304 | */ 305 | public function search_and_count_messages($search='') 306 | { 307 | $this->log_state('Searching current mailbox (' . $this->mailbox . ') - query: '.$this->search); 308 | 309 | // the search returns array of message ids 310 | $this->id_list_from_search = imap_search($this->resource,$this->search); 311 | //p($this->id_list_from_search); 312 | if (is_array($this->id_list_from_search)) 313 | { 314 | $this->message_count_from_search = count($this->id_list_from_search); 315 | $this->log_state('Search found: '.$this->message_count_from_search . ' messages.'); 316 | return $this->message_count_from_search; 317 | } 318 | else 319 | { 320 | $this->log_state('Search found 0 messages.'); 321 | return 0; 322 | } 323 | } 324 | 325 | /** 326 | * return id array or id 327 | * from the search 328 | * 329 | */ 330 | public function get_ids_from_search($index='') 331 | { 332 | return ($index==='') ? $this->id_list_from_search : $this->id_list_from_search[$index]; 333 | } 334 | 335 | 336 | /** 337 | * Extract an array listing from the header 338 | * Get all the possible headers (multi-line, domain keys, 339 | * repeated headers , etc...) tucked away nicely into 340 | * a multi-level nested array 341 | * Note: this does not decode any headers. 342 | * If you need to decode, pull out the data and decode 343 | * using the function decode_mime() 344 | * 345 | */ 346 | private function _extract_headers_to_array($header) 347 | { 348 | $header_array = explode("\n", rtrim($header)); 349 | // drop off any empty, null or FALSE values 350 | $header_array = array_filter($header_array); 351 | 352 | $new_header_array = array(); 353 | foreach ($header_array as $key => $line) 354 | { 355 | // check if this line starts with a header name 356 | // if it does, build the new header item 357 | // if it doesn't, build the string out 358 | if (preg_match('/^([^:\s]+):\s(.+)/',$line,$m)) 359 | { 360 | // force all header keys to have ucfirst() 361 | $current_header = ucfirst($m[1]); 362 | // remove the extra newline 363 | $current_data = trim($m[2]); 364 | // if there is no header by this name yet 365 | // set the data, otherwise, append it as array item 366 | if (!isset($new_header_array[$current_header])) 367 | { 368 | // this is the normal branch, new header, one line of data 369 | $new_header_array[$current_header] = $current_data; 370 | } 371 | else 372 | { 373 | // if it is not an array, it is a string and we need 374 | // to convert the existing data to an array, and add the new 375 | if (!is_array($new_header_array[$current_header])) 376 | { 377 | // this runs when a header name is repeated 378 | // (like Received often is) 379 | // runs the 1st time it is repeated 380 | // (second occurance of the header) 381 | // converts the existing string and the 382 | // incoming string to a 2-item sub-array 383 | $new_header_array[$current_header] = array($new_header_array[$current_header],$current_data); 384 | } 385 | else 386 | // if it is already an array then append an array item 387 | { 388 | // this runs when a header name is repeated 389 | // (like Received often is) 390 | // runs 3rd and subsequent times 391 | $new_header_array[$current_header][] = $current_data; 392 | } 393 | } 394 | } 395 | else 396 | { 397 | // if it is already an array then append 398 | // the string to the last sub-array item 399 | // because we assume the lines with no header names 400 | // are part of the most recently added sub-array item 401 | if (is_array($new_header_array[$current_header])) 402 | { 403 | // this runs if there has already been a header 404 | // of the same header name 405 | $new_header_array[$current_header][count($new_header_array[$current_header])-1] .= $line; 406 | } 407 | else 408 | // if it is not an array, it is still just a string 409 | // and we need to build the string out 410 | { 411 | // this runs if the line is part of the first 412 | // header encountered 413 | // but is part of a long multiline string 414 | // (like Received header) 415 | $new_header_array[$current_header] .= $line; 416 | } 417 | } 418 | } 419 | return $new_header_array; 420 | } 421 | 422 | /** 423 | * Decode a string, return string 424 | * decoded to a specified charset. if the charset 425 | * isn't supported by mb_convert_encoding(), 426 | * def_charset will be used to decode it. 427 | * send it a MIME encoded header string 428 | */ 429 | public function decode_mime($mime_str_in, $in_charset='utf-8', $target_charset='utf-8', $def_charset='iso-8859-1') 430 | { 431 | // valid encodings in lowercase array 432 | $in_charset = strtolower($in_charset); 433 | $target_charset = strtolower($target_charset); 434 | $def_charset = strtolower($def_charset); 435 | 436 | $decoded_str = ''; 437 | $mime_strs = imap_mime_header_decode($mime_str_in); 438 | $charset_match = ($in_charset === $target_charset); 439 | foreach ($mime_strs as $mime_str) 440 | { 441 | $mime_str->charset = strtolower($mime_str->charset); 442 | if ( ( $mime_str->charset === 'default' AND $charset_match ) OR 443 | ( $mime_str->charset === $target_charset ) ) 444 | { 445 | $decoded_str .= $mime_str->text; 446 | } 447 | else 448 | { 449 | $charset = ( in_array( $mime_str->charset, $this->valid_mb_encoding_array ) ) ? $mime_str->charset : $def_charset; 450 | // TODO: this should also handle base64 encoded stings as well as quoted_printable 451 | $decoded_str .= mb_convert_encoding(quoted_printable_decode( $mime_str->text ), $target_charset, $charset ); 452 | } 453 | } 454 | return $decoded_str; 455 | } 456 | 457 | 458 | /** 459 | * Change which class the messages() loop 460 | * uses to instance email objects 461 | * so it can be subclassed and 462 | * functions added for detectors 463 | * 464 | * This is critical for adapting the 465 | * peek_mail classes to do various 466 | * things with email messages 467 | * 468 | * 469 | */ 470 | public function set_message_class($class_name) 471 | { 472 | $this->message_class = $class_name; 473 | } 474 | 475 | /** 476 | * return one or all the array of message objects 477 | * acquired in messages() 478 | * 479 | */ 480 | public function get_message_object($key='') 481 | { 482 | if ($key!=='') 483 | { 484 | return $this->message_object_array[$key]; 485 | } 486 | else 487 | { 488 | return $this->message_object_array; 489 | } 490 | } 491 | 492 | /** 493 | * flag or unflag the message 494 | * wraps the imap_setflag_full() and 495 | * the imap_clearflag_full() functions 496 | * TODO: error checking on input 497 | */ 498 | public function flag_mail($id_or_range, $flag_string, $set_flag = TRUE) 499 | { 500 | if ( $set_flag ) 501 | { 502 | $bool = imap_setflag_full($this->resource,$id_or_range,$flag_string); 503 | $this->log_state('Flagged message: ' . $id_or_range . ' as ' . $flag_string); 504 | } 505 | else 506 | { 507 | $bool = imap_clearflag_full($this->resource,$id_or_range,$flag_string); 508 | $this->log_state('Unflagged message: ' . $id_or_range . ' as ' . $flag_string); 509 | } 510 | } 511 | 512 | 513 | /** 514 | * move the message to another mailbox 515 | * wraps imap_mail_move() function 516 | * This is only applicable to IMAP connections 517 | */ 518 | public function move_mail($id_or_range, $mailbox_name) 519 | { 520 | $bool = imap_mail_move($this->resource,$id_or_range,$mailbox_name); 521 | $this->log_state('Moved message: ' . $id_or_range . ' to mailbox ' . $mailbox_name); 522 | } 523 | 524 | /** 525 | * Target one or more messages to be immediately 526 | * marked for deletion and then expunged 527 | * 528 | * 529 | * Normal POP3 does not mark messages for later deletion 530 | * must delete them and expunge them in same connection 531 | * TRUE on success, FALSE on failure 532 | * BUT... if you use any body calls Google's gmail 533 | * does mark as read and then does not serve them to POP3 again even though 534 | * they may still be in the INBOX. They are doing some extra 535 | * thing to the message to make it invisible to POP3 once it has been 536 | * picked up by ANY POP3 request that grabs the email body data (headers ok) 537 | * So, gmail does not seem to be affected by imap_delete() imap_expunge(), 538 | * it only cares about its own settings with regard to how 539 | * to handle message storage after a POP3 connection (see gmail settings tab) 540 | */ 541 | public function delete_and_expunge($start_id_or_range, $end_id = '') 542 | { 543 | // make sure we've got a message there of that id 544 | // and it's not a bogus id like 0 or -1 545 | // also allow ranges to be sent to this function 546 | // format 1:5 or 1,3,5,7 so if a colon or comma is sent, we assume 547 | // it is properly formatted - not the best idea 548 | // should check the formatting with this kind of 549 | // regexp '/[0-9]+:[*0-9]+/' 550 | if (strpos($start_id_or_range,':') OR 551 | strpos($start_id_or_range,',')) 552 | { 553 | // use the string as is, 554 | // TODO: check it is properly formatted 555 | $imap_range_string = $start_id_or_range; 556 | } 557 | // these must be numbers, not required by imap range specifiers 558 | // but it makes this function simpler to explain 559 | elseif (is_numeric($start_id_or_range) && is_numeric($end_id)) 560 | { 561 | // concatenate the end_id using the colon (the imap range specifier) 562 | $imap_range_string = $start_id_or_range .':'. $end_id; 563 | } 564 | elseif (is_numeric($start_id_or_range) && ($start_id_or_range > 0) && ($end_id=='')) 565 | { 566 | // use the number only 567 | $imap_range_string = $start_id_or_range; 568 | } 569 | else 570 | { 571 | $this->log_state('Could not delete and expunge message id_or_range: ' . $start_id_or_range . ' ' . $end_id . '. Invalid message range specified.'); 572 | return FALSE; 573 | } 574 | 575 | $this->delete($imap_range_string); 576 | $this->expunge(); 577 | return TRUE; 578 | } 579 | 580 | 581 | /** 582 | * call imap_delete() to mark messages 583 | * for deletion, must call expunge to remove them 584 | * 585 | */ 586 | public function delete($imap_range_string) 587 | { 588 | if (is_resource($this->resource)) 589 | { 590 | // TODO: capture error here when the message doesn't exist 591 | imap_delete($this->resource, $imap_range_string); 592 | $this->log_state('Marked Deleted message range: '. $imap_range_string); 593 | } 594 | else 595 | { 596 | $this->log_state('Messages could not be marked deleted. No resource.'); 597 | } 598 | } 599 | 600 | /** 601 | * call imap_expunge() to remove messages 602 | * marked for deletion 603 | * 604 | */ 605 | public function expunge() 606 | { 607 | if (is_resource($this->resource)) 608 | { 609 | imap_expunge($this->resource); 610 | $this->log_state('Expunged OK.'); 611 | } 612 | else 613 | { 614 | $this->log_state('Messages could not be expunged. No resource.'); 615 | } 616 | } 617 | 618 | 619 | /** 620 | * Wrapper to close the IMAP connection 621 | * Returns TRUE if closed with no errors 622 | * FALSE if imap_close() fails or if 623 | * there is no resource 624 | * Overrides the parent class close() method 625 | * to implement expunge facility 626 | */ 627 | public function close($expunge_override=FALSE) 628 | { 629 | if (is_resource($this->resource)) 630 | { 631 | // allows cleaner application code, 632 | // this replicates the functionality 633 | // of the imap lib CL_EXPUNGE flag 634 | if ($this->expunge_on_close OR $expunge_override) $this->expunge(); 635 | 636 | $closed = imap_close($this->resource); 637 | if ($closed) 638 | { 639 | $this->log_state('Connection closed OK.'); 640 | } 641 | else 642 | { 643 | $this->log_state('Mail Resource OK but, connection did not close OK.'); 644 | } 645 | } 646 | else 647 | { 648 | $closed = FALSE; 649 | $this->log_state('Connection could not be closed. No Mail resource.'); 650 | } 651 | $this->connected = FALSE; 652 | //p(imap_alerts()); 653 | 654 | return $closed; 655 | } 656 | 657 | //------UTILITY methods------// 658 | 659 | /** 660 | * Get an array of emails waiting 661 | * returns empty array if no messages 662 | * SLOW: about 5 messages per second 663 | */ 664 | public function fetch_overview($start=NULL, $end=NULL) 665 | { 666 | // set start_id, end_id, and current_id 667 | $this->_set_start_and_end_ids($start, $end); 668 | 669 | if(is_resource($this->resource)) 670 | { 671 | $a = imap_fetch_overview($this->resource,$this->start_id.':'.$this->end_id); 672 | } 673 | else 674 | { 675 | $a = array(); 676 | $this->log_state('No mailserver Connection. Cannot fetch overview.'); 677 | } 678 | return $a; 679 | } 680 | 681 | 682 | /** 683 | * set the directory where attachments will be stored 684 | * set error flag if not writeable 685 | * 686 | */ 687 | public function set_attachment_dir($dir) 688 | { 689 | // isdir() causes blank page error 690 | //echo var_dump((isdir($dir))); 691 | 692 | if (is_writable($dir)) 693 | { 694 | $this->log_state('Attachment directory set: ' . $dir); 695 | $this->attachment_dir = $dir; 696 | return TRUE; 697 | } 698 | else 699 | { 700 | $this->log_state('Attachment directory not writeable: ' . $dir); 701 | return FALSE; 702 | } 703 | } 704 | 705 | /** 706 | * get the path to directory 707 | * where attachments will be stored 708 | * individual messages handle their 709 | * own sub-dirs inside this main dir 710 | * return FALSE if not set yet 711 | * 712 | */ 713 | public function get_attachment_dir() 714 | { 715 | // get default dir name 716 | $class_var_defaults = get_class_vars(get_class($this)); 717 | $dir = $class_var_defaults['attachment_dir']; 718 | // has the dir been changed from default? 719 | if ($this->attachment_dir === $dir) 720 | { 721 | $this->log_state('Attachment directory still at default setting - not set yet.'); 722 | return FALSE; 723 | } 724 | else 725 | { 726 | return $this->attachment_dir; 727 | } 728 | } 729 | 730 | 731 | /** 732 | * load up the layers which provide more methods 733 | * on the email object and useable by detector circuits 734 | * TODO: loop and load all the files requested 735 | */ 736 | public function layer($layer) 737 | { 738 | // tell the object we are using layers 739 | //$this->layers = TRUE; 740 | $layer_name = 'peeker_'.$layer.'_methods'; 741 | $this->log_state('Adding layer: '.$layer_name); 742 | // load file, could do this by autoload putting file 743 | // in plugin/layer folder 744 | include_once($layer_name.'.php'); 745 | // make an object we will use to send as a layer 746 | $obj = new $layer_name; 747 | // add to array that will be iterated 748 | // over on every message object 749 | $this->layer_object_array[] = $obj; 750 | } 751 | 752 | /** 753 | * load up the set which allows us to load 754 | * detectors 755 | * 756 | */ 757 | public function make_detector_set() 758 | { 759 | include_once('peeker_detector_set.php'); 760 | // make a new one if we don't have it already 761 | if (!isset($this->detector_set)) $this->detector_set = new peeker_detector_set(); 762 | $this->detectors_on = TRUE; 763 | return $this->detector_set; 764 | } 765 | 766 | /** 767 | * wrapper to abort the detector loop 768 | * creates an interface to abort 769 | * through the peek_mail parent class 770 | */ 771 | public function detectors_abort($state) 772 | { 773 | $this->detector_set->detectors_abort($state); 774 | } 775 | 776 | /** 777 | * 778 | * returns FALSE if id is > msg_count 779 | * otherwise TRUE 780 | * 781 | */ 782 | private function _check_msg_id($msg_id) 783 | { 784 | // make sure it is less than message count 785 | // and greater than zero 786 | $id_ok = (bool)( ($this->message_count >= $msg_id) && ($msg_id > 0) ); 787 | if (!$id_ok) 788 | { 789 | $this->log_state('Not a valid message id: ' . $msg_id); 790 | } 791 | return $id_ok; 792 | } 793 | 794 | } 795 | // EOF 796 | --------------------------------------------------------------------------------