├── .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 |
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.
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.
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.
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');
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;
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;
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.
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.
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.
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.
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 |
This class connects to IMAP or POP servers and tells you if there are messages waiting.
54 |
55 |
Features:
56 |
57 |
58 |
Connect to IMAP and POP servers.
59 |
Find out if connection was established.
60 |
Find out if messages are waiting.
61 |
Close connection.
62 |
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 |
Config
108 |
Default Value
109 |
Options
110 |
Description
111 |
112 |
113 |
login
114 |
NULL*
115 |
any valid login string
116 |
IMAP or POP server login name.
117 |
118 |
119 |
pass
120 |
NULL*
121 |
any valid password string
122 |
IMAP or POP server password.
123 |
124 |
125 |
host
126 |
NULL*
127 |
the fully-qualified domain name for a mailserver
128 |
e.g., mail.example.com
129 |
130 |
131 |
port
132 |
NULL*
133 |
the port number, typically 110, 143, 443, 993, etc...
Change these to fit your server settings. For instance, some servers require SSL and Certificates and some don't.
141 |
142 |
143 |
mailbox
144 |
NULL
145 |
any valid mailbox name (used only for IMAP, not POP)
146 |
POP servers don't use a mailbox name, use 'INBOX' for IMAP or another mailbox name in the IMAP account
147 |
148 |
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.
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).
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
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
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.
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 |
246 |
PHP 5.2+
247 |
IMAP extension
248 |
SSL support (if you want to use a IMAP or POP server over SSL - recommended)
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.
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.
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.
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 |
60 |
Everything the Peeker Connect class can do, plus...
61 |
Count the number of messages waiting.
62 |
Get all email headers from one message into one object.
63 |
Decode MIME encoded email headers.
64 |
Set IMAP search parameters and get list of message IDs matching the search.
65 |
Do not disturb the read state of the message (good for gMail POP).
66 |
Event System: Detector-Callback circuits in the email acquisition loop.
67 |
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):
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 |
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.
The Peeker class can automatically decode individual MIME-encoded header fields but does not do so unless you
use the get_message() function or...
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.
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.
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:
you use the get_message() function or...
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.
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.
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 |
--------------------------------------------------------------------------------