Version Description
(2016-10-17) * General release
Download this release
Release Info
Developer | WayneAllen |
Plugin | Postie |
Version | 1.8.4 |
Comparing to | |
See all releases |
Code changes from version 1.7.32 to 1.8.4
- config_form_attachments.php +2 -2
- config_form_image.php +3 -3
- config_form_message.php +3 -1
- config_form_server.php +20 -14
- docs/Changes.txt +32 -0
- docs/Postie.txt +3 -3
- docs/TODO.txt +22 -20
- icons/metro/default-256.png +0 -0
- icons/metro/default-32.png +0 -0
- icons/metro/default-48.png +0 -0
- icons/metro/default-64.png +0 -0
- icons/metro/doc-256.png +0 -0
- icons/metro/doc-32.png +0 -0
- icons/metro/doc-48.png +0 -0
- icons/metro/doc-64.png +0 -0
- icons/metro/pdf-256.png +0 -0
- icons/metro/pdf-32.png +0 -0
- icons/metro/pdf-48.png +0 -0
- icons/metro/pdf-64.png +0 -0
- icons/metro/xls-256.png +0 -0
- icons/metro/xls-32.png +0 -0
- icons/metro/xls-48.png +0 -0
- icons/metro/xls-64.png +0 -0
- lib/fConnectivityException.php +41 -0
- lib/fCore.php +1198 -0
- lib/fEmail.php +1805 -0
- lib/fEnvironmentException.php +41 -0
- lib/fException.php +593 -0
- lib/fExpectedException.php +41 -0
- lib/fMailbox.php +1086 -0
- lib/fProgrammerException.php +41 -0
- lib/fUnexpectedException.php +55 -0
- lib/fValidationException.php +211 -0
- lib/pConnection.php +45 -0
- lib/pCurlConnection.php +130 -0
- lib/pImapMailServer.php +189 -0
- lib/pMailServer.php +112 -0
- lib/pPop3MailServer.php +109 -0
- lib/pSocketConnection.php +229 -0
- mimedecode.php +0 -985
- postie-functions.php +694 -1142
- postie.php +34 -27
- readme.txt +35 -3
config_form_attachments.php
CHANGED
@@ -10,7 +10,7 @@
|
|
10 |
value="<?php echo esc_attr($icon_set) ?>" />
|
11 |
|
12 |
<?php
|
13 |
-
$icon_sets = array('silver', 'black', 'white', 'custom', 'none');
|
14 |
$icon_sizes = array(32, 48, 64);
|
15 |
?>
|
16 |
<select name='icon_set_select' id='icon_set_select' onchange="changeIconSet(this);" >
|
@@ -63,7 +63,7 @@
|
|
63 |
value="<?php echo esc_attr($selected_generaltemplate) ?>" />
|
64 |
<select name='generaltemplateselect' id='generaltemplateselect'
|
65 |
onchange="changeStyle('generalTemplatePreview', 'postie-settings-generaltemplate',
|
66 |
-
|
67 |
<?php
|
68 |
include(POSTIE_ROOT . '/templates/general_template.php');
|
69 |
$styleOptions = $generalTemplates;
|
10 |
value="<?php echo esc_attr($icon_set) ?>" />
|
11 |
|
12 |
<?php
|
13 |
+
$icon_sets = array('silver', 'black', 'white', 'metro', 'custom', 'none');
|
14 |
$icon_sizes = array(32, 48, 64);
|
15 |
?>
|
16 |
<select name='icon_set_select' id='icon_set_select' onchange="changeIconSet(this);" >
|
63 |
value="<?php echo esc_attr($selected_generaltemplate) ?>" />
|
64 |
<select name='generaltemplateselect' id='generaltemplateselect'
|
65 |
onchange="changeStyle('generalTemplatePreview', 'postie-settings-generaltemplate',
|
66 |
+
'generaltemplateselect', 'postie-settings-selected_generaltemplate', 'interesting_document.doc', false);" >
|
67 |
<?php
|
68 |
include(POSTIE_ROOT . '/templates/general_template.php');
|
69 |
$styleOptions = $generalTemplates;
|
config_form_image.php
CHANGED
@@ -3,10 +3,10 @@
|
|
3 |
|
4 |
<?php
|
5 |
echo BuildBooleanSelect(__("Use First Image as Featured Image", 'postie'), "postie-settings[featured_image]", $featured_image, __("If any images are attached, the first one will be the featured image for the post", 'postie'));
|
6 |
-
echo BuildBooleanSelect(__("Include Featured Image in Post", 'postie'), "postie-settings[include_featured_image]", $include_featured_image, __("Should the featured image be included in the post.
|
7 |
-
echo BuildBooleanSelect(__("Automatically insert image gallery", 'postie'), "postie-settings[auto_gallery]", $auto_gallery, __("If any images are attached, they will automatically be inserted as a gallery", 'postie'));
|
8 |
echo BuildSelect(__("Gallery Link Type", 'postie'), "postie-settings[auto_gallery_link]", $auto_gallery_link, array('Default', 'Post', 'File', 'None'), "Select the type of link the gallery should use");
|
9 |
-
echo BuildBooleanSelect(__("Image Location", 'postie'), "postie-settings[images_append]", $images_append, __("Location of attachments if using 'plain' format. Before or After content.", 'postie'), array('After', 'Before'));
|
10 |
echo BuildBooleanSelect(__("Generate Thumbnails", 'postie'), "postie-settings[generate_thumbnails]", $generate_thumbnails, __("Some hosts crash during thumbnail generation. Set this to 'No' if you have this issue", 'postie'));
|
11 |
echo BuildBooleanSelect(__("Start Image Count At", 'postie'), "postie-settings[start_image_count_at_zero]", $start_image_count_at_zero, __('For use if using "Image Place Holder Tag" below.', 'postie'), array('Start at 0', 'Start at 1'));
|
12 |
?>
|
3 |
|
4 |
<?php
|
5 |
echo BuildBooleanSelect(__("Use First Image as Featured Image", 'postie'), "postie-settings[featured_image]", $featured_image, __("If any images are attached, the first one will be the featured image for the post", 'postie'));
|
6 |
+
echo BuildBooleanSelect(__("Include Featured Image in Post", 'postie'), "postie-settings[include_featured_image]", $include_featured_image, __("Should the featured image be included in the post.", 'postie'));
|
7 |
+
echo BuildBooleanSelect(__("Automatically insert image gallery", 'postie'), "postie-settings[auto_gallery]", $auto_gallery, __("If any images are attached, they will automatically be inserted as a gallery. If the 'Preferred Text Type' is 'HTML' this will add a galery in addition to the images in the email. Not recommended.", 'postie'));
|
8 |
echo BuildSelect(__("Gallery Link Type", 'postie'), "postie-settings[auto_gallery_link]", $auto_gallery_link, array('Default', 'Post', 'File', 'None'), "Select the type of link the gallery should use");
|
9 |
+
echo BuildBooleanSelect(__("Image Location", 'postie'), "postie-settings[images_append]", $images_append, __("Location of attachments if using 'plain' format. Before or After content. For 'html' content this will only affect attachments that are not inline.", 'postie'), array('After', 'Before'));
|
10 |
echo BuildBooleanSelect(__("Generate Thumbnails", 'postie'), "postie-settings[generate_thumbnails]", $generate_thumbnails, __("Some hosts crash during thumbnail generation. Set this to 'No' if you have this issue", 'postie'));
|
11 |
echo BuildBooleanSelect(__("Start Image Count At", 'postie'), "postie-settings[start_image_count_at_zero]", $start_image_count_at_zero, __('For use if using "Image Place Holder Tag" below.', 'postie'), array('Start at 0', 'Start at 1'));
|
12 |
?>
|
config_form_message.php
CHANGED
@@ -9,6 +9,9 @@
|
|
9 |
</select>
|
10 |
</td>
|
11 |
</tr>
|
|
|
|
|
|
|
12 |
<tr valign = "top">
|
13 |
<th scope = "row"><?php _e('Default category', 'postie') ?></th>
|
14 |
<td>
|
@@ -158,7 +161,6 @@
|
|
158 |
<p class='description'>UTF-8 <?php _e("should handle ISO-8859-1 as well", 'postie'); ?></p>
|
159 |
</td>
|
160 |
</tr>
|
161 |
-
<?php echo BuildBooleanSelect(__("Decode Quoted Printable Data", 'postie'), "postie-settings[message_dequote]", $message_dequote); ?>
|
162 |
<?php echo BuildBooleanSelect(__("Drop The Signature From Mail", 'postie'), "postie-settings[drop_signature]", $drop_signature, __("Really only works with 'plain' format.")); ?>
|
163 |
<?php echo BuildTextArea(__("Signature Patterns", 'postie'), "postie-settings[sig_pattern_list]", $sig_pattern_list, __("Really only works with 'plain' format. Put each pattern on a separate line. Patterns are <a href='http://regex101.com/' target='_blank'>regular expressions</a> and are put inside /^{pattern}$/i", 'postie')); ?>
|
164 |
</table>
|
9 |
</select>
|
10 |
</td>
|
11 |
</tr>
|
12 |
+
<?php
|
13 |
+
echo BuildBooleanSelect(__("Text fallback", 'postie'), "postie-settings[prefer_text_convert]", $prefer_text_convert, __("Use plain if html is missing and vice versa.", 'postie'));
|
14 |
+
?>
|
15 |
<tr valign = "top">
|
16 |
<th scope = "row"><?php _e('Default category', 'postie') ?></th>
|
17 |
<td>
|
161 |
<p class='description'>UTF-8 <?php _e("should handle ISO-8859-1 as well", 'postie'); ?></p>
|
162 |
</td>
|
163 |
</tr>
|
|
|
164 |
<?php echo BuildBooleanSelect(__("Drop The Signature From Mail", 'postie'), "postie-settings[drop_signature]", $drop_signature, __("Really only works with 'plain' format.")); ?>
|
165 |
<?php echo BuildTextArea(__("Signature Patterns", 'postie'), "postie-settings[sig_pattern_list]", $sig_pattern_list, __("Really only works with 'plain' format. Put each pattern on a separate line. Patterns are <a href='http://regex101.com/' target='_blank'>regular expressions</a> and are put inside /^{pattern}$/i", 'postie')); ?>
|
166 |
</table>
|
config_form_server.php
CHANGED
@@ -1,35 +1,41 @@
|
|
1 |
<div id="simpleTabs-content-1" class="simpleTabs-content">
|
2 |
|
3 |
<table class='form-table'>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
|
5 |
<tr>
|
6 |
<th scope="row"><lable for="postie-settings-input_protocol"><?php _e('Mail Protocol', 'postie') ?></lable></th>
|
7 |
<td>
|
8 |
<select name='postie-settings[input_protocol]' id='postie-settings-input_protocol'>
|
9 |
<option value="pop3" <?php echo (($input_protocol == "pop3") ? " selected='selected' " : "") ?>>POP3</option>
|
10 |
-
<?php
|
11 |
-
|
12 |
-
|
13 |
-
<option value="imap-ssl" <?php echo ($input_protocol == "imap-ssl") ? "selected='selected' " : "" ?>>IMAP-SSL</option>
|
14 |
-
<?php endif; ?>
|
15 |
</select>
|
16 |
-
|
17 |
-
<span class="recommendation">IMAP/IMAP-SSL/POP3-SSL unavailable</span>
|
18 |
-
<?php endif; ?>
|
19 |
</td>
|
20 |
</tr>
|
21 |
|
22 |
-
<?php echo BuildBooleanSelect(__("Use Transport Layer Security (TLS)", 'postie'), 'postie-settings[email_tls]', $email_tls, __("Choose Yes if your server requires TLS", 'postie')); ?>
|
23 |
|
24 |
<tr>
|
25 |
<th scope="row"><label for="postie-settings-mail_server_port"><?php _e('Port', 'postie') ?></label></th>
|
26 |
<td valign="top">
|
27 |
<input name='postie-settings[mail_server_port]' style="width: 70px;" type="number" min="0" id='postie-settings-mail_server_port' value="<?php echo esc_attr($mail_server_port); ?>" size="6" />
|
28 |
<p class='description'><?php _e("Standard Ports:", 'postie'); ?><br />
|
29 |
-
<?php _e("POP3", 'postie');
|
30 |
-
<?php _e("IMAP", 'postie');
|
31 |
-
<?php _e("IMAP-SSL", 'postie');
|
32 |
-
<?php _e("POP3-SSL", 'postie');
|
33 |
</p>
|
34 |
</td>
|
35 |
</tr>
|
@@ -150,7 +156,7 @@
|
|
150 |
</td>
|
151 |
</tr>
|
152 |
<?php echo BuildBooleanSelect(__("Delete email after posting", 'postie'), 'postie-settings[delete_mail_after_processing]', $delete_mail_after_processing, __("Only set to no for testing purposes", 'postie')); ?>
|
153 |
-
<?php echo BuildBooleanSelect(__("Ignore mail state", 'postie'), 'postie-settings[ignore_mail_state]', $ignore_mail_state, __("Ignore whether the mails is 'read' or 'unread' If 'No' then only unread messages are processed.", 'postie')); ?>
|
154 |
|
155 |
<?php echo BuildBooleanSelect(__("Enable Error Logging", 'postie'), 'postie-settings[postie_log_error]', $postie_log_error, __("Log error messages to the web server error log.", 'postie')); ?>
|
156 |
<?php echo BuildBooleanSelect(__("Enable Debug Logging", 'postie'), 'postie-settings[postie_log_debug]', $postie_log_debug, __("Log debug messages to the web server error log.", 'postie')); ?>
|
1 |
<div id="simpleTabs-content-1" class="simpleTabs-content">
|
2 |
|
3 |
<table class='form-table'>
|
4 |
+
<tr>
|
5 |
+
<th scope="row"><lable for="postie-settings-input_connection"><?php _e('Connection', 'postie') ?></lable></th>
|
6 |
+
<td>
|
7 |
+
<select name='postie-settings[input_connection]' id='postie-settings-input_connection'>
|
8 |
+
<option value="sockets" <?php echo (($input_connection == "socket") ? " selected='selected' " : "") ?>>Sockets</option>
|
9 |
+
<option value="curl" <?php echo ($input_connection == "curl") ? "selected='selected' " : "" ?>>cURL</option>
|
10 |
+
</select>
|
11 |
+
<p class='description'><?php _e("Sockets is prefered, but doesn't work with some hosts.", 'postie'); ?></p>
|
12 |
+
</td>
|
13 |
+
</tr>
|
14 |
|
15 |
<tr>
|
16 |
<th scope="row"><lable for="postie-settings-input_protocol"><?php _e('Mail Protocol', 'postie') ?></lable></th>
|
17 |
<td>
|
18 |
<select name='postie-settings[input_protocol]' id='postie-settings-input_protocol'>
|
19 |
<option value="pop3" <?php echo (($input_protocol == "pop3") ? " selected='selected' " : "") ?>>POP3</option>
|
20 |
+
<option value="imap" <?php echo ($input_protocol == "imap") ? "selected='selected' " : "" ?>>IMAP</option>
|
21 |
+
<option value="pop3-ssl" <?php echo ($input_protocol == "pop3-ssl") ? "selected='selected' " : "" ?>>POP3-SSL</option>
|
22 |
+
<option value="imap-ssl" <?php echo ($input_protocol == "imap-ssl") ? "selected='selected' " : "" ?>>IMAP-SSL</option>
|
|
|
|
|
23 |
</select>
|
24 |
+
|
|
|
|
|
25 |
</td>
|
26 |
</tr>
|
27 |
|
28 |
+
<?php //echo BuildBooleanSelect(__("Use Transport Layer Security (TLS)", 'postie'), 'postie-settings[email_tls]', $email_tls, __("Choose Yes if your server requires TLS", 'postie')); ?>
|
29 |
|
30 |
<tr>
|
31 |
<th scope="row"><label for="postie-settings-mail_server_port"><?php _e('Port', 'postie') ?></label></th>
|
32 |
<td valign="top">
|
33 |
<input name='postie-settings[mail_server_port]' style="width: 70px;" type="number" min="0" id='postie-settings-mail_server_port' value="<?php echo esc_attr($mail_server_port); ?>" size="6" />
|
34 |
<p class='description'><?php _e("Standard Ports:", 'postie'); ?><br />
|
35 |
+
<?php _e("POP3", 'postie'); ?>: 110<br />
|
36 |
+
<?php _e("IMAP", 'postie'); ?>: 143<br />
|
37 |
+
<?php _e("IMAP-SSL", 'postie'); ?>: 993 <br />
|
38 |
+
<?php _e("POP3-SSL", 'postie'); ?>: 995 <br />
|
39 |
</p>
|
40 |
</td>
|
41 |
</tr>
|
156 |
</td>
|
157 |
</tr>
|
158 |
<?php echo BuildBooleanSelect(__("Delete email after posting", 'postie'), 'postie-settings[delete_mail_after_processing]', $delete_mail_after_processing, __("Only set to no for testing purposes", 'postie')); ?>
|
159 |
+
<?php //echo BuildBooleanSelect(__("Ignore mail state", 'postie'), 'postie-settings[ignore_mail_state]', $ignore_mail_state, __("Ignore whether the mails is 'read' or 'unread' If 'No' then only unread messages are processed. IMAP only", 'postie')); ?>
|
160 |
|
161 |
<?php echo BuildBooleanSelect(__("Enable Error Logging", 'postie'), 'postie-settings[postie_log_error]', $postie_log_error, __("Log error messages to the web server error log.", 'postie')); ?>
|
162 |
<?php echo BuildBooleanSelect(__("Enable Debug Logging", 'postie'), 'postie-settings[postie_log_debug]', $postie_log_debug, __("Log debug messages to the web server error log.", 'postie')); ?>
|
docs/Changes.txt
CHANGED
@@ -1,5 +1,10 @@
|
|
1 |
== Upgrade Notice ==
|
2 |
|
|
|
|
|
|
|
|
|
|
|
3 |
= 1.6.0 =
|
4 |
Remote cron jobs need to update the URL used to kick off a manual email check. The new URL is http://<mysite>/?postie=get-mail
|
5 |
Accessing http://<mysite>/wp-content/plugins/postie/get_mail.php will now receive a 403 error and a message stating what the new URL should be.
|
@@ -27,6 +32,33 @@ All script, style and body tags are stripped from html emails.
|
|
27 |
Attachments are now processed in the order they were attached.
|
28 |
|
29 |
== CHANGELOG ==
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
= 1.7.32 (2016-04-15) =
|
31 |
* Deal with incorrectly formatted date headers
|
32 |
|
1 |
== Upgrade Notice ==
|
2 |
|
3 |
+
= 1.8.0 =
|
4 |
+
The php-imap library has been replaced with logic based on Flourish fMailbox et al, there are some differences in the structure of the mail header array. This affects the
|
5 |
+
postie_filter_email3 and postie_post_before filters.
|
6 |
+
See http://flourishlib.com/docs/fMailbox
|
7 |
+
|
8 |
= 1.6.0 =
|
9 |
Remote cron jobs need to update the URL used to kick off a manual email check. The new URL is http://<mysite>/?postie=get-mail
|
10 |
Accessing http://<mysite>/wp-content/plugins/postie/get_mail.php will now receive a 403 error and a message stating what the new URL should be.
|
32 |
Attachments are now processed in the order they were attached.
|
33 |
|
34 |
== CHANGELOG ==
|
35 |
+
= 1.8.4 (2016-10-17)
|
36 |
+
* General release
|
37 |
+
|
38 |
+
= 1.8.3 (beta 4)
|
39 |
+
* Refactor attachment handling
|
40 |
+
* Gallery shortcode handling is now correct when there are both images and non-images.
|
41 |
+
|
42 |
+
= 1.8.2 (beta 3)
|
43 |
+
* New icon set thanks to Chris Lacey
|
44 |
+
* php-imap replaced by cURL and Socket connection - work sponsored by xsell.net
|
45 |
+
* TLS automatically detected, setting removed
|
46 |
+
* Ignore mail state no longer supported, setting removed
|
47 |
+
* Header array in filter_email3 and postie_post_before filters have changed format
|
48 |
+
* Fixed paragraph detection in plain text when removing newlines
|
49 |
+
* Removed old partially functioning forward detection logic
|
50 |
+
* Transform "[cid:xxx-xx-xx]" references gmail adds to image references.
|
51 |
+
* Add new filter: postie_post_pre that runs after the email is parsed, but before any changes are made.
|
52 |
+
* Fixed improper decoding of encoded headers when multiple 'encoded-words' are present.
|
53 |
+
* Fix to allow multiple #img# reference to the same image.
|
54 |
+
* The default video 1 template is now 'vshortcode'
|
55 |
+
* New option: Text fallback. Falls back to plain if html is blank and vice versa.
|
56 |
+
* Support removing featured image from html
|
57 |
+
|
58 |
+
= 1.8.1 (beta 2)
|
59 |
+
|
60 |
+
= 1.8.0 (beta 1)
|
61 |
+
|
62 |
= 1.7.32 (2016-04-15) =
|
63 |
* Deal with incorrectly formatted date headers
|
64 |
|
docs/Postie.txt
CHANGED
@@ -5,8 +5,8 @@ Author URI: http://allens-home.com/
|
|
5 |
Plugin URI: http://PostiePlugin.com/
|
6 |
Tags: e-mail, email, post-by-email
|
7 |
Requires at least: 3.3.0
|
8 |
-
Tested up to: 4.
|
9 |
-
Stable tag: 1.
|
10 |
License: GPLv2 or later
|
11 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
12 |
|
@@ -16,7 +16,7 @@ Postie allows you to create posts via email, including many advanced features no
|
|
16 |
Postie offers many advanced features for creating posts by email,
|
17 |
including the ability to assign categories by name, included pictures and
|
18 |
videos, and automatically strip off signatures. It also has support for both
|
19 |
-
IMAP and POP3, with the option for ssl with both.
|
20 |
[other notes](other_notes) page
|
21 |
|
22 |
More info at http://PostiePlugin.com/
|
5 |
Plugin URI: http://PostiePlugin.com/
|
6 |
Tags: e-mail, email, post-by-email
|
7 |
Requires at least: 3.3.0
|
8 |
+
Tested up to: 4.6.1
|
9 |
+
Stable tag: 1.8.4
|
10 |
License: GPLv2 or later
|
11 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
12 |
|
16 |
Postie offers many advanced features for creating posts by email,
|
17 |
including the ability to assign categories by name, included pictures and
|
18 |
videos, and automatically strip off signatures. It also has support for both
|
19 |
+
IMAP and POP3, with the option for ssl with both. For usage notes, see the
|
20 |
[other notes](other_notes) page
|
21 |
|
22 |
More info at http://PostiePlugin.com/
|
docs/TODO.txt
CHANGED
@@ -1,26 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
1 |
AddOn Ideas
|
|
|
|
|
|
|
|
|
2 |
CSS Inliner - use one of the online services to convert class styled html to inline styles. https://www.google.com/search?q=css+inline+tool&oq=css+inline+tool
|
|
|
3 |
Automatic updates for AddOns:
|
4 |
https://github.com/seedprod/sellwp-updater
|
5 |
http://code.tutsplus.com/series/create-a-license-controlled-theme-and-plugin-update-system--cms-760
|
6 |
|
|
|
|
|
|
|
7 |
Other
|
8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
check multiple email accounts
|
10 |
allow other roles access to manual check like "Roles That Can Post"
|
11 |
Add setting to not remove category text from subject/title
|
12 |
Test server port combination by opening a socket connection to see if any service responds. (via ajax call)
|
13 |
-
gallery logic does not handle both images and non-images
|
14 |
send email notice when attachments are rejected.
|
15 |
For IMAP mailboxes allow the user to choose the folder.
|
16 |
-
Preferred text type: just html, just plain, html fallback to plain
|
17 |
Check to see if post already exists. Store hash in custom field.
|
18 |
Change successful post message to take post status into account (i.e. draft really isn't posted - possible link to publish draft?)
|
19 |
Verify that WP is honoring the wp_set_current_user() call. I.e. if the user is not an "author" role can they publish?
|
20 |
-
Option to not process forwarded emails (getPostAuthorDetails)
|
21 |
Add Message-ID header value to custom field (postie_message_id?) to both posts and comments.
|
22 |
Use In-Reply-To header value as a better way to detect replies
|
23 |
-
Verify that {TITLE} is doing the right thing.
|
24 |
automatically create category if it doesn't exist. new option to allow this feature?
|
25 |
Comment not being created when subject contains category command []. I.e. listserv subject lines
|
26 |
date: tag is being detected in body when not wanted
|
@@ -29,11 +45,11 @@ dynamically determine video size (height/width) - https://code.google.com/p/phpv
|
|
29 |
plugin conflict - Image Rotation Fixer/Image Rotation Repair
|
30 |
readme tips http://wp.smashingmagazine.com/2011/11/23/improve-wordpress-plugins-readme-txt/
|
31 |
review http://codex.wordpress.org/Settings_API
|
|
|
32 |
use wordpress plugin template
|
33 |
boilerplate http://wppb.io/
|
34 |
starter-plugin https://github.com/mattyza/starter-plugin
|
35 |
WordPress-Plugin-Template https://github.com/hlashbrooke/WordPress-Plugin-Template
|
36 |
-
configurable message for "post confirmation" - variable substitution
|
37 |
|
38 |
Hooks
|
39 |
add hooks for post meta data change
|
@@ -82,17 +98,3 @@ There are filters that modify
|
|
82 |
meta data (tags, categories, date)
|
83 |
content (start, end, newlines, linkifying)
|
84 |
|
85 |
-
=========
|
86 |
-
Postie 2.0
|
87 |
-
|
88 |
-
http://www.yaconiello.com/blog/how-to-write-wordpress-plugin/
|
89 |
-
|
90 |
-
setting to turn off individual tags
|
91 |
-
process single email at a time and don't delete unless successful
|
92 |
-
fix postie settings http://alisothegeek.com/2011/01/wordpress-settings-api-tutorial-1/
|
93 |
-
Make sure all failures are sent to admin (option?) failed attachments, etc.
|
94 |
-
Option to send logs to support
|
95 |
-
Enhance #img# to specify the featured image
|
96 |
-
"yoast" style admin sidebar - see clicky by yoast
|
97 |
-
replace native imap MIME parser with flourish - http://flourishlib.com/docs/fMailbox or Hoard http://dev.horde.org/imap_client/install.php - see "Post By Email" plugin https://wordpress.org/plugins/post-by-email/
|
98 |
-
provide a location for custom icons. update docs about location.
|
1 |
+
IMAP-replacement
|
2 |
+
Announcement on postieplugin.com & forum
|
3 |
+
check other addons for compatibility
|
4 |
+
notify affected addon owners of updated addons
|
5 |
+
|
6 |
AddOn Ideas
|
7 |
+
Header/footer stripper - user provides "selector" (xpath) to identify header/footer and element is removed safely for html
|
8 |
+
selector = "div.footer" (div with the class "footer")
|
9 |
+
May need to remove parent (or granparent of element)
|
10 |
+
How to help non-techies build the selector?
|
11 |
CSS Inliner - use one of the online services to convert class styled html to inline styles. https://www.google.com/search?q=css+inline+tool&oq=css+inline+tool
|
12 |
+
|
13 |
Automatic updates for AddOns:
|
14 |
https://github.com/seedprod/sellwp-updater
|
15 |
http://code.tutsplus.com/series/create-a-license-controlled-theme-and-plugin-update-system--cms-760
|
16 |
|
17 |
+
Bug
|
18 |
+
#img# caption feature not working
|
19 |
+
|
20 |
Other
|
21 |
+
Process single email at a time and don't delete unless successful
|
22 |
+
Configurable message for "post confirmation" - variable substitution
|
23 |
+
Add default comment status (like post status) wp_set_comment_status
|
24 |
+
Option to send logs to support
|
25 |
+
provide a location for custom icons. update docs about location.
|
26 |
+
Make sure all failures are sent to admin (option?) failed attachments, etc.
|
27 |
+
"yoast" style admin sidebar - see clicky by yoast
|
28 |
+
fix postie settings http://alisothegeek.com/2011/01/wordpress-settings-api-tutorial-1/
|
29 |
check multiple email accounts
|
30 |
allow other roles access to manual check like "Roles That Can Post"
|
31 |
Add setting to not remove category text from subject/title
|
32 |
Test server port combination by opening a socket connection to see if any service responds. (via ajax call)
|
|
|
33 |
send email notice when attachments are rejected.
|
34 |
For IMAP mailboxes allow the user to choose the folder.
|
|
|
35 |
Check to see if post already exists. Store hash in custom field.
|
36 |
Change successful post message to take post status into account (i.e. draft really isn't posted - possible link to publish draft?)
|
37 |
Verify that WP is honoring the wp_set_current_user() call. I.e. if the user is not an "author" role can they publish?
|
|
|
38 |
Add Message-ID header value to custom field (postie_message_id?) to both posts and comments.
|
39 |
Use In-Reply-To header value as a better way to detect replies
|
|
|
40 |
automatically create category if it doesn't exist. new option to allow this feature?
|
41 |
Comment not being created when subject contains category command []. I.e. listserv subject lines
|
42 |
date: tag is being detected in body when not wanted
|
45 |
plugin conflict - Image Rotation Fixer/Image Rotation Repair
|
46 |
readme tips http://wp.smashingmagazine.com/2011/11/23/improve-wordpress-plugins-readme-txt/
|
47 |
review http://codex.wordpress.org/Settings_API
|
48 |
+
plugin structure http://www.yaconiello.com/blog/how-to-write-wordpress-plugin/
|
49 |
use wordpress plugin template
|
50 |
boilerplate http://wppb.io/
|
51 |
starter-plugin https://github.com/mattyza/starter-plugin
|
52 |
WordPress-Plugin-Template https://github.com/hlashbrooke/WordPress-Plugin-Template
|
|
|
53 |
|
54 |
Hooks
|
55 |
add hooks for post meta data change
|
98 |
meta data (tags, categories, date)
|
99 |
content (start, end, newlines, linkifying)
|
100 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
icons/metro/default-256.png
ADDED
Binary file
|
icons/metro/default-32.png
ADDED
Binary file
|
icons/metro/default-48.png
ADDED
Binary file
|
icons/metro/default-64.png
ADDED
Binary file
|
icons/metro/doc-256.png
ADDED
Binary file
|
icons/metro/doc-32.png
ADDED
Binary file
|
icons/metro/doc-48.png
ADDED
Binary file
|
icons/metro/doc-64.png
ADDED
Binary file
|
icons/metro/pdf-256.png
ADDED
Binary file
|
icons/metro/pdf-32.png
ADDED
Binary file
|
icons/metro/pdf-48.png
ADDED
Binary file
|
icons/metro/pdf-64.png
ADDED
Binary file
|
icons/metro/xls-256.png
ADDED
Binary file
|
icons/metro/xls-32.png
ADDED
Binary file
|
icons/metro/xls-48.png
ADDED
Binary file
|
icons/metro/xls-64.png
ADDED
Binary file
|
lib/fConnectivityException.php
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* An exception caused by a connectivity error
|
4 |
+
*
|
5 |
+
* @copyright Copyright (c) 2007-2008 Will Bond
|
6 |
+
* @author Will Bond [wb] <will@flourishlib.com>
|
7 |
+
* @license http://flourishlib.com/license
|
8 |
+
*
|
9 |
+
* @package Flourish
|
10 |
+
* @link http://flourishlib.com/fConnectivityException
|
11 |
+
*
|
12 |
+
* @version 1.0.0b
|
13 |
+
* @changes 1.0.0b The initial implementation [wb, 2007-06-14]
|
14 |
+
*/
|
15 |
+
class fConnectivityException extends fUnexpectedException
|
16 |
+
{
|
17 |
+
}
|
18 |
+
|
19 |
+
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Copyright (c) 2007-2008 Will Bond <will@flourishlib.com>
|
23 |
+
*
|
24 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
25 |
+
* of this software and associated documentation files (the "Software"), to deal
|
26 |
+
* in the Software without restriction, including without limitation the rights
|
27 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
28 |
+
* copies of the Software, and to permit persons to whom the Software is
|
29 |
+
* furnished to do so, subject to the following conditions:
|
30 |
+
*
|
31 |
+
* The above copyright notice and this permission notice shall be included in
|
32 |
+
* all copies or substantial portions of the Software.
|
33 |
+
*
|
34 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
35 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
36 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
37 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
38 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
39 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
40 |
+
* THE SOFTWARE.
|
41 |
+
*/
|
lib/fCore.php
ADDED
@@ -0,0 +1,1198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Provides low-level debugging, error and exception functionality
|
5 |
+
*
|
6 |
+
* @copyright Copyright (c) 2007-2011 Will Bond, others
|
7 |
+
* @author Will Bond [wb] <will@flourishlib.com>
|
8 |
+
* @author Will Bond, iMarc LLC [wb-imarc] <will@imarc.net>
|
9 |
+
* @author Nick Trew [nt]
|
10 |
+
* @author Kevin Hamer [kh] <kevin@imarc.net>
|
11 |
+
* @author Jeff Turcotte [jt] <jeff@imarc.net>
|
12 |
+
* @license http://flourishlib.com/license
|
13 |
+
*
|
14 |
+
* @package Flourish
|
15 |
+
* @link http://flourishlib.com/fCore
|
16 |
+
*
|
17 |
+
* @version 1.0.0b26
|
18 |
+
* @changes 1.0.0b26 Added handle_fatal_errors flag to enableErrorHandling [jt, 2013-06-10]
|
19 |
+
* @changes 1.0.0b25 exposing ->generateContext() [kh, 2012-12-18]
|
20 |
+
* @changes 1.0.0b24 Backwards Compatibility Break - moved ::detectOpcodeCache() to fLoader::hasOpcodeCache() [wb, 2011-08-26]
|
21 |
+
* @changes 1.0.0b23 Backwards Compatibility Break - changed the email subject of error/exception emails to include relevant file info, instead of the timestamp, for better email message threading [wb, 2011-06-20]
|
22 |
+
* @changes 1.0.0b22 Fixed a bug with dumping arrays containing integers [wb, 2011-05-26]
|
23 |
+
* @changes 1.0.0b21 Changed ::startErrorCapture() to allow "stacking" it via multiple calls, fixed a couple of bugs with ::dump() mangling strings in the form `int(1)`, fixed mispelling of `occurred` [wb, 2011-05-09]
|
24 |
+
* @changes 1.0.0b20 Backwards Compatibility Break - Updated ::expose() to not wrap the data in HTML when running via CLI, and instead just append a newline [wb, 2011-02-24]
|
25 |
+
* @changes 1.0.0b19 Added detection of AIX to ::checkOS() [wb, 2011-01-19]
|
26 |
+
* @changes 1.0.0b18 Updated ::expose() to be able to accept multiple parameters [wb, 2011-01-10]
|
27 |
+
* @changes 1.0.0b17 Fixed a bug with ::backtrace() triggering notices when an argument is not UTF-8 [wb, 2010-08-17]
|
28 |
+
* @changes 1.0.0b16 Added the `$types` and `$regex` parameters to ::startErrorCapture() and the `$regex` parameter to ::stopErrorCapture() [wb, 2010-08-09]
|
29 |
+
* @changes 1.0.0b15 Added ::startErrorCapture() and ::stopErrorCapture() [wb, 2010-07-05]
|
30 |
+
* @changes 1.0.0b14 Changed ::enableExceptionHandling() to only call fException::printMessage() when the destination is not `html` and no callback has been defined, added ::configureSMTP() to allow using fSMTP for error and exception emails [wb, 2010-06-04]
|
31 |
+
* @changes 1.0.0b13 Added the `$backtrace` parameter to ::backtrace() [wb, 2010-03-05]
|
32 |
+
* @changes 1.0.0b12 Added ::getDebug() to check for the global debugging flag, added more specific BSD checks to ::checkOS() [wb, 2010-03-02]
|
33 |
+
* @changes 1.0.0b11 Added ::detectOpcodeCache() [nt+wb, 2009-10-06]
|
34 |
+
* @changes 1.0.0b10 Fixed ::expose() to properly display when output includes non-UTF-8 binary data [wb, 2009-06-29]
|
35 |
+
* @changes 1.0.0b9 Added ::disableContext() to remove context info for exception/error handling, tweaked output for exceptions/errors [wb, 2009-06-28]
|
36 |
+
* @changes 1.0.0b8 ::enableErrorHandling() and ::enableExceptionHandling() now accept multiple email addresses, and a much wider range of emails [wb-imarc, 2009-06-01]
|
37 |
+
* @changes 1.0.0b7 ::backtrace() now properly replaces document root with {doc_root} on Windows [wb, 2009-05-02]
|
38 |
+
* @changes 1.0.0b6 Fixed a bug with getting the server name for error messages when running on the command line [wb, 2009-03-11]
|
39 |
+
* @changes 1.0.0b5 Fixed a bug with checking the error/exception destination when a log file is specified [wb, 2009-03-07]
|
40 |
+
* @changes 1.0.0b4 Backwards compatibility break - ::getOS() and ::getPHPVersion() removed, replaced with ::checkOS() and ::checkVersion() [wb, 2009-02-16]
|
41 |
+
* @changes 1.0.0b3 ::handleError() now displays what kind of error occurred as the heading [wb, 2009-02-15]
|
42 |
+
* @changes 1.0.0b2 Added ::registerDebugCallback() [wb, 2009-02-07]
|
43 |
+
* @changes 1.0.0b The initial implementation [wb, 2007-09-25]
|
44 |
+
*/
|
45 |
+
class fCore {
|
46 |
+
|
47 |
+
// The following constants allow for nice looking callbacks to static methods
|
48 |
+
const backtrace = 'fCore::backtrace';
|
49 |
+
const call = 'fCore::call';
|
50 |
+
const callback = 'fCore::callback';
|
51 |
+
const checkOS = 'fCore::checkOS';
|
52 |
+
const checkVersion = 'fCore::checkVersion';
|
53 |
+
const configureSMTP = 'fCore::configureSMTP';
|
54 |
+
const debug = 'fCore::debug';
|
55 |
+
const disableContext = 'fCore::disableContext';
|
56 |
+
const dump = 'fCore::dump';
|
57 |
+
const enableDebugging = 'fCore::enableDebugging';
|
58 |
+
const enableDynamicConstants = 'fCore::enableDynamicConstants';
|
59 |
+
const enableErrorHandling = 'fCore::enableErrorHandling';
|
60 |
+
const enableExceptionHandling = 'fCore::enableExceptionHandling';
|
61 |
+
const expose = 'fCore::expose';
|
62 |
+
const getDebug = 'fCore::getDebug';
|
63 |
+
const handleError = 'fCore::handleError';
|
64 |
+
const handleFatalError = 'fCore::handleFatalError';
|
65 |
+
const handleException = 'fCore::handleException';
|
66 |
+
const registerDebugCallback = 'fCore::registerDebugCallback';
|
67 |
+
const reset = 'fCore::reset';
|
68 |
+
const sendMessagesOnShutdown = 'fCore::sendMessagesOnShutdown';
|
69 |
+
const startErrorCapture = 'fCore::startErrorCapture';
|
70 |
+
const stopErrorCapture = 'fCore::stopErrorCapture';
|
71 |
+
|
72 |
+
/**
|
73 |
+
* The nesting level of error capturing
|
74 |
+
*
|
75 |
+
* @var integer
|
76 |
+
*/
|
77 |
+
static private $captured_error_level = 0;
|
78 |
+
|
79 |
+
/**
|
80 |
+
* A stack of regex to match errors to capture, one string per level
|
81 |
+
*
|
82 |
+
* @var array
|
83 |
+
*/
|
84 |
+
static private $captured_error_regex = array();
|
85 |
+
|
86 |
+
/**
|
87 |
+
* A stack of the types of errors to capture, one integer per level
|
88 |
+
*
|
89 |
+
* @var array
|
90 |
+
*/
|
91 |
+
static private $captured_error_types = array();
|
92 |
+
|
93 |
+
/**
|
94 |
+
* A stack of arrays of errors that have been captured, one array per level
|
95 |
+
*
|
96 |
+
* @var array
|
97 |
+
*/
|
98 |
+
static private $captured_errors = array();
|
99 |
+
|
100 |
+
/**
|
101 |
+
* A stack of the previous error handler, one callback per level
|
102 |
+
*
|
103 |
+
* @var array
|
104 |
+
*/
|
105 |
+
static private $captured_errors_previous_handler = array();
|
106 |
+
|
107 |
+
/**
|
108 |
+
* If the context info has been shown
|
109 |
+
*
|
110 |
+
* @var boolean
|
111 |
+
*/
|
112 |
+
static private $context_shown = FALSE;
|
113 |
+
|
114 |
+
/**
|
115 |
+
* If global debugging is enabled
|
116 |
+
*
|
117 |
+
* @var boolean
|
118 |
+
*/
|
119 |
+
static private $debug = NULL;
|
120 |
+
|
121 |
+
/**
|
122 |
+
* A callback to pass debug messages to
|
123 |
+
*
|
124 |
+
* @var callback
|
125 |
+
*/
|
126 |
+
static private $debug_callback = NULL;
|
127 |
+
|
128 |
+
/**
|
129 |
+
* If dynamic constants should be created
|
130 |
+
*
|
131 |
+
* @var boolean
|
132 |
+
*/
|
133 |
+
static private $dynamic_constants = FALSE;
|
134 |
+
|
135 |
+
/**
|
136 |
+
* Error destination
|
137 |
+
*
|
138 |
+
* @var string
|
139 |
+
*/
|
140 |
+
static private $error_destination = 'html';
|
141 |
+
|
142 |
+
/**
|
143 |
+
* An array of errors to be send to the destination upon page completion
|
144 |
+
*
|
145 |
+
* @var array
|
146 |
+
*/
|
147 |
+
static private $error_message_queue = array();
|
148 |
+
|
149 |
+
/**
|
150 |
+
* Exception destination
|
151 |
+
*
|
152 |
+
* @var string
|
153 |
+
*/
|
154 |
+
static private $exception_destination = 'html';
|
155 |
+
|
156 |
+
/**
|
157 |
+
* Exception handler callback
|
158 |
+
*
|
159 |
+
* @var mixed
|
160 |
+
*/
|
161 |
+
static private $exception_handler_callback = NULL;
|
162 |
+
|
163 |
+
/**
|
164 |
+
* Exception handler callback parameters
|
165 |
+
*
|
166 |
+
* @var array
|
167 |
+
*/
|
168 |
+
static private $exception_handler_parameters = array();
|
169 |
+
|
170 |
+
/**
|
171 |
+
* The message generated by the uncaught exception
|
172 |
+
*
|
173 |
+
* @var string
|
174 |
+
*/
|
175 |
+
static private $exception_message = NULL;
|
176 |
+
|
177 |
+
/**
|
178 |
+
* If this class is handling errors
|
179 |
+
*
|
180 |
+
* @var boolean
|
181 |
+
*/
|
182 |
+
static private $handles_errors = FALSE;
|
183 |
+
|
184 |
+
/**
|
185 |
+
* If this class is handling exceptions
|
186 |
+
*
|
187 |
+
* @var boolean
|
188 |
+
*/
|
189 |
+
static private $handles_exceptions = FALSE;
|
190 |
+
|
191 |
+
/**
|
192 |
+
* If the context info should be shown with errors/exceptions
|
193 |
+
*
|
194 |
+
* @var boolean
|
195 |
+
*/
|
196 |
+
static private $show_context = TRUE;
|
197 |
+
|
198 |
+
/**
|
199 |
+
* An array of the most significant lines from error and exception backtraces
|
200 |
+
*
|
201 |
+
* @var array
|
202 |
+
*/
|
203 |
+
static private $significant_error_lines = array();
|
204 |
+
|
205 |
+
/**
|
206 |
+
* An SMTP connection for sending error and exception emails
|
207 |
+
*
|
208 |
+
* @var fSMTP
|
209 |
+
*/
|
210 |
+
static private $smtp_connection = NULL;
|
211 |
+
|
212 |
+
/**
|
213 |
+
* The email address to send error emails from
|
214 |
+
*
|
215 |
+
* @var string
|
216 |
+
*/
|
217 |
+
static private $smtp_from_email = NULL;
|
218 |
+
|
219 |
+
/**
|
220 |
+
* Creates a nicely formatted backtrace to the the point where this method is called
|
221 |
+
*
|
222 |
+
* @param integer $remove_lines The number of trailing lines to remove from the backtrace
|
223 |
+
* @param array $backtrace A backtrace from [http://php.net/backtrace `debug_backtrace()`] to format - this is not usually required or desired
|
224 |
+
* @return string The formatted backtrace
|
225 |
+
*/
|
226 |
+
static public function backtrace($remove_lines = 0, $backtrace = NULL) {
|
227 |
+
if ($remove_lines !== NULL && !is_numeric($remove_lines)) {
|
228 |
+
$remove_lines = 0;
|
229 |
+
}
|
230 |
+
|
231 |
+
settype($remove_lines, 'integer');
|
232 |
+
|
233 |
+
$doc_root = realpath($_SERVER['DOCUMENT_ROOT']);
|
234 |
+
$doc_root .= (substr($doc_root, -1) != DIRECTORY_SEPARATOR) ? DIRECTORY_SEPARATOR : '';
|
235 |
+
|
236 |
+
if ($backtrace === NULL) {
|
237 |
+
$backtrace = debug_backtrace();
|
238 |
+
}
|
239 |
+
|
240 |
+
while ($remove_lines > 0) {
|
241 |
+
array_shift($backtrace);
|
242 |
+
$remove_lines--;
|
243 |
+
}
|
244 |
+
|
245 |
+
$backtrace = array_reverse($backtrace);
|
246 |
+
|
247 |
+
$bt_string = '';
|
248 |
+
$i = 0;
|
249 |
+
foreach ($backtrace as $call) {
|
250 |
+
if ($i) {
|
251 |
+
$bt_string .= "\n";
|
252 |
+
}
|
253 |
+
if (isset($call['file'])) {
|
254 |
+
$bt_string .= str_replace($doc_root, '{doc_root}' . DIRECTORY_SEPARATOR, $call['file']) . '(' . $call['line'] . '): ';
|
255 |
+
} else {
|
256 |
+
$bt_string .= '[internal function]: ';
|
257 |
+
}
|
258 |
+
if (isset($call['class'])) {
|
259 |
+
$bt_string .= $call['class'] . $call['type'];
|
260 |
+
}
|
261 |
+
if (isset($call['class']) || isset($call['function'])) {
|
262 |
+
$bt_string .= $call['function'] . '(';
|
263 |
+
$j = 0;
|
264 |
+
if (!isset($call['args'])) {
|
265 |
+
$call['args'] = array();
|
266 |
+
}
|
267 |
+
foreach ($call['args'] as $arg) {
|
268 |
+
if ($j) {
|
269 |
+
$bt_string .= ', ';
|
270 |
+
}
|
271 |
+
if (is_bool($arg)) {
|
272 |
+
$bt_string .= ($arg) ? 'true' : 'false';
|
273 |
+
} elseif (is_null($arg)) {
|
274 |
+
$bt_string .= 'NULL';
|
275 |
+
} elseif (is_array($arg)) {
|
276 |
+
$bt_string .= 'Array';
|
277 |
+
} elseif (is_object($arg)) {
|
278 |
+
$bt_string .= 'Object(' . get_class($arg) . ')';
|
279 |
+
} elseif (is_string($arg)) {
|
280 |
+
// Shorten the UTF-8 string if it is too long
|
281 |
+
if (strlen(utf8_decode($arg)) > 18) {
|
282 |
+
// If we can't match as unicode, try single byte
|
283 |
+
if (!preg_match('#^(.{0,15})#us', $arg, $short_arg)) {
|
284 |
+
preg_match('#^(.{0,15})#s', $arg, $short_arg);
|
285 |
+
}
|
286 |
+
$arg = $short_arg[0] . '...';
|
287 |
+
}
|
288 |
+
$bt_string .= "'" . $arg . "'";
|
289 |
+
} else {
|
290 |
+
$bt_string .= (string) $arg;
|
291 |
+
}
|
292 |
+
$j++;
|
293 |
+
}
|
294 |
+
$bt_string .= ')';
|
295 |
+
}
|
296 |
+
$i++;
|
297 |
+
}
|
298 |
+
|
299 |
+
return $bt_string;
|
300 |
+
}
|
301 |
+
|
302 |
+
/**
|
303 |
+
* Performs a [http://php.net/call_user_func call_user_func()], while translating PHP 5.2 static callback syntax for PHP 5.1 and 5.0
|
304 |
+
*
|
305 |
+
* Parameters can be passed either as a single array of parameters or as
|
306 |
+
* multiple parameters.
|
307 |
+
*
|
308 |
+
* {{{
|
309 |
+
* #!php
|
310 |
+
* // Passing multiple parameters in a normal fashion
|
311 |
+
* fCore::call('Class::method', TRUE, 0, 'test');
|
312 |
+
*
|
313 |
+
* // Passing multiple parameters in a parameters array
|
314 |
+
* fCore::call('Class::method', array(TRUE, 0, 'test'));
|
315 |
+
* }}}
|
316 |
+
*
|
317 |
+
* To pass parameters by reference they must be assigned to an
|
318 |
+
* array by reference and the function/method being called must accept those
|
319 |
+
* parameters by reference. If either condition is not met, the parameter
|
320 |
+
* will be passed by value.
|
321 |
+
*
|
322 |
+
* {{{
|
323 |
+
* #!php
|
324 |
+
* // Passing parameters by reference
|
325 |
+
* fCore::call('Class::method', array(&$var1, &$var2));
|
326 |
+
* }}}
|
327 |
+
*
|
328 |
+
* @param callback $callback The function or method to call
|
329 |
+
* @param array $parameters The parameters to pass to the function/method
|
330 |
+
* @return mixed The return value of the called function/method
|
331 |
+
*/
|
332 |
+
static public function call($callback, $parameters = array()) {
|
333 |
+
// Fix PHP 5.0 and 5.1 static callback syntax
|
334 |
+
if (is_string($callback) && strpos($callback, '::') !== FALSE) {
|
335 |
+
$callback = explode('::', $callback);
|
336 |
+
}
|
337 |
+
|
338 |
+
$parameters = array_slice(func_get_args(), 1);
|
339 |
+
if (sizeof($parameters) == 1 && is_array($parameters[0])) {
|
340 |
+
$parameters = $parameters[0];
|
341 |
+
}
|
342 |
+
|
343 |
+
return call_user_func_array($callback, $parameters);
|
344 |
+
}
|
345 |
+
|
346 |
+
/**
|
347 |
+
* Translates a Class::method style static method callback to array style for compatibility with PHP 5.0 and 5.1 and built-in PHP functions
|
348 |
+
*
|
349 |
+
* @param callback $callback The callback to translate
|
350 |
+
* @return array The translated callback
|
351 |
+
*/
|
352 |
+
static public function callback($callback) {
|
353 |
+
if (is_string($callback) && strpos($callback, '::') !== FALSE) {
|
354 |
+
return explode('::', $callback);
|
355 |
+
}
|
356 |
+
|
357 |
+
return $callback;
|
358 |
+
}
|
359 |
+
|
360 |
+
/**
|
361 |
+
* Checks an error/exception destination to make sure it is valid
|
362 |
+
*
|
363 |
+
* @param string $destination The destination for the exception. An email, file or the string `'html'`.
|
364 |
+
* @return string|boolean `'email'`, `'file'`, `'html'` or `FALSE`
|
365 |
+
*/
|
366 |
+
static private function checkDestination($destination) {
|
367 |
+
if ($destination == 'html') {
|
368 |
+
return 'html';
|
369 |
+
}
|
370 |
+
|
371 |
+
if (preg_match('~^(?: # Allow leading whitespace
|
372 |
+
(?:[^\x00-\x20\(\)<>@,;:\\\\"\.\[\]]+|"[^"\\\\\n\r]+") # An "atom" or a quoted string
|
373 |
+
(?:\.[ \t]*(?:[^\x00-\x20\(\)<>@,;:\\\\"\.\[\]]+|"[^"\\\\\n\r]+"[ \t]*))* # A . plus another "atom" or a quoted string, any number of times
|
374 |
+
)@(?: # The @ symbol
|
375 |
+
(?:[a-z0-9\\-]+\.)+[a-z]{2,}| # Domain name
|
376 |
+
(?:(?:[01]?\d?\d|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d?\d|2[0-4]\d|25[0-5]) # (or) IP addresses
|
377 |
+
)
|
378 |
+
(?:\s*,\s* # Any number of other emails separated by a comma with surrounding spaces
|
379 |
+
(?:
|
380 |
+
(?:[^\x00-\x20\(\)<>@,;:\\\\"\.\[\]]+|"[^"\\\\\n\r]+")
|
381 |
+
(?:\.[ \t]*(?:[^\x00-\x20\(\)<>@,;:\\\\"\.\[\]]+|"[^"\\\\\n\r]+"[ \t]*))*
|
382 |
+
)@(?:
|
383 |
+
(?:[a-z0-9\\-]+\.)+[a-z]{2,}|
|
384 |
+
(?:(?:[01]?\d?\d|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d?\d|2[0-4]\d|25[0-5])
|
385 |
+
)
|
386 |
+
)*$~xiD', $destination)) {
|
387 |
+
return 'email';
|
388 |
+
}
|
389 |
+
|
390 |
+
$path_info = pathinfo($destination);
|
391 |
+
$dir_exists = file_exists($path_info['dirname']);
|
392 |
+
$dir_writable = ($dir_exists) ? is_writable($path_info['dirname']) : FALSE;
|
393 |
+
$file_exists = file_exists($destination);
|
394 |
+
$file_writable = ($file_exists) ? is_writable($destination) : FALSE;
|
395 |
+
|
396 |
+
if (!$dir_exists || ($dir_exists && ((!$file_exists && !$dir_writable) || ($file_exists && !$file_writable)))) {
|
397 |
+
return FALSE;
|
398 |
+
}
|
399 |
+
|
400 |
+
return 'file';
|
401 |
+
}
|
402 |
+
|
403 |
+
/**
|
404 |
+
* Returns is the current OS is one of the OSes passed as a parameter
|
405 |
+
*
|
406 |
+
* Valid OS strings are:
|
407 |
+
* - `'linux'`
|
408 |
+
* - `'aix'`
|
409 |
+
* - `'bsd'`
|
410 |
+
* - `'freebsd'`
|
411 |
+
* - `'netbsd'`
|
412 |
+
* - `'openbsd'`
|
413 |
+
* - `'osx'`
|
414 |
+
* - `'solaris'`
|
415 |
+
* - `'windows'`
|
416 |
+
*
|
417 |
+
* @param string $os The operating system to check - see method description for valid OSes
|
418 |
+
* @param string ...
|
419 |
+
* @return boolean If the current OS is included in the list of OSes passed as parameters
|
420 |
+
*/
|
421 |
+
static public function checkOS($os) {
|
422 |
+
$oses = func_get_args();
|
423 |
+
|
424 |
+
$valid_oses = array('linux', 'aix', 'bsd', 'freebsd', 'openbsd', 'netbsd', 'osx', 'solaris', 'windows');
|
425 |
+
|
426 |
+
if ($invalid_oses = array_diff($oses, $valid_oses)) {
|
427 |
+
throw new fProgrammerException(
|
428 |
+
'One or more of the OSes specified, %$1s, is invalid. Must be one of: %2$s.', join(' ', $invalid_oses), join(', ', $valid_oses)
|
429 |
+
);
|
430 |
+
}
|
431 |
+
|
432 |
+
$uname = php_uname('s');
|
433 |
+
|
434 |
+
if (stripos($uname, 'linux') !== FALSE) {
|
435 |
+
return in_array('linux', $oses);
|
436 |
+
} elseif (stripos($uname, 'aix') !== FALSE) {
|
437 |
+
return in_array('aix', $oses);
|
438 |
+
} elseif (stripos($uname, 'netbsd') !== FALSE) {
|
439 |
+
return in_array('netbsd', $oses) || in_array('bsd', $oses);
|
440 |
+
} elseif (stripos($uname, 'openbsd') !== FALSE) {
|
441 |
+
return in_array('openbsd', $oses) || in_array('bsd', $oses);
|
442 |
+
} elseif (stripos($uname, 'freebsd') !== FALSE) {
|
443 |
+
return in_array('freebsd', $oses) || in_array('bsd', $oses);
|
444 |
+
} elseif (stripos($uname, 'solaris') !== FALSE || stripos($uname, 'sunos') !== FALSE) {
|
445 |
+
return in_array('solaris', $oses);
|
446 |
+
} elseif (stripos($uname, 'windows') !== FALSE) {
|
447 |
+
return in_array('windows', $oses);
|
448 |
+
} elseif (stripos($uname, 'darwin') !== FALSE) {
|
449 |
+
return in_array('osx', $oses);
|
450 |
+
}
|
451 |
+
|
452 |
+
throw new fEnvironmentException('Unable to determine the current OS');
|
453 |
+
}
|
454 |
+
|
455 |
+
/**
|
456 |
+
* Checks to see if the running version of PHP is greater or equal to the version passed
|
457 |
+
*
|
458 |
+
* @return boolean If the running version of PHP is greater or equal to the version passed
|
459 |
+
*/
|
460 |
+
static public function checkVersion($version) {
|
461 |
+
static $running_version = NULL;
|
462 |
+
|
463 |
+
if ($running_version === NULL) {
|
464 |
+
$running_version = preg_replace(
|
465 |
+
'#^(\d+\.\d+\.\d+).*$#D', '\1', PHP_VERSION
|
466 |
+
);
|
467 |
+
}
|
468 |
+
|
469 |
+
return version_compare($running_version, $version, '>=');
|
470 |
+
}
|
471 |
+
|
472 |
+
/**
|
473 |
+
* Composes text using fText if loaded
|
474 |
+
*
|
475 |
+
* @param string $message The message to compose
|
476 |
+
* @param mixed $component A string or number to insert into the message
|
477 |
+
* @param mixed ...
|
478 |
+
* @return string The composed and possible translated message
|
479 |
+
*/
|
480 |
+
static private function compose($message) {
|
481 |
+
$args = array_slice(func_get_args(), 1);
|
482 |
+
|
483 |
+
if (class_exists('fText', FALSE)) {
|
484 |
+
return call_user_func_array(
|
485 |
+
array('fText', 'compose'), array($message, $args)
|
486 |
+
);
|
487 |
+
} else {
|
488 |
+
return vsprintf($message, $args);
|
489 |
+
}
|
490 |
+
}
|
491 |
+
|
492 |
+
/**
|
493 |
+
* Sets an fSMTP object to be used for sending error and exception emails
|
494 |
+
*
|
495 |
+
* @param fSMTP $smtp The SMTP connection to send emails over
|
496 |
+
* @param string $from_email The email address to use in the `From:` header
|
497 |
+
* @return void
|
498 |
+
*/
|
499 |
+
static public function configureSMTP($smtp, $from_email) {
|
500 |
+
self::$smtp_connection = $smtp;
|
501 |
+
self::$smtp_from_email = $from_email;
|
502 |
+
}
|
503 |
+
|
504 |
+
/**
|
505 |
+
* Prints a debugging message if global or code-specific debugging is enabled
|
506 |
+
*
|
507 |
+
* @param string $message The debug message
|
508 |
+
* @param boolean $force If debugging should be forced even when global debugging is off
|
509 |
+
* @return void
|
510 |
+
*/
|
511 |
+
static public function debug($message, $force = FALSE) {
|
512 |
+
if ($force || self::$debug) {
|
513 |
+
if (self::$debug_callback) {
|
514 |
+
call_user_func(self::$debug_callback, $message);
|
515 |
+
} else {
|
516 |
+
self::expose($message);
|
517 |
+
}
|
518 |
+
}
|
519 |
+
}
|
520 |
+
|
521 |
+
/**
|
522 |
+
* Creates a string representation of any variable using predefined strings for booleans, `NULL` and empty strings
|
523 |
+
*
|
524 |
+
* The string output format of this method is very similar to the output of
|
525 |
+
* [http://php.net/print_r print_r()] except that the following values
|
526 |
+
* are represented as special strings:
|
527 |
+
*
|
528 |
+
* - `TRUE`: `'{true}'`
|
529 |
+
* - `FALSE`: `'{false}'`
|
530 |
+
* - `NULL`: `'{null}'`
|
531 |
+
* - `''`: `'{empty_string}'`
|
532 |
+
*
|
533 |
+
* @param mixed $data The value to dump
|
534 |
+
* @return string The string representation of the value
|
535 |
+
*/
|
536 |
+
static public function dump($data) {
|
537 |
+
if (is_bool($data)) {
|
538 |
+
return ($data) ? '{true}' : '{false}';
|
539 |
+
} elseif (is_null($data)) {
|
540 |
+
return '{null}';
|
541 |
+
} elseif ($data === '') {
|
542 |
+
return '{empty_string}';
|
543 |
+
} elseif (is_array($data) || is_object($data)) {
|
544 |
+
|
545 |
+
ob_start();
|
546 |
+
var_dump($data);
|
547 |
+
$output = ob_get_contents();
|
548 |
+
ob_end_clean();
|
549 |
+
|
550 |
+
// Make the var dump more like a print_r
|
551 |
+
$output = preg_replace('#=>\n( )+(?=[a-zA-Z]|&)#m', ' => ', $output);
|
552 |
+
$output = str_replace('string(0) ""', '{empty_string}', $output);
|
553 |
+
$output = preg_replace('#=> (&)?NULL#', '=> \1{null}', $output);
|
554 |
+
$output = preg_replace('#=> (&)?bool\((false|true)\)#', '=> \1{\2}', $output);
|
555 |
+
$output = preg_replace('#(?<=^|\] => )(?:float|int)\((-?\d+(?:.\d+)?)\)#', '\1', $output);
|
556 |
+
$output = preg_replace('#string\(\d+\) "#', '', $output);
|
557 |
+
$output = preg_replace('#"(\n( )*)(?=\[|\})#', '\1', $output);
|
558 |
+
$output = preg_replace('#((?: )+)\["(.*?)"\]#', '\1[\2]', $output);
|
559 |
+
$output = preg_replace('#(?:&)?array\(\d+\) \{\n((?: )*)((?: )(?=\[)|(?=\}))#', "Array\n\\1(\n\\1\\2", $output);
|
560 |
+
$output = preg_replace('/object\((\w+)\)#\d+ \(\d+\) {\n((?: )*)((?: )(?=\[)|(?=\}))/', "\\1 Object\n\\2(\n\\2\\3", $output);
|
561 |
+
$output = preg_replace('#^((?: )+)}(?=\n|$)#m', "\\1)\n", $output);
|
562 |
+
$output = substr($output, 0, -2) . ')';
|
563 |
+
|
564 |
+
// Fix indenting issues with the var dump output
|
565 |
+
$output_lines = explode("\n", $output);
|
566 |
+
$new_output = array();
|
567 |
+
$stack = 0;
|
568 |
+
foreach ($output_lines as $line) {
|
569 |
+
if (preg_match('#^((?: )*)([^ ])#', $line, $match)) {
|
570 |
+
$spaces = strlen($match[1]);
|
571 |
+
if ($spaces && $match[2] == '(') {
|
572 |
+
$stack += 1;
|
573 |
+
}
|
574 |
+
$new_output[] = str_pad('', ($spaces) + (4 * $stack)) . $line;
|
575 |
+
if ($spaces && $match[2] == ')') {
|
576 |
+
$stack -= 1;
|
577 |
+
}
|
578 |
+
} else {
|
579 |
+
$new_output[] = str_pad('', ($spaces) + (4 * $stack)) . $line;
|
580 |
+
}
|
581 |
+
}
|
582 |
+
|
583 |
+
return join("\n", $new_output);
|
584 |
+
} else {
|
585 |
+
return (string) $data;
|
586 |
+
}
|
587 |
+
}
|
588 |
+
|
589 |
+
/**
|
590 |
+
* Disables including the context information with exception and error messages
|
591 |
+
*
|
592 |
+
* The context information includes the following superglobals:
|
593 |
+
*
|
594 |
+
* - `$_SERVER`
|
595 |
+
* - `$_POST`
|
596 |
+
* - `$_GET`
|
597 |
+
* - `$_SESSION`
|
598 |
+
* - `$_FILES`
|
599 |
+
* - `$_COOKIE`
|
600 |
+
*
|
601 |
+
* @return void
|
602 |
+
*/
|
603 |
+
static public function disableContext() {
|
604 |
+
self::$show_context = FALSE;
|
605 |
+
}
|
606 |
+
|
607 |
+
/**
|
608 |
+
* Enables debug messages globally, i.e. they will be shown for any call to ::debug()
|
609 |
+
*
|
610 |
+
* @param boolean $flag If debugging messages should be shown
|
611 |
+
* @return void
|
612 |
+
*/
|
613 |
+
static public function enableDebugging($flag) {
|
614 |
+
self::$debug = (boolean) $flag;
|
615 |
+
}
|
616 |
+
|
617 |
+
/**
|
618 |
+
* Turns on a feature where undefined constants are automatically created with the string value equivalent to the name
|
619 |
+
*
|
620 |
+
* This functionality only works if ::enableErrorHandling() has been
|
621 |
+
* called first. This functionality may have a very slight performance
|
622 |
+
* impact since a `E_STRICT` error message must be captured and then a
|
623 |
+
* call to [http://php.net/define define()] is made.
|
624 |
+
*
|
625 |
+
* @return void
|
626 |
+
*/
|
627 |
+
static public function enableDynamicConstants() {
|
628 |
+
if (!self::$handles_errors) {
|
629 |
+
throw new fProgrammerException(
|
630 |
+
'Dynamic constants can not be enabled unless error handling has been enabled via %s', __CLASS__ . '::enableErrorHandling()'
|
631 |
+
);
|
632 |
+
}
|
633 |
+
self::$dynamic_constants = TRUE;
|
634 |
+
}
|
635 |
+
|
636 |
+
/**
|
637 |
+
* Turns on developer-friendly error handling that includes context information including a backtrace and superglobal dumps
|
638 |
+
*
|
639 |
+
* All errors that match the current
|
640 |
+
* [http://php.net/error_reporting error_reporting()] level will be
|
641 |
+
* redirected to the destination and will include a full backtrace. In
|
642 |
+
* addition, dumps of the following superglobals will be made to aid in
|
643 |
+
* debugging:
|
644 |
+
*
|
645 |
+
* - `$_SERVER`
|
646 |
+
* - `$_POST`
|
647 |
+
* - `$_GET`
|
648 |
+
* - `$_SESSION`
|
649 |
+
* - `$_FILES`
|
650 |
+
* - `$_COOKIE`
|
651 |
+
*
|
652 |
+
* The superglobal dumps are only done once per page, however a backtrace
|
653 |
+
* in included for each error.
|
654 |
+
*
|
655 |
+
* If an email address is specified for the destination, only one email
|
656 |
+
* will be sent per script execution. If both error and
|
657 |
+
* [enableExceptionHandling() exception handling] are set to the same
|
658 |
+
* email address, the email will contain both errors and exceptions.
|
659 |
+
*
|
660 |
+
* @param string $destination The destination for the errors and context information - an email address, a file path or the string `'html'`
|
661 |
+
* @param boolean $handle_fatal_errors If true, a shutdown function will be registered to handle a fatal error.
|
662 |
+
* Be aware, other shutdown functions could inadvertantly disable this one or exit the process.
|
663 |
+
*
|
664 |
+
* @return void
|
665 |
+
*/
|
666 |
+
static public function enableErrorHandling($destination, $handle_fatal_errors = FALSE) {
|
667 |
+
if (!self::checkDestination($destination)) {
|
668 |
+
return;
|
669 |
+
}
|
670 |
+
self::$error_destination = $destination;
|
671 |
+
self::$handles_errors = TRUE;
|
672 |
+
set_error_handler(self::callback(self::handleError));
|
673 |
+
|
674 |
+
if ($handle_fatal_errors) {
|
675 |
+
register_shutdown_function(self::callback(self::handleFatalError));
|
676 |
+
}
|
677 |
+
}
|
678 |
+
|
679 |
+
/**
|
680 |
+
* Turns on developer-friendly uncaught exception handling that includes context information including a backtrace and superglobal dumps
|
681 |
+
*
|
682 |
+
* Any uncaught exception will be redirected to the destination specified,
|
683 |
+
* and the page will execute the `$closing_code` callback before exiting.
|
684 |
+
* The destination will receive a message with the exception messaage, a
|
685 |
+
* full backtrace and dumps of the following superglobals to aid in
|
686 |
+
* debugging:
|
687 |
+
*
|
688 |
+
* - `$_SERVER`
|
689 |
+
* - `$_POST`
|
690 |
+
* - `$_GET`
|
691 |
+
* - `$_SESSION`
|
692 |
+
* - `$_FILES`
|
693 |
+
* - `$_COOKIE`
|
694 |
+
*
|
695 |
+
* The superglobal dumps are only done once per page, however a backtrace
|
696 |
+
* in included for each error.
|
697 |
+
*
|
698 |
+
* If an email address is specified for the destination, only one email
|
699 |
+
* will be sent per script execution.
|
700 |
+
*
|
701 |
+
* If an email address is specified for the destination, only one email
|
702 |
+
* will be sent per script execution. If both exception and
|
703 |
+
* [enableErrorHandling() error handling] are set to the same
|
704 |
+
* email address, the email will contain both exceptions and errors.
|
705 |
+
*
|
706 |
+
* @param string $destination The destination for the exception and context information - an email address, a file path or the string `'html'`
|
707 |
+
* @param callback $closing_code This callback will happen after the exception is handled and before page execution stops. Good for printing a footer. If no callback is provided and the exception extends fException, fException::printMessage() will be called.
|
708 |
+
* @param array $parameters The parameters to send to `$closing_code`
|
709 |
+
* @return void
|
710 |
+
*/
|
711 |
+
static public function enableExceptionHandling($destination, $closing_code = NULL, $parameters = array()) {
|
712 |
+
if (!self::checkDestination($destination)) {
|
713 |
+
return;
|
714 |
+
}
|
715 |
+
self::$handles_exceptions = TRUE;
|
716 |
+
self::$exception_destination = $destination;
|
717 |
+
self::$exception_handler_callback = $closing_code;
|
718 |
+
if (!is_object($parameters)) {
|
719 |
+
settype($parameters, 'array');
|
720 |
+
} else {
|
721 |
+
$parameters = array($parameters);
|
722 |
+
}
|
723 |
+
self::$exception_handler_parameters = $parameters;
|
724 |
+
set_exception_handler(self::callback(self::handleException));
|
725 |
+
}
|
726 |
+
|
727 |
+
/**
|
728 |
+
* Prints the ::dump() of a value
|
729 |
+
*
|
730 |
+
* The dump will be printed in a `<pre>` tag with the class `exposed` if
|
731 |
+
* PHP is running anywhere but via the command line (cli). If PHP is
|
732 |
+
* running via the cli, the data will be printed, followed by a single
|
733 |
+
* line break (`\n`).
|
734 |
+
*
|
735 |
+
* If multiple parameters are passed, they are exposed as an array.
|
736 |
+
*
|
737 |
+
* @param mixed $data The value to show
|
738 |
+
* @param mixed ...
|
739 |
+
* @return void
|
740 |
+
*/
|
741 |
+
static public function expose($data) {
|
742 |
+
$args = func_get_args();
|
743 |
+
if (count($args) > 1) {
|
744 |
+
$data = $args;
|
745 |
+
}
|
746 |
+
if (PHP_SAPI != 'cli') {
|
747 |
+
echo '<pre class="exposed">' . htmlspecialchars((string) self::dump($data), ENT_QUOTES) . '</pre>';
|
748 |
+
} else {
|
749 |
+
echo self::dump($data) . "\n";
|
750 |
+
}
|
751 |
+
}
|
752 |
+
|
753 |
+
/**
|
754 |
+
* Generates some information about the context of an error or exception
|
755 |
+
*
|
756 |
+
* @return string A string containing `$_SERVER`, `$_GET`, `$_POST`, `$_FILES`, `$_SESSION` and `$_COOKIE`
|
757 |
+
*/
|
758 |
+
static public function generateContext() {
|
759 |
+
return self::compose('Context') . "\n-------" .
|
760 |
+
"\n\n\$_SERVER: " . self::dump($_SERVER) .
|
761 |
+
"\n\n\$_POST: " . self::dump($_POST) .
|
762 |
+
"\n\n\$_GET: " . self::dump($_GET) .
|
763 |
+
"\n\n\$_FILES: " . self::dump($_FILES) .
|
764 |
+
"\n\n\$_SESSION: " . self::dump((isset($_SESSION)) ? $_SESSION : NULL) .
|
765 |
+
"\n\n\$_COOKIE: " . self::dump($_COOKIE);
|
766 |
+
}
|
767 |
+
|
768 |
+
/**
|
769 |
+
* If debugging is enabled
|
770 |
+
*
|
771 |
+
* @param boolean $force If debugging is forced
|
772 |
+
* @return boolean If debugging is enabled
|
773 |
+
*/
|
774 |
+
static public function getDebug($force = FALSE) {
|
775 |
+
return self::$debug || $force;
|
776 |
+
}
|
777 |
+
|
778 |
+
/**
|
779 |
+
* A shutdown function to handle a fatal error
|
780 |
+
*
|
781 |
+
* @internal
|
782 |
+
*
|
783 |
+
* @return void
|
784 |
+
*/
|
785 |
+
static public function handleFatalError() {
|
786 |
+
$error = error_get_last();
|
787 |
+
|
788 |
+
$allowed_error_types = array(
|
789 |
+
E_ERROR, E_CORE_ERROR
|
790 |
+
);
|
791 |
+
|
792 |
+
if ($error != NULL && in_array($error['type'], $allowed_error_types)) {
|
793 |
+
$error_number = $error['type'];
|
794 |
+
$error_file = $error['file'];
|
795 |
+
$error_line = $error['line'];
|
796 |
+
$error_string = $error['message'];
|
797 |
+
|
798 |
+
self::handleError($error_number, $error_string, $error_file, $error_line);
|
799 |
+
}
|
800 |
+
}
|
801 |
+
|
802 |
+
/**
|
803 |
+
* Handles an error, creating the necessary context information and sending it to the specified destination
|
804 |
+
*
|
805 |
+
* @internal
|
806 |
+
*
|
807 |
+
* @param integer $error_number The error type
|
808 |
+
* @param string $error_string The message for the error
|
809 |
+
* @param string $error_file The file the error occurred in
|
810 |
+
* @param integer $error_line The line the error occurred on
|
811 |
+
* @param array $error_context A references to all variables in scope at the occurence of the error
|
812 |
+
* @return void
|
813 |
+
*/
|
814 |
+
static public function handleError($error_number, $error_string, $error_file = NULL, $error_line = NULL, $error_context = NULL) {
|
815 |
+
if (self::$dynamic_constants && $error_number == E_NOTICE) {
|
816 |
+
if (preg_match("#^Use of undefined constant (\w+) - assumed '\w+'\$#D", $error_string, $matches)) {
|
817 |
+
define($matches[1], $matches[1]);
|
818 |
+
return;
|
819 |
+
}
|
820 |
+
}
|
821 |
+
|
822 |
+
$capturing = (bool) self::$captured_error_level;
|
823 |
+
$level_match = (bool) (error_reporting() & $error_number);
|
824 |
+
|
825 |
+
if (!$capturing && !$level_match) {
|
826 |
+
return;
|
827 |
+
}
|
828 |
+
|
829 |
+
$doc_root = realpath($_SERVER['DOCUMENT_ROOT']);
|
830 |
+
$doc_root .= (substr($doc_root, -1) != '/' && substr($doc_root, -1) != '\\') ? '/' : '';
|
831 |
+
|
832 |
+
$backtrace = self::backtrace(1);
|
833 |
+
|
834 |
+
// Remove the reference to handleError
|
835 |
+
$backtrace = preg_replace('#: fCore::handleError\(.*?\)$#', '', $backtrace);
|
836 |
+
|
837 |
+
$error_string = preg_replace('# \[<a href=\'.*?</a>\]: #', ': ', $error_string);
|
838 |
+
|
839 |
+
// This was added in 5.2
|
840 |
+
if (!defined('E_RECOVERABLE_ERROR')) {
|
841 |
+
define('E_RECOVERABLE_ERROR', 4096);
|
842 |
+
}
|
843 |
+
|
844 |
+
// These were added in 5.3
|
845 |
+
if (!defined('E_DEPRECATED')) {
|
846 |
+
define('E_DEPRECATED', 8192);
|
847 |
+
}
|
848 |
+
|
849 |
+
if (!defined('E_USER_DEPRECATED')) {
|
850 |
+
define('E_USER_DEPRECATED', 16384);
|
851 |
+
}
|
852 |
+
|
853 |
+
switch ($error_number) {
|
854 |
+
case E_WARNING: $type = self::compose('Warning');
|
855 |
+
break;
|
856 |
+
case E_NOTICE: $type = self::compose('Notice');
|
857 |
+
break;
|
858 |
+
case E_USER_ERROR: $type = self::compose('User Error');
|
859 |
+
break;
|
860 |
+
case E_USER_WARNING: $type = self::compose('User Warning');
|
861 |
+
break;
|
862 |
+
case E_USER_NOTICE: $type = self::compose('User Notice');
|
863 |
+
break;
|
864 |
+
case E_STRICT: $type = self::compose('Strict');
|
865 |
+
break;
|
866 |
+
case E_RECOVERABLE_ERROR: $type = self::compose('Recoverable Error');
|
867 |
+
break;
|
868 |
+
case E_DEPRECATED: $type = self::compose('Deprecated');
|
869 |
+
break;
|
870 |
+
case E_USER_DEPRECATED: $type = self::compose('User Deprecated');
|
871 |
+
break;
|
872 |
+
case E_CORE_ERROR: $type = self::compose('Fatal Error');
|
873 |
+
break;
|
874 |
+
case E_ERROR: $type = self::compose('Fatal Error');
|
875 |
+
break;
|
876 |
+
}
|
877 |
+
|
878 |
+
if ($capturing) {
|
879 |
+
$type_to_capture = (bool) (self::$captured_error_types[self::$captured_error_level] & $error_number);
|
880 |
+
$string_to_capture = !self::$captured_error_regex[self::$captured_error_level] || (self::$captured_error_regex[self::$captured_error_level] && preg_match(self::$captured_error_regex[self::$captured_error_level], $error_string));
|
881 |
+
if ($type_to_capture && $string_to_capture) {
|
882 |
+
self::$captured_errors[self::$captured_error_level][] = array(
|
883 |
+
'number' => $error_number,
|
884 |
+
'type' => $type,
|
885 |
+
'string' => $error_string,
|
886 |
+
'file' => str_replace($doc_root, '{doc_root}/', $error_file),
|
887 |
+
'line' => $error_line,
|
888 |
+
'backtrace' => $backtrace,
|
889 |
+
'context' => $error_context
|
890 |
+
);
|
891 |
+
return;
|
892 |
+
}
|
893 |
+
|
894 |
+
// If the old handler is not this method, then we must have been trying to match a regex and failed
|
895 |
+
// so we pass the error on to the original handler to do its thing
|
896 |
+
if (self::$captured_errors_previous_handler[self::$captured_error_level] != array('fCore', 'handleError')) {
|
897 |
+
if (self::$captured_errors_previous_handler[self::$captured_error_level] === NULL) {
|
898 |
+
return FALSE;
|
899 |
+
}
|
900 |
+
return call_user_func(self::$captured_errors_previous_handler[self::$captured_error_level], $error_number, $error_string, $error_file, $error_line, $error_context);
|
901 |
+
|
902 |
+
// If we get here, this method is the error handler, but we don't want to actually report the error so we return
|
903 |
+
} elseif (!$level_match) {
|
904 |
+
return;
|
905 |
+
}
|
906 |
+
}
|
907 |
+
|
908 |
+
$error = $type . "\n" . str_pad('', strlen($type), '-') . "\n" . $backtrace . "\n" . $error_string;
|
909 |
+
|
910 |
+
$backtrace_lines = explode("\n", $backtrace);
|
911 |
+
|
912 |
+
self::sendMessageToDestination('error', $error, end($backtrace_lines));
|
913 |
+
}
|
914 |
+
|
915 |
+
/**
|
916 |
+
* Handles an uncaught exception, creating the necessary context information, sending it to the specified destination and finally executing the closing callback
|
917 |
+
*
|
918 |
+
* @internal
|
919 |
+
*
|
920 |
+
* @param object $exception The uncaught exception to handle
|
921 |
+
* @return void
|
922 |
+
*/
|
923 |
+
static public function handleException($exception) {
|
924 |
+
$message = ($exception->getMessage()) ? $exception->getMessage() : '{no message}';
|
925 |
+
if ($exception instanceof fException) {
|
926 |
+
$trace = $exception->formatTrace();
|
927 |
+
} else {
|
928 |
+
$trace = $exception->getTraceAsString();
|
929 |
+
}
|
930 |
+
$code = ($exception->getCode()) ? ' (code ' . $exception->getCode() . ')' : '';
|
931 |
+
|
932 |
+
$info = $trace . "\n" . $message . $code;
|
933 |
+
$headline = self::compose("Uncaught") . " " . get_class($exception);
|
934 |
+
$info_block = $headline . "\n" . str_pad('', strlen($headline), '-') . "\n" . trim($info);
|
935 |
+
|
936 |
+
$trace_lines = explode("\n", $trace);
|
937 |
+
|
938 |
+
self::sendMessageToDestination('exception', $info_block, end($trace_lines));
|
939 |
+
|
940 |
+
if (self::$exception_handler_callback === NULL) {
|
941 |
+
if (self::$exception_destination != 'html' && $exception instanceof fException) {
|
942 |
+
$exception->printMessage();
|
943 |
+
}
|
944 |
+
return;
|
945 |
+
}
|
946 |
+
|
947 |
+
try {
|
948 |
+
self::call(self::$exception_handler_callback, self::$exception_handler_parameters);
|
949 |
+
} catch (Exception $e) {
|
950 |
+
trigger_error(
|
951 |
+
self::compose(
|
952 |
+
'An exception was thrown in the %s closing code callback', 'setExceptionHandling()'
|
953 |
+
), E_USER_ERROR
|
954 |
+
);
|
955 |
+
}
|
956 |
+
}
|
957 |
+
|
958 |
+
/**
|
959 |
+
* Registers a callback to handle debug messages instead of the default action of calling ::expose() on the message
|
960 |
+
*
|
961 |
+
* @param callback $callback A callback that accepts a single parameter, the string debug message to handle
|
962 |
+
* @return void
|
963 |
+
*/
|
964 |
+
static public function registerDebugCallback($callback) {
|
965 |
+
self::$debug_callback = self::callback($callback);
|
966 |
+
}
|
967 |
+
|
968 |
+
/**
|
969 |
+
* Resets the configuration of the class
|
970 |
+
*
|
971 |
+
* @internal
|
972 |
+
*
|
973 |
+
* @return void
|
974 |
+
*/
|
975 |
+
static public function reset() {
|
976 |
+
if (self::$handles_errors) {
|
977 |
+
restore_error_handler();
|
978 |
+
}
|
979 |
+
if (self::$handles_exceptions) {
|
980 |
+
restore_exception_handler();
|
981 |
+
}
|
982 |
+
|
983 |
+
if (is_array(self::$captured_errors)) {
|
984 |
+
restore_error_handler();
|
985 |
+
}
|
986 |
+
|
987 |
+
self::$captured_error_level = 0;
|
988 |
+
self::$captured_error_regex = array();
|
989 |
+
self::$captured_error_types = array();
|
990 |
+
self::$captured_errors = array();
|
991 |
+
self::$captured_errors_previous_handler = array();
|
992 |
+
self::$context_shown = FALSE;
|
993 |
+
self::$debug = NULL;
|
994 |
+
self::$debug_callback = NULL;
|
995 |
+
self::$dynamic_constants = FALSE;
|
996 |
+
self::$error_destination = 'html';
|
997 |
+
self::$error_message_queue = array();
|
998 |
+
self::$exception_destination = 'html';
|
999 |
+
self::$exception_handler_callback = NULL;
|
1000 |
+
self::$exception_handler_parameters = array();
|
1001 |
+
self::$exception_message = NULL;
|
1002 |
+
self::$handles_errors = FALSE;
|
1003 |
+
self::$handles_exceptions = FALSE;
|
1004 |
+
self::$significant_error_lines = array();
|
1005 |
+
self::$show_context = TRUE;
|
1006 |
+
self::$smtp_connection = NULL;
|
1007 |
+
self::$smtp_from_email = NULL;
|
1008 |
+
}
|
1009 |
+
|
1010 |
+
/**
|
1011 |
+
* Sends an email or writes a file with messages generated during the page execution
|
1012 |
+
*
|
1013 |
+
* This method prevents multiple emails from being sent or a log file from
|
1014 |
+
* being written multiple times for one script execution.
|
1015 |
+
*
|
1016 |
+
* @internal
|
1017 |
+
*
|
1018 |
+
* @return void
|
1019 |
+
*/
|
1020 |
+
static public function sendMessagesOnShutdown() {
|
1021 |
+
$messages = array();
|
1022 |
+
|
1023 |
+
if (self::$error_message_queue) {
|
1024 |
+
$message = join("\n\n", self::$error_message_queue);
|
1025 |
+
$messages[self::$error_destination] = $message;
|
1026 |
+
}
|
1027 |
+
|
1028 |
+
if (self::$exception_message) {
|
1029 |
+
if (isset($messages[self::$exception_destination])) {
|
1030 |
+
$messages[self::$exception_destination] .= "\n\n";
|
1031 |
+
} else {
|
1032 |
+
$messages[self::$exception_destination] = '';
|
1033 |
+
}
|
1034 |
+
$messages[self::$exception_destination] .= self::$exception_message;
|
1035 |
+
}
|
1036 |
+
|
1037 |
+
$hash = md5(join('', self::$significant_error_lines), TRUE);
|
1038 |
+
$hash = strtr(base64_encode($hash), '/', '-');
|
1039 |
+
$hash = substr(rtrim($hash, '='), 0, 8);
|
1040 |
+
|
1041 |
+
$first_file_line = preg_replace(
|
1042 |
+
'#^.*[/\\\\](.*)$#', '\1', reset(self::$significant_error_lines)
|
1043 |
+
);
|
1044 |
+
|
1045 |
+
$subject = self::compose(
|
1046 |
+
'[%1$s] %2$s error(s) beginning at %3$s {%4$s}', isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : php_uname('n'), count($messages), $first_file_line, $hash
|
1047 |
+
);
|
1048 |
+
|
1049 |
+
foreach ($messages as $destination => $message) {
|
1050 |
+
if (self::$show_context) {
|
1051 |
+
$message .= "\n\n" . self::generateContext();
|
1052 |
+
}
|
1053 |
+
|
1054 |
+
if (self::checkDestination($destination) == 'email') {
|
1055 |
+
if (self::$smtp_connection) {
|
1056 |
+
$email = new fEmail();
|
1057 |
+
foreach (explode(',', $destination) as $recipient) {
|
1058 |
+
$email->addRecipient($recipient);
|
1059 |
+
}
|
1060 |
+
$email->setFromEmail(self::$smtp_from_email);
|
1061 |
+
$email->setSubject($subject);
|
1062 |
+
$email->setBody($message);
|
1063 |
+
$email->send(self::$smtp_connection);
|
1064 |
+
} else {
|
1065 |
+
mail($destination, $subject, $message);
|
1066 |
+
}
|
1067 |
+
} else {
|
1068 |
+
$handle = fopen($destination, 'a');
|
1069 |
+
fwrite($handle, $subject . "\n\n");
|
1070 |
+
fwrite($handle, $message . "\n\n");
|
1071 |
+
fclose($handle);
|
1072 |
+
}
|
1073 |
+
}
|
1074 |
+
}
|
1075 |
+
|
1076 |
+
/**
|
1077 |
+
* Handles sending a message to a destination
|
1078 |
+
*
|
1079 |
+
* If the destination is an email address or file, the messages will be
|
1080 |
+
* spooled up until the end of the script execution to prevent multiple
|
1081 |
+
* emails from being sent or a log file being written to multiple times.
|
1082 |
+
*
|
1083 |
+
* @param string $type If the message is an error or an exception
|
1084 |
+
* @param string $message The message to send to the destination
|
1085 |
+
* @param string $significant_line The most significant line from an error or exception backtrace
|
1086 |
+
* @return void
|
1087 |
+
*/
|
1088 |
+
static private function sendMessageToDestination($type, $message, $significant_line) {
|
1089 |
+
$destination = ($type == 'exception') ? self::$exception_destination : self::$error_destination;
|
1090 |
+
|
1091 |
+
if ($destination == 'html') {
|
1092 |
+
if (self::$show_context && !self::$context_shown) {
|
1093 |
+
self::expose(self::generateContext());
|
1094 |
+
self::$context_shown = TRUE;
|
1095 |
+
}
|
1096 |
+
self::expose($message);
|
1097 |
+
return;
|
1098 |
+
}
|
1099 |
+
|
1100 |
+
static $registered_function = FALSE;
|
1101 |
+
if (!$registered_function) {
|
1102 |
+
register_shutdown_function(self::callback(self::sendMessagesOnShutdown));
|
1103 |
+
$registered_function = TRUE;
|
1104 |
+
}
|
1105 |
+
|
1106 |
+
if ($type == 'error') {
|
1107 |
+
self::$error_message_queue[] = $message;
|
1108 |
+
} else {
|
1109 |
+
self::$exception_message = $message;
|
1110 |
+
}
|
1111 |
+
|
1112 |
+
self::$significant_error_lines[] = $significant_line;
|
1113 |
+
}
|
1114 |
+
|
1115 |
+
/**
|
1116 |
+
* Temporarily enables capturing error messages
|
1117 |
+
*
|
1118 |
+
* @param integer $types The error types to capture - this should be as specific as possible - defaults to all (E_ALL | E_STRICT)
|
1119 |
+
* @param string $regex A PCRE regex to match against the error message
|
1120 |
+
* @return void
|
1121 |
+
*/
|
1122 |
+
static public function startErrorCapture($types = NULL, $regex = NULL) {
|
1123 |
+
if ($types === NULL) {
|
1124 |
+
$types = E_ALL | E_STRICT;
|
1125 |
+
}
|
1126 |
+
|
1127 |
+
self::$captured_error_level++;
|
1128 |
+
|
1129 |
+
self::$captured_error_regex[self::$captured_error_level] = $regex;
|
1130 |
+
self::$captured_error_types[self::$captured_error_level] = $types;
|
1131 |
+
self::$captured_errors[self::$captured_error_level] = array();
|
1132 |
+
self::$captured_errors_previous_handler[self::$captured_error_level] = set_error_handler(self::callback(self::handleError));
|
1133 |
+
}
|
1134 |
+
|
1135 |
+
/**
|
1136 |
+
* Stops capturing error messages, returning all that have been captured
|
1137 |
+
*
|
1138 |
+
* @param string $regex A PCRE regex to filter messages by
|
1139 |
+
* @return array The captured error messages
|
1140 |
+
*/
|
1141 |
+
static public function stopErrorCapture($regex = NULL) {
|
1142 |
+
$captures = self::$captured_errors[self::$captured_error_level];
|
1143 |
+
|
1144 |
+
self::$captured_error_level--;
|
1145 |
+
|
1146 |
+
self::$captured_error_regex = array_slice(self::$captured_error_regex, 0, self::$captured_error_level, TRUE);
|
1147 |
+
self::$captured_error_types = array_slice(self::$captured_error_types, 0, self::$captured_error_level, TRUE);
|
1148 |
+
self::$captured_errors = array_slice(self::$captured_errors, 0, self::$captured_error_level, TRUE);
|
1149 |
+
self::$captured_errors_previous_handler = array_slice(self::$captured_errors_previous_handler, 0, self::$captured_error_level, TRUE);
|
1150 |
+
|
1151 |
+
restore_error_handler();
|
1152 |
+
|
1153 |
+
if ($regex) {
|
1154 |
+
$new_captures = array();
|
1155 |
+
foreach ($captures as $capture) {
|
1156 |
+
if (!preg_match($regex, $capture['string'])) {
|
1157 |
+
continue;
|
1158 |
+
}
|
1159 |
+
$new_captures[] = $capture;
|
1160 |
+
}
|
1161 |
+
$captures = $new_captures;
|
1162 |
+
}
|
1163 |
+
|
1164 |
+
return $captures;
|
1165 |
+
}
|
1166 |
+
|
1167 |
+
/**
|
1168 |
+
* Forces use as a static class
|
1169 |
+
*
|
1170 |
+
* @return fCore
|
1171 |
+
*/
|
1172 |
+
private function __construct() {
|
1173 |
+
|
1174 |
+
}
|
1175 |
+
|
1176 |
+
}
|
1177 |
+
|
1178 |
+
/**
|
1179 |
+
* Copyright (c) 2007-2011 Will Bond <will@flourishlib.com>, others
|
1180 |
+
*
|
1181 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
1182 |
+
* of this software and associated documentation files (the "Software"), to deal
|
1183 |
+
* in the Software without restriction, including without limitation the rights
|
1184 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
1185 |
+
* copies of the Software, and to permit persons to whom the Software is
|
1186 |
+
* furnished to do so, subject to the following conditions:
|
1187 |
+
*
|
1188 |
+
* The above copyright notice and this permission notice shall be included in
|
1189 |
+
* all copies or substantial portions of the Software.
|
1190 |
+
*
|
1191 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
1192 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
1193 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
1194 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
1195 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
1196 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
1197 |
+
* THE SOFTWARE.
|
1198 |
+
*/
|
lib/fEmail.php
ADDED
@@ -0,0 +1,1805 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Allows creating and sending a single email containing plaintext, HTML, attachments and S/MIME encryption
|
4 |
+
*
|
5 |
+
* Please note that this class uses the [http://php.net/function.mail mail()]
|
6 |
+
* function by default. Developers that are sending multiple emails, or need
|
7 |
+
* SMTP support, should use fSMTP with this class.
|
8 |
+
*
|
9 |
+
* This class is implemented to use the UTF-8 character encoding. Please see
|
10 |
+
* http://flourishlib.com/docs/UTF-8 for more information.
|
11 |
+
*
|
12 |
+
* @copyright Copyright (c) 2008-2011 Will Bond, others
|
13 |
+
* @author Will Bond [wb] <will@flourishlib.com>
|
14 |
+
* @author Bill Bushee, iMarc LLC [bb-imarc] <bill@imarc.net>
|
15 |
+
* @author netcarver [n] <fContrib@netcarving.com>
|
16 |
+
* @license http://flourishlib.com/license
|
17 |
+
*
|
18 |
+
* @package Flourish
|
19 |
+
* @link http://flourishlib.com/fEmail
|
20 |
+
*
|
21 |
+
* @version 1.0.0b30
|
22 |
+
* @changes 1.0.0b30 Changed methods to return instance for method chaining [n, 2011-09-12]
|
23 |
+
* @changes 1.0.0b29 Changed ::combineNameEmail() to be a static method and to be exposed publicly for use by other classes [wb, 2011-07-26]
|
24 |
+
* @changes 1.0.0b28 Fixed ::addAttachment() and ::addRelatedFile() to properly handle duplicate filenames [wb, 2011-05-17]
|
25 |
+
* @changes 1.0.0b27 Fixed a bug with generating FQDNs on some Windows machines [wb, 2011-02-24]
|
26 |
+
* @changes 1.0.0b26 Added ::addCustomerHeader() [wb, 2011-02-02]
|
27 |
+
* @changes 1.0.0b25 Fixed a bug with finding the FQDN on non-Windows machines [wb, 2011-01-19]
|
28 |
+
* @changes 1.0.0b24 Backwards Compatibility Break - the `$contents` parameter of ::addAttachment() is now first instead of third, ::addAttachment() will now accept fFile objects for the `$contents` parameter, added ::addRelatedFile() [wb, 2010-12-01]
|
29 |
+
* @changes 1.0.0b23 Fixed a bug on Windows where emails starting with a `.` would have the `.` removed [wb, 2010-09-11]
|
30 |
+
* @changes 1.0.0b22 Revamped the FQDN code and added ::getFQDN() [wb, 2010-09-07]
|
31 |
+
* @changes 1.0.0b21 Added a check to prevent permissions warnings when getting the FQDN on Windows machines [wb, 2010-09-02]
|
32 |
+
* @changes 1.0.0b20 Fixed ::send() to only remove the name of a recipient when dealing with the `mail()` function on Windows and to leave it when using fSMTP [wb, 2010-06-22]
|
33 |
+
* @changes 1.0.0b19 Changed ::send() to return the message id for the email, fixed the email regexes to require [] around IPs [wb, 2010-05-05]
|
34 |
+
* @changes 1.0.0b18 Fixed the name of the static method ::unindentExpand() [wb, 2010-04-28]
|
35 |
+
* @changes 1.0.0b17 Added the static method ::unindentExpand() [wb, 2010-04-26]
|
36 |
+
* @changes 1.0.0b16 Added support for sending emails via fSMTP [wb, 2010-04-20]
|
37 |
+
* @changes 1.0.0b15 Added the `$unindent_expand_constants` parameter to ::setBody(), added ::loadBody() and ::loadHTMLBody(), fixed HTML emails with attachments [wb, 2010-03-14]
|
38 |
+
* @changes 1.0.0b14 Changed ::send() to not double `.`s at the beginning of lines on Windows since it seemed to break things rather than fix them [wb, 2010-03-05]
|
39 |
+
* @changes 1.0.0b13 Fixed the class to work when safe mode is turned on [wb, 2009-10-23]
|
40 |
+
* @changes 1.0.0b12 Removed duplicate MIME-Version headers that were being included in S/MIME encrypted emails [wb, 2009-10-05]
|
41 |
+
* @changes 1.0.0b11 Updated to use the new fValidationException API [wb, 2009-09-17]
|
42 |
+
* @changes 1.0.0b10 Fixed a bug with sending both an HTML and a plaintext body [bb-imarc, 2009-06-18]
|
43 |
+
* @changes 1.0.0b9 Fixed a bug where the MIME headers were not being set for all emails [wb, 2009-06-12]
|
44 |
+
* @changes 1.0.0b8 Added the method ::clearRecipients() [wb, 2009-05-29]
|
45 |
+
* @changes 1.0.0b7 Email names with UTF-8 characters are now properly encoded [wb, 2009-05-08]
|
46 |
+
* @changes 1.0.0b6 Fixed a bug where <> quoted email addresses in validation messages were not showing [wb, 2009-03-27]
|
47 |
+
* @changes 1.0.0b5 Updated for new fCore API [wb, 2009-02-16]
|
48 |
+
* @changes 1.0.0b4 The recipient error message in ::validate() no longer contains a typo [wb, 2009-02-09]
|
49 |
+
* @changes 1.0.0b3 Fixed a bug with missing content in the fValidationException thrown by ::validate() [wb, 2009-01-14]
|
50 |
+
* @changes 1.0.0b2 Fixed a few bugs with sending S/MIME encrypted/signed emails [wb, 2009-01-10]
|
51 |
+
* @changes 1.0.0b The initial implementation [wb, 2008-06-23]
|
52 |
+
*/
|
53 |
+
class fEmail
|
54 |
+
{
|
55 |
+
// The following constants allow for nice looking callbacks to static methods
|
56 |
+
const combineNameEmail = 'fEmail::combineNameEmail';
|
57 |
+
const fixQmail = 'fEmail::fixQmail';
|
58 |
+
const getFQDN = 'fEmail::getFQDN';
|
59 |
+
const reset = 'fEmail::reset';
|
60 |
+
const unindentExpand = 'fEmail::unindentExpand';
|
61 |
+
|
62 |
+
|
63 |
+
/**
|
64 |
+
* A regular expression to match an email address, exluding those with comments and folding whitespace
|
65 |
+
*
|
66 |
+
* The matches will be:
|
67 |
+
*
|
68 |
+
* - `[0]`: The whole email address
|
69 |
+
* - `[1]`: The name before the `@`
|
70 |
+
* - `[2]`: The domain/ip after the `@`
|
71 |
+
*
|
72 |
+
* @var string
|
73 |
+
*/
|
74 |
+
const EMAIL_REGEX = '~^[ \t]*( # Allow leading whitespace
|
75 |
+
(?:[^\x00-\x20\(\)<>@,;:\\\\"\.\[\]]+|"[^"\\\\\n\r]+") # An "atom" or a quoted string
|
76 |
+
(?:\.[ \t]*(?:[^\x00-\x20\(\)<>@,;:\\\\"\.\[\]]+|"[^"\\\\\n\r]+"[ \t]*))* # A . plus another "atom" or a quoted string, any number of times
|
77 |
+
)@( # The @ symbol
|
78 |
+
(?:[a-z0-9\\-]+\.)+[a-z]{2,}| # Domain name
|
79 |
+
\[(?:(?:[01]?\d?\d|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d?\d|2[0-4]\d|25[0-5])\] # (or) IP addresses
|
80 |
+
)[ \t]*$~ixD'; # Allow Trailing whitespace
|
81 |
+
|
82 |
+
/**
|
83 |
+
* A regular expression to match a `name <email>` string, exluding those with comments and folding whitespace
|
84 |
+
*
|
85 |
+
* The matches will be:
|
86 |
+
*
|
87 |
+
* - `[0]`: The whole name and email address
|
88 |
+
* - `[1]`: The name
|
89 |
+
* - `[2]`: The whole email address
|
90 |
+
* - `[3]`: The email username before the `@`
|
91 |
+
* - `[4]`: The email domain/ip after the `@`
|
92 |
+
*
|
93 |
+
* @var string
|
94 |
+
*/
|
95 |
+
const NAME_EMAIL_REGEX = '~^[ \t]*( # Allow leading whitespace
|
96 |
+
(?:[^\x00-\x20\(\)<>@,;:\\\\"\.\[\]]+[ \t]*|"[^"\\\\\n\r]+"[ \t]*) # An "atom" or a quoted string
|
97 |
+
(?:\.?[ \t]*(?:[^\x00-\x20\(\)<>@,;:\\\\"\.\[\]]+[ \t]*|"[^"\\\\\n\r]+"[ \t]*))*) # Another "atom" or a quoted string or a . followed by one of those, any number of times
|
98 |
+
[ \t]*<[ \t]*(( # The < encapsulating the email address
|
99 |
+
(?:[^\x00-\x20\(\)<>@,;:\\\\"\.\[\]]+|"[^"\\\\\n\r]+") # An "atom" or a quoted string
|
100 |
+
(?:\.[ \t]*(?:[^\x00-\x20\(\)<>@,;:\\\\"\.\[\]]+|"[^"\\\\\n\r]+"[ \t]*))* # A . plus another "atom" or a quoted string, any number of times
|
101 |
+
)@( # The @ symbol
|
102 |
+
(?:[a-z0-9\\-]+\.)+[a-z]{2,}| # Domain nam
|
103 |
+
\[(?:(?:[01]?\d?\d|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d?\d|2[0-4]\d|25[0-5])\] # (or) IP addresses
|
104 |
+
))[ \t]*>[ \t]*$~ixD'; # Closing > and trailing whitespace
|
105 |
+
|
106 |
+
|
107 |
+
/**
|
108 |
+
* Flags if the class should convert `\r\n` to `\n` for qmail. This makes invalid email headers that may work.
|
109 |
+
*
|
110 |
+
* @var boolean
|
111 |
+
*/
|
112 |
+
static private $convert_crlf = FALSE;
|
113 |
+
|
114 |
+
/**
|
115 |
+
* The local fully-qualified domain name
|
116 |
+
*/
|
117 |
+
static private $fqdn;
|
118 |
+
|
119 |
+
/**
|
120 |
+
* Flags if the class should use [http://php.net/popen popen()] to send mail via sendmail
|
121 |
+
*
|
122 |
+
* @var boolean
|
123 |
+
*/
|
124 |
+
static private $popen_sendmail = FALSE;
|
125 |
+
|
126 |
+
|
127 |
+
/**
|
128 |
+
* Turns a name and email into a `"name" <email>` string, or just `email` if no name is provided
|
129 |
+
*
|
130 |
+
* This method will remove newline characters from the name and email, and
|
131 |
+
* will remove any backslash (`\`) and double quote (`"`) characters from
|
132 |
+
* the name.
|
133 |
+
*
|
134 |
+
* @internal
|
135 |
+
*
|
136 |
+
* @param string $name The name associated with the email address
|
137 |
+
* @param string $email The email address
|
138 |
+
* @return string The '"name" <email>' or 'email' string
|
139 |
+
*/
|
140 |
+
static public function combineNameEmail($name, $email)
|
141 |
+
{
|
142 |
+
// Strip lower ascii character since they aren't useful in email addresses
|
143 |
+
$email = preg_replace('#[\x0-\x19]+#', '', $email);
|
144 |
+
$name = preg_replace('#[\x0-\x19]+#', '', $name);
|
145 |
+
|
146 |
+
if (!$name) {
|
147 |
+
return $email;
|
148 |
+
}
|
149 |
+
|
150 |
+
// If the name contains any non-ascii bytes or stuff not allowed
|
151 |
+
// in quoted strings we just make an encoded word out of it
|
152 |
+
if (preg_replace('#[\x80-\xff\x5C\x22]#', '', $name) != $name) {
|
153 |
+
// The longest header name that will contain email addresses is
|
154 |
+
// "Bcc: ", which is 5 characters long
|
155 |
+
$name = self::makeEncodedWord($name, 5);
|
156 |
+
} else {
|
157 |
+
$name = '"' . $name . '"';
|
158 |
+
}
|
159 |
+
|
160 |
+
return $name . ' <' . $email . '>';
|
161 |
+
}
|
162 |
+
|
163 |
+
|
164 |
+
/**
|
165 |
+
* Composes text using fText if loaded
|
166 |
+
*
|
167 |
+
* @param string $message The message to compose
|
168 |
+
* @param mixed $component A string or number to insert into the message
|
169 |
+
* @param mixed ...
|
170 |
+
* @return string The composed and possible translated message
|
171 |
+
*/
|
172 |
+
static protected function compose($message)
|
173 |
+
{
|
174 |
+
$args = array_slice(func_get_args(), 1);
|
175 |
+
|
176 |
+
if (class_exists('fText', FALSE)) {
|
177 |
+
return call_user_func_array(
|
178 |
+
array('fText', 'compose'),
|
179 |
+
array($message, $args)
|
180 |
+
);
|
181 |
+
} else {
|
182 |
+
return vsprintf($message, $args);
|
183 |
+
}
|
184 |
+
}
|
185 |
+
|
186 |
+
|
187 |
+
/**
|
188 |
+
* Sets the class to try and fix broken qmail implementations that add `\r` to `\r\n`
|
189 |
+
*
|
190 |
+
* Before trying to fix qmail with this method, please try using fSMTP
|
191 |
+
* to connect to `localhost` and pass the fSMTP object to ::send().
|
192 |
+
*
|
193 |
+
* @return void
|
194 |
+
*/
|
195 |
+
static public function fixQmail()
|
196 |
+
{
|
197 |
+
if (fCore::checkOS('windows')) {
|
198 |
+
return;
|
199 |
+
}
|
200 |
+
|
201 |
+
$sendmail_command = ini_get('sendmail_path');
|
202 |
+
|
203 |
+
if (!$sendmail_command) {
|
204 |
+
self::$convert_crlf = TRUE;
|
205 |
+
trigger_error(
|
206 |
+
self::compose('The proper fix for sending through qmail is not possible since the sendmail path is not set'),
|
207 |
+
E_USER_WARNING
|
208 |
+
);
|
209 |
+
trigger_error(
|
210 |
+
self::compose('Trying to fix qmail by converting all \r\n to \n. This will cause invalid (but possibly functioning) email headers to be generated.'),
|
211 |
+
E_USER_WARNING
|
212 |
+
);
|
213 |
+
}
|
214 |
+
|
215 |
+
$sendmail_command_parts = explode(' ', $sendmail_command, 2);
|
216 |
+
|
217 |
+
$sendmail_path = $sendmail_command_parts[0];
|
218 |
+
$sendmail_dir = pathinfo($sendmail_path, PATHINFO_DIRNAME);
|
219 |
+
$sendmail_params = (isset($sendmail_command_parts[1])) ? $sendmail_command_parts[1] : '';
|
220 |
+
|
221 |
+
// Check to see if we can run sendmail via popen
|
222 |
+
$executable = FALSE;
|
223 |
+
$safe_mode = FALSE;
|
224 |
+
|
225 |
+
if (!in_array(strtolower(ini_get('safe_mode')), array('0', '', 'off'))) {
|
226 |
+
$safe_mode = TRUE;
|
227 |
+
$exec_dirs = explode(';', ini_get('safe_mode_exec_dir'));
|
228 |
+
foreach ($exec_dirs as $exec_dir) {
|
229 |
+
if (stripos($sendmail_dir, $exec_dir) !== 0) {
|
230 |
+
continue;
|
231 |
+
}
|
232 |
+
if (file_exists($sendmail_path) && is_executable($sendmail_path)) {
|
233 |
+
$executable = TRUE;
|
234 |
+
}
|
235 |
+
}
|
236 |
+
|
237 |
+
} else {
|
238 |
+
if (file_exists($sendmail_path) && is_executable($sendmail_path)) {
|
239 |
+
$executable = TRUE;
|
240 |
+
}
|
241 |
+
}
|
242 |
+
|
243 |
+
if ($executable) {
|
244 |
+
self::$popen_sendmail = TRUE;
|
245 |
+
} else {
|
246 |
+
self::$convert_crlf = TRUE;
|
247 |
+
if ($safe_mode) {
|
248 |
+
trigger_error(
|
249 |
+
self::compose('The proper fix for sending through qmail is not possible since safe mode is turned on and the sendmail binary is not in one of the paths defined by the safe_mode_exec_dir ini setting'),
|
250 |
+
E_USER_WARNING
|
251 |
+
);
|
252 |
+
trigger_error(
|
253 |
+
self::compose('Trying to fix qmail by converting all \r\n to \n. This will cause invalid (but possibly functioning) email headers to be generated.'),
|
254 |
+
E_USER_WARNING
|
255 |
+
);
|
256 |
+
} else {
|
257 |
+
trigger_error(
|
258 |
+
self::compose('The proper fix for sending through qmail is not possible since the sendmail binary could not be found or is not executable'),
|
259 |
+
E_USER_WARNING
|
260 |
+
);
|
261 |
+
trigger_error(
|
262 |
+
self::compose('Trying to fix qmail by converting all \r\n to \n. This will cause invalid (but possibly functioning) email headers to be generated.'),
|
263 |
+
E_USER_WARNING
|
264 |
+
);
|
265 |
+
}
|
266 |
+
}
|
267 |
+
}
|
268 |
+
|
269 |
+
|
270 |
+
/**
|
271 |
+
* Returns the fully-qualified domain name of the server
|
272 |
+
*
|
273 |
+
* @internal
|
274 |
+
*
|
275 |
+
* @return string The fully-qualified domain name of the server
|
276 |
+
*/
|
277 |
+
static public function getFQDN()
|
278 |
+
{
|
279 |
+
if (self::$fqdn !== NULL) {
|
280 |
+
return self::$fqdn;
|
281 |
+
}
|
282 |
+
|
283 |
+
if (isset($_ENV['HOST'])) {
|
284 |
+
self::$fqdn = $_ENV['HOST'];
|
285 |
+
}
|
286 |
+
if (strpos(self::$fqdn, '.') === FALSE && isset($_ENV['HOSTNAME'])) {
|
287 |
+
self::$fqdn = $_ENV['HOSTNAME'];
|
288 |
+
}
|
289 |
+
if (strpos(self::$fqdn, '.') === FALSE) {
|
290 |
+
self::$fqdn = php_uname('n');
|
291 |
+
}
|
292 |
+
|
293 |
+
if (strpos(self::$fqdn, '.') === FALSE) {
|
294 |
+
|
295 |
+
$can_exec = !in_array('exec', array_map('trim', explode(',', ini_get('disable_functions')))) && !ini_get('safe_mode');
|
296 |
+
if (fCore::checkOS('linux') && $can_exec) {
|
297 |
+
self::$fqdn = trim(shell_exec('hostname --fqdn'));
|
298 |
+
|
299 |
+
} elseif (fCore::checkOS('windows')) {
|
300 |
+
$shell = new COM('WScript.Shell');
|
301 |
+
$tcpip_key = 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip';
|
302 |
+
try {
|
303 |
+
$domain = $shell->RegRead($tcpip_key . '\Parameters\NV Domain');
|
304 |
+
} catch (com_exception $e) {
|
305 |
+
try {
|
306 |
+
$domain = $shell->RegRead($tcpip_key . '\Parameters\DhcpDomain');
|
307 |
+
} catch (com_exception $e) {
|
308 |
+
try {
|
309 |
+
$adapters = $shell->RegRead($tcpip_key . '\Linkage\Route');
|
310 |
+
foreach ($adapters as $adapter) {
|
311 |
+
if ($adapter[0] != '{') { continue; }
|
312 |
+
try {
|
313 |
+
$domain = $shell->RegRead($tcpip_key . '\Interfaces\\' . $adapter . '\Domain');
|
314 |
+
} catch (com_exception $e) {
|
315 |
+
try {
|
316 |
+
$domain = $shell->RegRead($tcpip_key . '\Interfaces\\' . $adapter . '\DhcpDomain');
|
317 |
+
} catch (com_exception $e) { }
|
318 |
+
}
|
319 |
+
}
|
320 |
+
} catch (com_exception $e) { }
|
321 |
+
}
|
322 |
+
}
|
323 |
+
if (!empty($domain)) {
|
324 |
+
self::$fqdn .= '.' . $domain;
|
325 |
+
}
|
326 |
+
|
327 |
+
} elseif (!fCore::checkOS('windows') && !ini_get('open_basedir') && file_exists('/etc/resolv.conf')) {
|
328 |
+
$output = file_get_contents('/etc/resolv.conf');
|
329 |
+
if (preg_match('#^domain ([a-z0-9_.-]+)#im', $output, $match)) {
|
330 |
+
self::$fqdn .= '.' . $match[1];
|
331 |
+
}
|
332 |
+
}
|
333 |
+
}
|
334 |
+
|
335 |
+
return self::$fqdn;
|
336 |
+
}
|
337 |
+
|
338 |
+
|
339 |
+
/**
|
340 |
+
* Encodes a string to UTF-8 encoded-word
|
341 |
+
*
|
342 |
+
* @param string $content The content to encode
|
343 |
+
* @param integer $first_line_prefix_length The length of any prefix applied to the first line of the encoded word - this allows properly accounting for a header name
|
344 |
+
* @return string The encoded string
|
345 |
+
*/
|
346 |
+
static private function makeEncodedWord($content, $first_line_prefix_length)
|
347 |
+
{
|
348 |
+
// Homogenize the line-endings to CRLF
|
349 |
+
$content = str_replace("\r\n", "\n", $content);
|
350 |
+
$content = str_replace("\r", "\n", $content);
|
351 |
+
$content = str_replace("\n", "\r\n", $content);
|
352 |
+
|
353 |
+
// Encoded word is not required if all characters are ascii
|
354 |
+
if (!preg_match('#[\x80-\xFF]#', $content)) {
|
355 |
+
return $content;
|
356 |
+
}
|
357 |
+
|
358 |
+
// A quick a dirty hex encoding
|
359 |
+
$content = rawurlencode($content);
|
360 |
+
$content = str_replace('=', '%3D', $content);
|
361 |
+
$content = str_replace('%', '=', $content);
|
362 |
+
|
363 |
+
// Decode characters that don't have to be coded
|
364 |
+
$decodings = array(
|
365 |
+
'=20' => '_', '=21' => '!', '=22' => '"', '=23' => '#',
|
366 |
+
'=24' => '$', '=25' => '%', '=26' => '&', '=27' => "'",
|
367 |
+
'=28' => '(', '=29' => ')', '=2A' => '*', '=2B' => '+',
|
368 |
+
'=2C' => ',', '=2D' => '-', '=2E' => '.', '=2F' => '/',
|
369 |
+
'=3A' => ':', '=3B' => ';', '=3C' => '<', '=3E' => '>',
|
370 |
+
'=40' => '@', '=5B' => '[', '=5C' => '\\', '=5D' => ']',
|
371 |
+
'=5E' => '^', '=60' => '`', '=7B' => '{', '=7C' => '|',
|
372 |
+
'=7D' => '}', '=7E' => '~', ' ' => '_'
|
373 |
+
);
|
374 |
+
|
375 |
+
$content = strtr($content, $decodings);
|
376 |
+
|
377 |
+
$length = strlen($content);
|
378 |
+
|
379 |
+
$prefix = '=?utf-8?Q?';
|
380 |
+
$suffix = '?=';
|
381 |
+
|
382 |
+
$prefix_length = 10;
|
383 |
+
$suffix_length = 2;
|
384 |
+
|
385 |
+
// This loop goes through and ensures we are wrapping by 75 chars
|
386 |
+
// including the encoded word delimiters
|
387 |
+
$output = $prefix;
|
388 |
+
$line_length = $prefix_length + $first_line_prefix_length;
|
389 |
+
|
390 |
+
for ($i=0; $i<$length; $i++) {
|
391 |
+
|
392 |
+
// Get info about the next character
|
393 |
+
$char_length = ($content[$i] == '=') ? 3 : 1;
|
394 |
+
$char = $content[$i];
|
395 |
+
if ($char_length == 3) {
|
396 |
+
$char .= $content[$i+1] . $content[$i+2];
|
397 |
+
}
|
398 |
+
|
399 |
+
// If we have too long a line, wrap it
|
400 |
+
if ($line_length + $suffix_length + $char_length > 75) {
|
401 |
+
$output .= $suffix . "\r\n " . $prefix;
|
402 |
+
$line_length = $prefix_length + 2;
|
403 |
+
}
|
404 |
+
|
405 |
+
// Add the character
|
406 |
+
$output .= $char;
|
407 |
+
|
408 |
+
// Figure out how much longer the line is
|
409 |
+
$line_length += $char_length;
|
410 |
+
|
411 |
+
// Skip characters if we have an encoded character
|
412 |
+
$i += $char_length-1;
|
413 |
+
}
|
414 |
+
|
415 |
+
if (substr($output, -2) != $suffix) {
|
416 |
+
$output .= $suffix;
|
417 |
+
}
|
418 |
+
|
419 |
+
return $output;
|
420 |
+
}
|
421 |
+
|
422 |
+
|
423 |
+
/**
|
424 |
+
* Resets the configuration of the class
|
425 |
+
*
|
426 |
+
* @internal
|
427 |
+
*
|
428 |
+
* @return void
|
429 |
+
*/
|
430 |
+
static public function reset()
|
431 |
+
{
|
432 |
+
self::$convert_crlf = FALSE;
|
433 |
+
self::$fqdn = NULL;
|
434 |
+
self::$popen_sendmail = FALSE;
|
435 |
+
}
|
436 |
+
|
437 |
+
|
438 |
+
/**
|
439 |
+
* Returns `TRUE` for non-empty strings, numbers, objects, empty numbers and string-like numbers (such as `0`, `0.0`, `'0'`)
|
440 |
+
*
|
441 |
+
* @param mixed $value The value to check
|
442 |
+
* @return boolean If the value is string-like
|
443 |
+
*/
|
444 |
+
static protected function stringlike($value)
|
445 |
+
{
|
446 |
+
if ((!is_string($value) && !is_object($value) && !is_numeric($value)) || !strlen(trim($value))) {
|
447 |
+
return FALSE;
|
448 |
+
}
|
449 |
+
|
450 |
+
return TRUE;
|
451 |
+
}
|
452 |
+
|
453 |
+
|
454 |
+
/**
|
455 |
+
* Takes a block of text, unindents it and replaces {CONSTANT} tokens with the constant's value
|
456 |
+
*
|
457 |
+
* @param string $text The text to unindent and replace constants in
|
458 |
+
* @return string The unindented text
|
459 |
+
*/
|
460 |
+
static public function unindentExpand($text)
|
461 |
+
{
|
462 |
+
$text = preg_replace('#^[ \t]*\n|\n[ \t]*$#D', '', $text);
|
463 |
+
|
464 |
+
if (preg_match('#^[ \t]+(?=\S)#m', $text, $match)) {
|
465 |
+
$text = preg_replace('#^' . preg_quote($match[0]) . '#m', '', $text);
|
466 |
+
}
|
467 |
+
|
468 |
+
preg_match_all('#\{([a-z][a-z0-9_]*)\}#i', $text, $constants, PREG_SET_ORDER);
|
469 |
+
foreach ($constants as $constant) {
|
470 |
+
if (!defined($constant[1])) { continue; }
|
471 |
+
$text = preg_replace('#' . preg_quote($constant[0], '#') . '#', constant($constant[1]), $text, 1);
|
472 |
+
}
|
473 |
+
|
474 |
+
return $text;
|
475 |
+
}
|
476 |
+
|
477 |
+
|
478 |
+
/**
|
479 |
+
* The file contents to attach
|
480 |
+
*
|
481 |
+
* @var array
|
482 |
+
*/
|
483 |
+
private $attachments = array();
|
484 |
+
|
485 |
+
/**
|
486 |
+
* The email address(es) to BCC to
|
487 |
+
*
|
488 |
+
* @var array
|
489 |
+
*/
|
490 |
+
private $bcc_emails = array();
|
491 |
+
|
492 |
+
/**
|
493 |
+
* The email address to bounce to
|
494 |
+
*
|
495 |
+
* @var string
|
496 |
+
*/
|
497 |
+
private $bounce_to_email = NULL;
|
498 |
+
|
499 |
+
/**
|
500 |
+
* The email address(es) to CC to
|
501 |
+
*
|
502 |
+
* @var array
|
503 |
+
*/
|
504 |
+
private $cc_emails = array();
|
505 |
+
|
506 |
+
/**
|
507 |
+
* Custom headers
|
508 |
+
*
|
509 |
+
* @var array
|
510 |
+
*/
|
511 |
+
private $custom_headers = array();
|
512 |
+
|
513 |
+
/**
|
514 |
+
* The email address being sent from
|
515 |
+
*
|
516 |
+
* @var string
|
517 |
+
*/
|
518 |
+
private $from_email = NULL;
|
519 |
+
|
520 |
+
/**
|
521 |
+
* The HTML body of the email
|
522 |
+
*
|
523 |
+
* @var string
|
524 |
+
*/
|
525 |
+
private $html_body = NULL;
|
526 |
+
|
527 |
+
/**
|
528 |
+
* The Message-ID header for the email
|
529 |
+
*
|
530 |
+
* @var string
|
531 |
+
*/
|
532 |
+
private $message_id = NULL;
|
533 |
+
|
534 |
+
/**
|
535 |
+
* The plaintext body of the email
|
536 |
+
*
|
537 |
+
* @var string
|
538 |
+
*/
|
539 |
+
private $plaintext_body = NULL;
|
540 |
+
|
541 |
+
/**
|
542 |
+
* The recipient's S/MIME PEM certificate filename, used for encryption of the message
|
543 |
+
*
|
544 |
+
* @var string
|
545 |
+
*/
|
546 |
+
private $recipients_smime_cert_file = NULL;
|
547 |
+
|
548 |
+
/**
|
549 |
+
* The files to include as multipart/related
|
550 |
+
*
|
551 |
+
* @var array
|
552 |
+
*/
|
553 |
+
private $related_files = array();
|
554 |
+
|
555 |
+
/**
|
556 |
+
* The email address to reply to
|
557 |
+
*
|
558 |
+
* @var string
|
559 |
+
*/
|
560 |
+
private $reply_to_email = NULL;
|
561 |
+
|
562 |
+
/**
|
563 |
+
* The email address actually sending the email
|
564 |
+
*
|
565 |
+
* @var string
|
566 |
+
*/
|
567 |
+
private $sender_email = NULL;
|
568 |
+
|
569 |
+
/**
|
570 |
+
* The senders's S/MIME PEM certificate filename, used for singing the message
|
571 |
+
*
|
572 |
+
* @var string
|
573 |
+
*/
|
574 |
+
private $senders_smime_cert_file = NULL;
|
575 |
+
|
576 |
+
/**
|
577 |
+
* The senders's S/MIME private key filename, used for singing the message
|
578 |
+
*
|
579 |
+
* @var string
|
580 |
+
*/
|
581 |
+
private $senders_smime_pk_file = NULL;
|
582 |
+
|
583 |
+
/**
|
584 |
+
* The senders's S/MIME private key password, used for singing the message
|
585 |
+
*
|
586 |
+
* @var string
|
587 |
+
*/
|
588 |
+
private $senders_smime_pk_password = NULL;
|
589 |
+
|
590 |
+
/**
|
591 |
+
* If the message should be encrypted using the recipient's S/MIME certificate
|
592 |
+
*
|
593 |
+
* @var boolean
|
594 |
+
*/
|
595 |
+
private $smime_encrypt = FALSE;
|
596 |
+
|
597 |
+
/**
|
598 |
+
* If the message should be signed using the senders's S/MIME private key
|
599 |
+
*
|
600 |
+
* @var boolean
|
601 |
+
*/
|
602 |
+
private $smime_sign = FALSE;
|
603 |
+
|
604 |
+
/**
|
605 |
+
* The subject of the email
|
606 |
+
*
|
607 |
+
* @var string
|
608 |
+
*/
|
609 |
+
private $subject = NULL;
|
610 |
+
|
611 |
+
/**
|
612 |
+
* The email address(es) to send to
|
613 |
+
*
|
614 |
+
* @var array
|
615 |
+
*/
|
616 |
+
private $to_emails = array();
|
617 |
+
|
618 |
+
|
619 |
+
/**
|
620 |
+
* Initializes fEmail for creating message ids
|
621 |
+
*
|
622 |
+
* @return fEmail
|
623 |
+
*/
|
624 |
+
public function __construct()
|
625 |
+
{
|
626 |
+
$this->message_id = '<' . fCryptography::randomString(10, 'hexadecimal') . '.' . time() . '@' . self::getFQDN() . '>';
|
627 |
+
}
|
628 |
+
|
629 |
+
|
630 |
+
/**
|
631 |
+
* All requests that hit this method should be requests for callbacks
|
632 |
+
*
|
633 |
+
* @internal
|
634 |
+
*
|
635 |
+
* @param string $method The method to create a callback for
|
636 |
+
* @return callback The callback for the method requested
|
637 |
+
*/
|
638 |
+
public function __get($method)
|
639 |
+
{
|
640 |
+
return array($this, $method);
|
641 |
+
}
|
642 |
+
|
643 |
+
|
644 |
+
/**
|
645 |
+
* Adds an attachment to the email
|
646 |
+
*
|
647 |
+
* If a duplicate filename is detected, it will be changed to be unique.
|
648 |
+
*
|
649 |
+
* @param string|fFile $contents The contents of the file
|
650 |
+
* @param string $filename The name to give the attachement - optional if `$contents` is an fFile object
|
651 |
+
* @param string $mime_type The mime type of the file - this allows overriding the mime type of the file if incorrectly detected
|
652 |
+
* @return fEmail The email object, to allow for method chaining
|
653 |
+
*/
|
654 |
+
public function addAttachment($contents, $filename=NULL, $mime_type=NULL)
|
655 |
+
{
|
656 |
+
$this->extrapolateFileInfo($contents, $filename, $mime_type);
|
657 |
+
|
658 |
+
while (isset($this->attachments[$filename])) {
|
659 |
+
$filename = $this->generateNewFilename($filename);
|
660 |
+
}
|
661 |
+
|
662 |
+
$this->attachments[$filename] = array(
|
663 |
+
'mime-type' => $mime_type,
|
664 |
+
'contents' => $contents
|
665 |
+
);
|
666 |
+
|
667 |
+
return $this;
|
668 |
+
}
|
669 |
+
|
670 |
+
|
671 |
+
/**
|
672 |
+
* Adds a “related” file to the email, returning the `Content-ID` for use in HTML
|
673 |
+
*
|
674 |
+
* The purpose of a related file is to be able to reference it in part of
|
675 |
+
* the HTML body. Image `src` URLs can reference a related file by starting
|
676 |
+
* the URL with `cid:` and then inserting the `Content-ID`.
|
677 |
+
*
|
678 |
+
* If a duplicate filename is detected, it will be changed to be unique.
|
679 |
+
*
|
680 |
+
* @param string|fFile $contents The contents of the file
|
681 |
+
* @param string $filename The name to give the attachement - optional if `$contents` is an fFile object
|
682 |
+
* @param string $mime_type The mime type of the file - this allows overriding the mime type of the file if incorrectly detected
|
683 |
+
* @return string The fully-formed `cid:` URL for use in HTML `src` attributes
|
684 |
+
*/
|
685 |
+
public function addRelatedFile($contents, $filename=NULL, $mime_type=NULL)
|
686 |
+
{
|
687 |
+
$this->extrapolateFileInfo($contents, $filename, $mime_type);
|
688 |
+
|
689 |
+
while (isset($this->related_files[$filename])) {
|
690 |
+
$filename = $this->generateNewFilename($filename);
|
691 |
+
}
|
692 |
+
|
693 |
+
$cid = count($this->related_files) . '.' . substr($this->message_id, 1, -1);
|
694 |
+
|
695 |
+
$this->related_files[$filename] = array(
|
696 |
+
'mime-type' => $mime_type,
|
697 |
+
'contents' => $contents,
|
698 |
+
'content-id' => '<' . $cid . '>'
|
699 |
+
);
|
700 |
+
|
701 |
+
return 'cid:' . $cid;
|
702 |
+
}
|
703 |
+
|
704 |
+
|
705 |
+
/**
|
706 |
+
* Adds a blind carbon copy (BCC) email recipient
|
707 |
+
*
|
708 |
+
* @param string $email The email address to BCC
|
709 |
+
* @param string $name The recipient's name
|
710 |
+
* @return fEmail The email object, to allow for method chaining
|
711 |
+
*/
|
712 |
+
public function addBCCRecipient($email, $name=NULL)
|
713 |
+
{
|
714 |
+
if (!$email) {
|
715 |
+
return;
|
716 |
+
}
|
717 |
+
|
718 |
+
$this->bcc_emails[] = self::combineNameEmail($name, $email);
|
719 |
+
|
720 |
+
return $this;
|
721 |
+
}
|
722 |
+
|
723 |
+
|
724 |
+
/**
|
725 |
+
* Adds a carbon copy (CC) email recipient
|
726 |
+
*
|
727 |
+
* @param string $email The email address to BCC
|
728 |
+
* @param string $name The recipient's name
|
729 |
+
* @return fEmail The email object, to allow for method chaining
|
730 |
+
*/
|
731 |
+
public function addCCRecipient($email, $name=NULL)
|
732 |
+
{
|
733 |
+
if (!$email) {
|
734 |
+
return;
|
735 |
+
}
|
736 |
+
|
737 |
+
$this->cc_emails[] = self::combineNameEmail($name, $email);
|
738 |
+
|
739 |
+
return $this;
|
740 |
+
}
|
741 |
+
|
742 |
+
|
743 |
+
/**
|
744 |
+
* Allows adding a custom header to the email
|
745 |
+
*
|
746 |
+
* If the method is called multiple times with the same name, the last
|
747 |
+
* value will be used.
|
748 |
+
*
|
749 |
+
* Please note that this class will properly format the header, including
|
750 |
+
* adding the `:` between the name and value and wrapping values that are
|
751 |
+
* too long for a single line.
|
752 |
+
*
|
753 |
+
* @param string $name The name of the header
|
754 |
+
* @param string $value The value of the header
|
755 |
+
* @param array :$headers An associative array of `{name} => {value}`
|
756 |
+
* @return fEmail The email object, to allow for method chaining
|
757 |
+
*/
|
758 |
+
public function addCustomHeader($name, $value=NULL)
|
759 |
+
{
|
760 |
+
if ($value === NULL && is_array($name)) {
|
761 |
+
foreach ($name as $key => $value) {
|
762 |
+
$this->addCustomHeader($key, $value);
|
763 |
+
}
|
764 |
+
return;
|
765 |
+
}
|
766 |
+
|
767 |
+
$lower_name = fUTF8::lower($name);
|
768 |
+
$this->custom_headers[$lower_name] = array($name, $value);
|
769 |
+
|
770 |
+
return $this;
|
771 |
+
}
|
772 |
+
|
773 |
+
|
774 |
+
/**
|
775 |
+
* Adds an email recipient
|
776 |
+
*
|
777 |
+
* @param string $email The email address to send to
|
778 |
+
* @param string $name The recipient's name
|
779 |
+
* @return fEmail The email object, to allow for method chaining
|
780 |
+
*/
|
781 |
+
public function addRecipient($email, $name=NULL)
|
782 |
+
{
|
783 |
+
if (!$email) {
|
784 |
+
return;
|
785 |
+
}
|
786 |
+
|
787 |
+
$this->to_emails[] = self::combineNameEmail($name, $email);
|
788 |
+
|
789 |
+
return $this;
|
790 |
+
}
|
791 |
+
|
792 |
+
|
793 |
+
/**
|
794 |
+
* Takes a multi-address email header and builds it out using an array of emails
|
795 |
+
*
|
796 |
+
* @param string $header The header name without `': '`, the header is non-blank, `': '` will be added
|
797 |
+
* @param array $emails The email addresses for the header
|
798 |
+
* @return string The email header with a trailing `\r\n`
|
799 |
+
*/
|
800 |
+
private function buildMultiAddressHeader($header, $emails)
|
801 |
+
{
|
802 |
+
$header .= ': ';
|
803 |
+
|
804 |
+
$first = TRUE;
|
805 |
+
$line = 1;
|
806 |
+
foreach ($emails as $email) {
|
807 |
+
if ($first) { $first = FALSE; } else { $header .= ', '; }
|
808 |
+
|
809 |
+
// Try to stay within the recommended 78 character line limit
|
810 |
+
$last_crlf_pos = (integer) strrpos($header, "\r\n");
|
811 |
+
if (strlen($header . $email) - $last_crlf_pos > 78) {
|
812 |
+
$header .= "\r\n ";
|
813 |
+
$line++;
|
814 |
+
}
|
815 |
+
|
816 |
+
$header .= trim($email);
|
817 |
+
}
|
818 |
+
|
819 |
+
return $header . "\r\n";
|
820 |
+
}
|
821 |
+
|
822 |
+
|
823 |
+
/**
|
824 |
+
* Removes all To, CC and BCC recipients from the email
|
825 |
+
*
|
826 |
+
* @return fEmail The email object, to allow for method chaining
|
827 |
+
*/
|
828 |
+
public function clearRecipients()
|
829 |
+
{
|
830 |
+
$this->to_emails = array();
|
831 |
+
$this->cc_emails = array();
|
832 |
+
$this->bcc_emails = array();
|
833 |
+
|
834 |
+
return $this;
|
835 |
+
}
|
836 |
+
|
837 |
+
|
838 |
+
/**
|
839 |
+
* Creates a 32-character boundary for a multipart message
|
840 |
+
*
|
841 |
+
* @return string A multipart boundary
|
842 |
+
*/
|
843 |
+
private function createBoundary()
|
844 |
+
{
|
845 |
+
$chars = 'ancdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:-_';
|
846 |
+
$last_index = strlen($chars) - 1;
|
847 |
+
$output = '';
|
848 |
+
|
849 |
+
for ($i = 0; $i < 28; $i++) {
|
850 |
+
$output .= $chars[rand(0, $last_index)];
|
851 |
+
}
|
852 |
+
return $output;
|
853 |
+
}
|
854 |
+
|
855 |
+
|
856 |
+
/**
|
857 |
+
* Builds the body of the email
|
858 |
+
*
|
859 |
+
* @param string $boundary The boundary to use for the top level mime block
|
860 |
+
* @return string The message body to be sent to the mail() function
|
861 |
+
*/
|
862 |
+
private function createBody($boundary)
|
863 |
+
{
|
864 |
+
$boundary_stack = array($boundary);
|
865 |
+
|
866 |
+
$mime_notice = self::compose(
|
867 |
+
"This message has been formatted using MIME. It does not appear that your\r\nemail client supports MIME."
|
868 |
+
);
|
869 |
+
|
870 |
+
$body = '';
|
871 |
+
|
872 |
+
if ($this->html_body || $this->attachments) {
|
873 |
+
$body .= $mime_notice . "\r\n\r\n";
|
874 |
+
}
|
875 |
+
|
876 |
+
if ($this->html_body && $this->related_files && $this->attachments) {
|
877 |
+
$body .= '--' . end($boundary_stack) . "\r\n";
|
878 |
+
$boundary_stack[] = $this->createBoundary();
|
879 |
+
$body .= 'Content-Type: multipart/related; boundary="' . end($boundary_stack) . "\"\r\n\r\n";
|
880 |
+
}
|
881 |
+
|
882 |
+
if ($this->html_body && ($this->attachments || $this->related_files)) {
|
883 |
+
$body .= '--' . end($boundary_stack) . "\r\n";
|
884 |
+
$boundary_stack[] = $this->createBoundary();
|
885 |
+
$body .= 'Content-Type: multipart/alternative; boundary="' . end($boundary_stack) . "\"\r\n\r\n";
|
886 |
+
}
|
887 |
+
|
888 |
+
if ($this->html_body || $this->attachments) {
|
889 |
+
$body .= '--' . end($boundary_stack) . "\r\n";
|
890 |
+
$body .= "Content-Type: text/plain; charset=utf-8\r\n";
|
891 |
+
$body .= "Content-Transfer-Encoding: quoted-printable\r\n\r\n";
|
892 |
+
}
|
893 |
+
|
894 |
+
$body .= $this->makeQuotedPrintable($this->plaintext_body) . "\r\n";
|
895 |
+
|
896 |
+
if ($this->html_body) {
|
897 |
+
$body .= '--' . end($boundary_stack) . "\r\n";
|
898 |
+
$body .= "Content-Type: text/html; charset=utf-8\r\n";
|
899 |
+
$body .= "Content-Transfer-Encoding: quoted-printable\r\n\r\n";
|
900 |
+
$body .= $this->makeQuotedPrintable($this->html_body) . "\r\n";
|
901 |
+
}
|
902 |
+
|
903 |
+
if ($this->related_files) {
|
904 |
+
$body .= '--' . end($boundary_stack) . "--\r\n";
|
905 |
+
array_pop($boundary_stack);
|
906 |
+
|
907 |
+
foreach ($this->related_files as $filename => $file_info) {
|
908 |
+
$body .= '--' . end($boundary_stack) . "\r\n";
|
909 |
+
$body .= 'Content-Type: ' . $file_info['mime-type'] . '; name="' . $filename . "\"\r\n";
|
910 |
+
$body .= "Content-Transfer-Encoding: base64\r\n";
|
911 |
+
$body .= 'Content-ID: ' . $file_info['content-id'] . "\r\n\r\n";
|
912 |
+
$body .= $this->makeBase64($file_info['contents']) . "\r\n";
|
913 |
+
}
|
914 |
+
}
|
915 |
+
|
916 |
+
if ($this->attachments) {
|
917 |
+
|
918 |
+
if ($this->html_body) {
|
919 |
+
$body .= '--' . end($boundary_stack) . "--\r\n";
|
920 |
+
array_pop($boundary_stack);
|
921 |
+
}
|
922 |
+
|
923 |
+
foreach ($this->attachments as $filename => $file_info) {
|
924 |
+
$body .= '--' . end($boundary_stack) . "\r\n";
|
925 |
+
$body .= 'Content-Type: ' . $file_info['mime-type'] . "\r\n";
|
926 |
+
$body .= "Content-Transfer-Encoding: base64\r\n";
|
927 |
+
$body .= 'Content-Disposition: attachment; filename="' . $filename . "\";\r\n\r\n";
|
928 |
+
$body .= $this->makeBase64($file_info['contents']) . "\r\n";
|
929 |
+
}
|
930 |
+
}
|
931 |
+
|
932 |
+
if ($this->html_body || $this->attachments) {
|
933 |
+
$body .= '--' . end($boundary_stack) . "--\r\n";
|
934 |
+
array_pop($boundary_stack);
|
935 |
+
}
|
936 |
+
|
937 |
+
return $body;
|
938 |
+
}
|
939 |
+
|
940 |
+
|
941 |
+
/**
|
942 |
+
* Builds the headers for the email
|
943 |
+
*
|
944 |
+
* @param string $boundary The boundary to use for the top level mime block
|
945 |
+
* @param string $message_id The message id for the message
|
946 |
+
* @return string The headers to be sent to the [http://php.net/function.mail mail()] function
|
947 |
+
*/
|
948 |
+
private function createHeaders($boundary, $message_id)
|
949 |
+
{
|
950 |
+
$headers = '';
|
951 |
+
|
952 |
+
if ($this->cc_emails) {
|
953 |
+
$headers .= $this->buildMultiAddressHeader("Cc", $this->cc_emails);
|
954 |
+
}
|
955 |
+
|
956 |
+
if ($this->bcc_emails) {
|
957 |
+
$headers .= $this->buildMultiAddressHeader("Bcc", $this->bcc_emails);
|
958 |
+
}
|
959 |
+
|
960 |
+
$headers .= "From: " . trim($this->from_email) . "\r\n";
|
961 |
+
|
962 |
+
if ($this->reply_to_email) {
|
963 |
+
$headers .= "Reply-To: " . trim($this->reply_to_email) . "\r\n";
|
964 |
+
}
|
965 |
+
|
966 |
+
if ($this->sender_email) {
|
967 |
+
$headers .= "Sender: " . trim($this->sender_email) . "\r\n";
|
968 |
+
}
|
969 |
+
|
970 |
+
foreach ($this->custom_headers as $header_info) {
|
971 |
+
$header_prefix = $header_info[0] . ': ';
|
972 |
+
$headers .= $header_prefix . self::makeEncodedWord($header_info[1], strlen($header_prefix)) . "\r\n";
|
973 |
+
}
|
974 |
+
|
975 |
+
$headers .= "Message-ID: " . $message_id . "\r\n";
|
976 |
+
$headers .= "MIME-Version: 1.0\r\n";
|
977 |
+
|
978 |
+
if (!$this->html_body && !$this->attachments) {
|
979 |
+
$headers .= "Content-Type: text/plain; charset=utf-8\r\n";
|
980 |
+
$headers .= "Content-Transfer-Encoding: quoted-printable\r\n";
|
981 |
+
|
982 |
+
} elseif ($this->html_body && !$this->attachments) {
|
983 |
+
if ($this->related_files) {
|
984 |
+
$headers .= 'Content-Type: multipart/related; boundary="' . $boundary . "\"\r\n";
|
985 |
+
} else {
|
986 |
+
$headers .= 'Content-Type: multipart/alternative; boundary="' . $boundary . "\"\r\n";
|
987 |
+
}
|
988 |
+
|
989 |
+
} elseif ($this->attachments) {
|
990 |
+
$headers .= 'Content-Type: multipart/mixed; boundary="' . $boundary . "\"\r\n";
|
991 |
+
}
|
992 |
+
|
993 |
+
return $headers . "\r\n";
|
994 |
+
}
|
995 |
+
|
996 |
+
|
997 |
+
/**
|
998 |
+
* Takes the body of the message and processes it with S/MIME
|
999 |
+
*
|
1000 |
+
* @param string $to The recipients being sent to
|
1001 |
+
* @param string $subject The subject of the email
|
1002 |
+
* @param string $headers The headers for the message
|
1003 |
+
* @param string $body The message body
|
1004 |
+
* @return array `0` => The message headers, `1` => The message body
|
1005 |
+
*/
|
1006 |
+
private function createSMIMEBody($to, $subject, $headers, $body)
|
1007 |
+
{
|
1008 |
+
if (!$this->smime_encrypt && !$this->smime_sign) {
|
1009 |
+
return array($headers, $body);
|
1010 |
+
}
|
1011 |
+
|
1012 |
+
$plaintext_file = tempnam('', '__fEmail_');
|
1013 |
+
$ciphertext_file = tempnam('', '__fEmail_');
|
1014 |
+
|
1015 |
+
$headers_array = array(
|
1016 |
+
'To' => $to,
|
1017 |
+
'Subject' => $subject
|
1018 |
+
);
|
1019 |
+
|
1020 |
+
preg_match_all('#^([\w\-]+):\s+([^\n]+\n( [^\n]+\n)*)#im', $headers, $header_matches, PREG_SET_ORDER);
|
1021 |
+
foreach ($header_matches as $header_match) {
|
1022 |
+
$headers_array[$header_match[1]] = trim($header_match[2]);
|
1023 |
+
}
|
1024 |
+
|
1025 |
+
$body_headers = "";
|
1026 |
+
if (isset($headers_array['Content-Type'])) {
|
1027 |
+
$body_headers .= 'Content-Type: ' . $headers_array['Content-Type'] . "\r\n";
|
1028 |
+
}
|
1029 |
+
if (isset($headers_array['Content-Transfer-Encoding'])) {
|
1030 |
+
$body_headers .= 'Content-Transfer-Encoding: ' . $headers_array['Content-Transfer-Encoding'] . "\r\n";
|
1031 |
+
}
|
1032 |
+
|
1033 |
+
if ($body_headers) {
|
1034 |
+
$body = $body_headers . "\r\n" . $body;
|
1035 |
+
}
|
1036 |
+
|
1037 |
+
file_put_contents($plaintext_file, $body);
|
1038 |
+
file_put_contents($ciphertext_file, '');
|
1039 |
+
|
1040 |
+
// Set up the neccessary S/MIME resources
|
1041 |
+
if ($this->smime_sign) {
|
1042 |
+
$senders_smime_cert = file_get_contents($this->senders_smime_cert_file);
|
1043 |
+
$senders_private_key = openssl_pkey_get_private(
|
1044 |
+
file_get_contents($this->senders_smime_pk_file),
|
1045 |
+
$this->senders_smime_pk_password
|
1046 |
+
);
|
1047 |
+
|
1048 |
+
if ($senders_private_key === FALSE) {
|
1049 |
+
throw new fValidationException(
|
1050 |
+
"The sender's S/MIME private key password specified does not appear to be valid for the private key"
|
1051 |
+
);
|
1052 |
+
}
|
1053 |
+
}
|
1054 |
+
|
1055 |
+
if ($this->smime_encrypt) {
|
1056 |
+
$recipients_smime_cert = file_get_contents($this->recipients_smime_cert_file);
|
1057 |
+
}
|
1058 |
+
|
1059 |
+
|
1060 |
+
// If we are going to sign and encrypt, the best way is to sign, encrypt and then sign again
|
1061 |
+
if ($this->smime_encrypt && $this->smime_sign) {
|
1062 |
+
openssl_pkcs7_sign($plaintext_file, $ciphertext_file, $senders_smime_cert, $senders_private_key, array());
|
1063 |
+
openssl_pkcs7_encrypt($ciphertext_file, $plaintext_file, $recipients_smime_cert, array(), NULL, OPENSSL_CIPHER_RC2_128);
|
1064 |
+
openssl_pkcs7_sign($plaintext_file, $ciphertext_file, $senders_smime_cert, $senders_private_key, $headers_array);
|
1065 |
+
|
1066 |
+
} elseif ($this->smime_sign) {
|
1067 |
+
openssl_pkcs7_sign($plaintext_file, $ciphertext_file, $senders_smime_cert, $senders_private_key, $headers_array);
|
1068 |
+
|
1069 |
+
} elseif ($this->smime_encrypt) {
|
1070 |
+
openssl_pkcs7_encrypt($plaintext_file, $ciphertext_file, $recipients_smime_cert, $headers_array, NULL, OPENSSL_CIPHER_RC2_128);
|
1071 |
+
}
|
1072 |
+
|
1073 |
+
// It seems that the contents of the ciphertext is not always \r\n line breaks
|
1074 |
+
$message = file_get_contents($ciphertext_file);
|
1075 |
+
$message = str_replace("\r\n", "\n", $message);
|
1076 |
+
$message = str_replace("\r", "\n", $message);
|
1077 |
+
$message = str_replace("\n", "\r\n", $message);
|
1078 |
+
|
1079 |
+
list($new_headers, $new_body) = explode("\r\n\r\n", $message, 2);
|
1080 |
+
|
1081 |
+
$new_headers = preg_replace('#^To:[^\n]+\n( [^\n]+\n)*#mi', '', $new_headers);
|
1082 |
+
$new_headers = preg_replace('#^Subject:[^\n]+\n( [^\n]+\n)*#mi', '', $new_headers);
|
1083 |
+
$new_headers = preg_replace("#^MIME-Version: 1.0\r?\n#mi", '', $new_headers, 1);
|
1084 |
+
$new_headers = preg_replace('#^Content-Type:\s+' . preg_quote($headers_array['Content-Type'], '#') . "\r?\n#mi", '', $new_headers);
|
1085 |
+
$new_headers = preg_replace('#^Content-Transfer-Encoding:\s+' . preg_quote($headers_array['Content-Transfer-Encoding'], '#') . "\r?\n#mi", '', $new_headers);
|
1086 |
+
|
1087 |
+
unlink($plaintext_file);
|
1088 |
+
unlink($ciphertext_file);
|
1089 |
+
|
1090 |
+
if ($this->smime_sign) {
|
1091 |
+
openssl_pkey_free($senders_private_key);
|
1092 |
+
}
|
1093 |
+
|
1094 |
+
return array($new_headers, $new_body);
|
1095 |
+
}
|
1096 |
+
|
1097 |
+
|
1098 |
+
/**
|
1099 |
+
* Sets the email to be encrypted with S/MIME
|
1100 |
+
*
|
1101 |
+
* @param string $recipients_smime_cert_file The file path to the PEM-encoded S/MIME certificate for the recipient
|
1102 |
+
* @return fEmail The email object, to allow for method chaining
|
1103 |
+
*/
|
1104 |
+
public function encrypt($recipients_smime_cert_file)
|
1105 |
+
{
|
1106 |
+
if (!extension_loaded('openssl')) {
|
1107 |
+
throw new fEnvironmentException(
|
1108 |
+
'S/MIME encryption was requested for an email, but the %s extension is not installed',
|
1109 |
+
'openssl'
|
1110 |
+
);
|
1111 |
+
}
|
1112 |
+
|
1113 |
+
if (!self::stringlike($recipients_smime_cert_file)) {
|
1114 |
+
throw new fProgrammerException(
|
1115 |
+
"The recipient's S/MIME certificate filename specified, %s, does not appear to be a valid filename",
|
1116 |
+
$recipients_smime_cert_file
|
1117 |
+
);
|
1118 |
+
}
|
1119 |
+
|
1120 |
+
$this->smime_encrypt = TRUE;
|
1121 |
+
$this->recipients_smime_cert_file = $recipients_smime_cert_file;
|
1122 |
+
|
1123 |
+
return $this;
|
1124 |
+
}
|
1125 |
+
|
1126 |
+
|
1127 |
+
/**
|
1128 |
+
* Extracts just the email addresses from an array of strings containing an
|
1129 |
+
* <email@address.com> or "Name" <email@address.com> combination.
|
1130 |
+
*
|
1131 |
+
* @param array $list The list of email or name/email to extract from
|
1132 |
+
* @return array The email addresses
|
1133 |
+
*/
|
1134 |
+
private function extractEmails($list)
|
1135 |
+
{
|
1136 |
+
$output = array();
|
1137 |
+
foreach ($list as $email) {
|
1138 |
+
if (preg_match(self::NAME_EMAIL_REGEX, $email, $match)) {
|
1139 |
+
$output[] = $match[2];
|
1140 |
+
} else {
|
1141 |
+
preg_match(self::EMAIL_REGEX, $email, $match);
|
1142 |
+
$output[] = $match[0];
|
1143 |
+
}
|
1144 |
+
}
|
1145 |
+
return $output;
|
1146 |
+
}
|
1147 |
+
|
1148 |
+
|
1149 |
+
/**
|
1150 |
+
* Extracts the filename and mime-type from an fFile object
|
1151 |
+
*
|
1152 |
+
* @param string|fFile &$contents The file to extrapolate the info from
|
1153 |
+
* @param string &$filename The filename to use for the file
|
1154 |
+
* @param string &$mime_type The mime type of the file
|
1155 |
+
* @return void
|
1156 |
+
*/
|
1157 |
+
private function extrapolateFileInfo(&$contents, &$filename, &$mime_type)
|
1158 |
+
{
|
1159 |
+
if ($contents instanceof fFile) {
|
1160 |
+
if ($filename === NULL) {
|
1161 |
+
$filename = $contents->getName();
|
1162 |
+
}
|
1163 |
+
if ($mime_type === NULL) {
|
1164 |
+
$mime_type = $contents->getMimeType();
|
1165 |
+
}
|
1166 |
+
$contents = $contents->read();
|
1167 |
+
|
1168 |
+
} else {
|
1169 |
+
if (!self::stringlike($filename)) {
|
1170 |
+
throw new fProgrammerException(
|
1171 |
+
'The filename specified, %s, does not appear to be a valid filename',
|
1172 |
+
$filename
|
1173 |
+
);
|
1174 |
+
}
|
1175 |
+
|
1176 |
+
$filename = (string) $filename;
|
1177 |
+
|
1178 |
+
if ($mime_type === NULL) {
|
1179 |
+
$mime_type = fFile::determineMimeType($filename, $contents);
|
1180 |
+
}
|
1181 |
+
}
|
1182 |
+
}
|
1183 |
+
|
1184 |
+
|
1185 |
+
/**
|
1186 |
+
* Generates a new filename in an attempt to create a unique name
|
1187 |
+
*
|
1188 |
+
* @param string $filename The filename to generate another name for
|
1189 |
+
* @return string The newly generated filename
|
1190 |
+
*/
|
1191 |
+
private function generateNewFilename($filename)
|
1192 |
+
{
|
1193 |
+
$filename_info = fFilesystem::getPathInfo($filename);
|
1194 |
+
if (preg_match('#_copy(\d+)($|\.)#D', $filename_info['filename'], $match)) {
|
1195 |
+
$i = $match[1] + 1;
|
1196 |
+
} else {
|
1197 |
+
$i = 1;
|
1198 |
+
}
|
1199 |
+
$extension = ($filename_info['extension']) ? '.' . $filename_info['extension'] : '';
|
1200 |
+
return preg_replace('#_copy\d+$#D', '', $filename_info['filename']) . '_copy' . $i . $extension;
|
1201 |
+
}
|
1202 |
+
|
1203 |
+
|
1204 |
+
/**
|
1205 |
+
* Loads the plaintext version of the email body from a file and applies replacements
|
1206 |
+
*
|
1207 |
+
* The should contain either ASCII or UTF-8 encoded text. Please see
|
1208 |
+
* http://flourishlib.com/docs/UTF-8 for more information.
|
1209 |
+
*
|
1210 |
+
* @throws fValidationException When no file was specified, the file does not exist or the path specified is not a file
|
1211 |
+
*
|
1212 |
+
* @param string|fFile $file The plaintext version of the email body
|
1213 |
+
* @param array $replacements The method will search the contents of the file for each key and replace it with the corresponding value
|
1214 |
+
* @return fEmail The email object, to allow for method chaining
|
1215 |
+
*/
|
1216 |
+
public function loadBody($file, $replacements=array())
|
1217 |
+
{
|
1218 |
+
if (!$file instanceof fFile) {
|
1219 |
+
$file = new fFile($file);
|
1220 |
+
}
|
1221 |
+
|
1222 |
+
$plaintext = $file->read();
|
1223 |
+
if ($replacements) {
|
1224 |
+
$plaintext = strtr($plaintext, $replacements);
|
1225 |
+
}
|
1226 |
+
|
1227 |
+
$this->plaintext_body = $plaintext;
|
1228 |
+
|
1229 |
+
return $this;
|
1230 |
+
}
|
1231 |
+
|
1232 |
+
|
1233 |
+
/**
|
1234 |
+
* Loads the plaintext version of the email body from a file and applies replacements
|
1235 |
+
*
|
1236 |
+
* The should contain either ASCII or UTF-8 encoded text. Please see
|
1237 |
+
* http://flourishlib.com/docs/UTF-8 for more information.
|
1238 |
+
*
|
1239 |
+
* @throws fValidationException When no file was specified, the file does not exist or the path specified is not a file
|
1240 |
+
*
|
1241 |
+
* @param string|fFile $file The plaintext version of the email body
|
1242 |
+
* @param array $replacements The method will search the contents of the file for each key and replace it with the corresponding value
|
1243 |
+
* @return fEmail The email object, to allow for method chaining
|
1244 |
+
*/
|
1245 |
+
public function loadHTMLBody($file, $replacements=array())
|
1246 |
+
{
|
1247 |
+
if (!$file instanceof fFile) {
|
1248 |
+
$file = new fFile($file);
|
1249 |
+
}
|
1250 |
+
|
1251 |
+
$html = $file->read();
|
1252 |
+
if ($replacements) {
|
1253 |
+
$html = strtr($html, $replacements);
|
1254 |
+
}
|
1255 |
+
|
1256 |
+
$this->html_body = $html;
|
1257 |
+
|
1258 |
+
return $this;
|
1259 |
+
}
|
1260 |
+
|
1261 |
+
|
1262 |
+
/**
|
1263 |
+
* Encodes a string to base64
|
1264 |
+
*
|
1265 |
+
* @param string $content The content to encode
|
1266 |
+
* @return string The encoded string
|
1267 |
+
*/
|
1268 |
+
private function makeBase64($content)
|
1269 |
+
{
|
1270 |
+
return chunk_split(base64_encode($content));
|
1271 |
+
}
|
1272 |
+
|
1273 |
+
|
1274 |
+
/**
|
1275 |
+
* Encodes a string to quoted-printable, properly handles UTF-8
|
1276 |
+
*
|
1277 |
+
* @param string $content The content to encode
|
1278 |
+
* @return string The encoded string
|
1279 |
+
*/
|
1280 |
+
private function makeQuotedPrintable($content)
|
1281 |
+
{
|
1282 |
+
// Homogenize the line-endings to CRLF
|
1283 |
+
$content = str_replace("\r\n", "\n", $content);
|
1284 |
+
$content = str_replace("\r", "\n", $content);
|
1285 |
+
$content = str_replace("\n", "\r\n", $content);
|
1286 |
+
|
1287 |
+
// A quick a dirty hex encoding
|
1288 |
+
$content = rawurlencode($content);
|
1289 |
+
$content = str_replace('=', '%3D', $content);
|
1290 |
+
$content = str_replace('%', '=', $content);
|
1291 |
+
|
1292 |
+
// Decode characters that don't have to be coded
|
1293 |
+
$decodings = array(
|
1294 |
+
'=20' => ' ', '=21' => '!', '=22' => '"', '=23' => '#',
|
1295 |
+
'=24' => '$', '=25' => '%', '=26' => '&', '=27' => "'",
|
1296 |
+
'=28' => '(', '=29' => ')', '=2A' => '*', '=2B' => '+',
|
1297 |
+
'=2C' => ',', '=2D' => '-', '=2E' => '.', '=2F' => '/',
|
1298 |
+
'=3A' => ':', '=3B' => ';', '=3C' => '<', '=3E' => '>',
|
1299 |
+
'=3F' => '?', '=40' => '@', '=5B' => '[', '=5C' => '\\',
|
1300 |
+
'=5D' => ']', '=5E' => '^', '=5F' => '_', '=60' => '`',
|
1301 |
+
'=7B' => '{', '=7C' => '|', '=7D' => '}', '=7E' => '~'
|
1302 |
+
);
|
1303 |
+
|
1304 |
+
$content = strtr($content, $decodings);
|
1305 |
+
|
1306 |
+
$output = '';
|
1307 |
+
|
1308 |
+
$length = strlen($content);
|
1309 |
+
|
1310 |
+
// This loop goes through and ensures we are wrapping by 76 chars
|
1311 |
+
$line_length = 0;
|
1312 |
+
for ($i=0; $i<$length; $i++) {
|
1313 |
+
|
1314 |
+
// Get info about the next character
|
1315 |
+
$char_length = ($content[$i] == '=') ? 3 : 1;
|
1316 |
+
$char = $content[$i];
|
1317 |
+
if ($char_length == 3) {
|
1318 |
+
$char .= $content[$i+1] . $content[$i+2];
|
1319 |
+
}
|
1320 |
+
|
1321 |
+
// Skip characters if we have an encoded character, this must be
|
1322 |
+
// done before checking for whitespace at the beginning and end of
|
1323 |
+
// lines or else characters in the content will be skipped
|
1324 |
+
$i += $char_length-1;
|
1325 |
+
|
1326 |
+
// Spaces and tabs at the beginning and ending of lines have to be encoded
|
1327 |
+
$begining_or_end = $line_length > 69 || $line_length == 0;
|
1328 |
+
$tab_or_space = $char == ' ' || $char == "\t";
|
1329 |
+
if ($begining_or_end && $tab_or_space) {
|
1330 |
+
$char_length = 3;
|
1331 |
+
$char = ($char == ' ') ? '=20' : '=09';
|
1332 |
+
}
|
1333 |
+
|
1334 |
+
// If we have too long a line, wrap it
|
1335 |
+
if ($char != "\r" && $char != "\n" && $line_length + $char_length > 75) {
|
1336 |
+
$output .= "=\r\n";
|
1337 |
+
$line_length = 0;
|
1338 |
+
}
|
1339 |
+
|
1340 |
+
// Add the character
|
1341 |
+
$output .= $char;
|
1342 |
+
|
1343 |
+
// Figure out how much longer the line is now
|
1344 |
+
if ($char == "\r" || $char == "\n") {
|
1345 |
+
$line_length = 0;
|
1346 |
+
} else {
|
1347 |
+
$line_length += $char_length;
|
1348 |
+
}
|
1349 |
+
}
|
1350 |
+
|
1351 |
+
return $output;
|
1352 |
+
}
|
1353 |
+
|
1354 |
+
|
1355 |
+
/**
|
1356 |
+
* Sends the email
|
1357 |
+
*
|
1358 |
+
* The return value is the message id, which should be included as the
|
1359 |
+
* `Message-ID` header of the email. While almost all SMTP servers will not
|
1360 |
+
* modify this value, testing has indicated at least one (smtp.live.com
|
1361 |
+
* for Windows Live Mail) does.
|
1362 |
+
*
|
1363 |
+
* @throws fValidationException When ::validate() throws an exception
|
1364 |
+
*
|
1365 |
+
* @param fSMTP $connection The SMTP connection to send the message over
|
1366 |
+
* @return string The message id for the message - see method description for details
|
1367 |
+
*/
|
1368 |
+
public function send($connection=NULL)
|
1369 |
+
{
|
1370 |
+
$this->validate();
|
1371 |
+
|
1372 |
+
// The mail() function on Windows doesn't support names in headers so
|
1373 |
+
// we must strip them down to just the email address
|
1374 |
+
if ($connection === NULL && fCore::checkOS('windows')) {
|
1375 |
+
$vars = array('bcc_emails', 'bounce_to_email', 'cc_emails', 'from_email', 'reply_to_email', 'sender_email', 'to_emails');
|
1376 |
+
foreach ($vars as $var) {
|
1377 |
+
if (!is_array($this->$var)) {
|
1378 |
+
if (preg_match(self::NAME_EMAIL_REGEX, $this->$var, $match)) {
|
1379 |
+
$this->$var = $match[2];
|
1380 |
+
}
|
1381 |
+
} else {
|
1382 |
+
$new_emails = array();
|
1383 |
+
foreach ($this->$var as $email) {
|
1384 |
+
if (preg_match(self::NAME_EMAIL_REGEX, $email, $match)) {
|
1385 |
+
$email = $match[2];
|
1386 |
+
}
|
1387 |
+
$new_emails[] = $email;
|
1388 |
+
}
|
1389 |
+
$this->$var = $new_emails;
|
1390 |
+
}
|
1391 |
+
}
|
1392 |
+
}
|
1393 |
+
|
1394 |
+
$to = substr(trim($this->buildMultiAddressHeader("To", $this->to_emails)), 4);
|
1395 |
+
|
1396 |
+
$top_level_boundary = $this->createBoundary();
|
1397 |
+
$headers = $this->createHeaders($top_level_boundary, $this->message_id);
|
1398 |
+
|
1399 |
+
$subject = str_replace(array("\r", "\n"), '', $this->subject);
|
1400 |
+
$subject = self::makeEncodedWord($subject, 9);
|
1401 |
+
|
1402 |
+
$body = $this->createBody($top_level_boundary);
|
1403 |
+
|
1404 |
+
if ($this->smime_encrypt || $this->smime_sign) {
|
1405 |
+
list($headers, $body) = $this->createSMIMEBody($to, $subject, $headers, $body);
|
1406 |
+
}
|
1407 |
+
|
1408 |
+
// Remove extra line breaks
|
1409 |
+
$headers = trim($headers);
|
1410 |
+
$body = trim($body);
|
1411 |
+
|
1412 |
+
if ($connection) {
|
1413 |
+
$to_emails = $this->extractEmails($this->to_emails);
|
1414 |
+
$to_emails = array_merge($to_emails, $this->extractEmails($this->cc_emails));
|
1415 |
+
$to_emails = array_merge($to_emails, $this->extractEmails($this->bcc_emails));
|
1416 |
+
$from = $this->bounce_to_email ? $this->bounce_to_email : current($this->extractEmails(array($this->from_email)));
|
1417 |
+
$connection->send($from, $to_emails, "To: " . $to . "\r\nSubject: " . $subject . "\r\n" . $headers, $body);
|
1418 |
+
return $this->message_id;
|
1419 |
+
}
|
1420 |
+
|
1421 |
+
// Sendmail when not in safe mode will allow you to set the envelope from address via the -f parameter
|
1422 |
+
$parameters = NULL;
|
1423 |
+
if (!fCore::checkOS('windows') && $this->bounce_to_email) {
|
1424 |
+
preg_match(self::EMAIL_REGEX, $this->bounce_to_email, $matches);
|
1425 |
+
$parameters = '-f ' . $matches[0];
|
1426 |
+
|
1427 |
+
// Windows takes the Return-Path email from the sendmail_from ini setting
|
1428 |
+
} elseif (fCore::checkOS('windows') && $this->bounce_to_email) {
|
1429 |
+
$old_sendmail_from = ini_get('sendmail_from');
|
1430 |
+
preg_match(self::EMAIL_REGEX, $this->bounce_to_email, $matches);
|
1431 |
+
ini_set('sendmail_from', $matches[0]);
|
1432 |
+
}
|
1433 |
+
|
1434 |
+
// This is a gross qmail fix that is a last resort
|
1435 |
+
if (self::$popen_sendmail || self::$convert_crlf) {
|
1436 |
+
$to = str_replace("\r\n", "\n", $to);
|
1437 |
+
$subject = str_replace("\r\n", "\n", $subject);
|
1438 |
+
$body = str_replace("\r\n", "\n", $body);
|
1439 |
+
$headers = str_replace("\r\n", "\n", $headers);
|
1440 |
+
}
|
1441 |
+
|
1442 |
+
// If the user is using qmail and wants to try to fix the \r\r\n line break issue
|
1443 |
+
if (self::$popen_sendmail) {
|
1444 |
+
$sendmail_command = ini_get('sendmail_path');
|
1445 |
+
if ($parameters) {
|
1446 |
+
$sendmail_command .= ' ' . $parameters;
|
1447 |
+
}
|
1448 |
+
|
1449 |
+
$sendmail_process = popen($sendmail_command, 'w');
|
1450 |
+
fprintf($sendmail_process, "To: %s\n", $to);
|
1451 |
+
fprintf($sendmail_process, "Subject: %s\n", $subject);
|
1452 |
+
if ($headers) {
|
1453 |
+
fprintf($sendmail_process, "%s\n", $headers);
|
1454 |
+
}
|
1455 |
+
fprintf($sendmail_process, "\n%s\n", $body);
|
1456 |
+
$error = pclose($sendmail_process);
|
1457 |
+
|
1458 |
+
// This is the normal way to send mail
|
1459 |
+
} else {
|
1460 |
+
// On Windows, mail() sends directly to an SMTP server and will
|
1461 |
+
// strip a leading . from the body
|
1462 |
+
if (fCore::checkOS('windows')) {
|
1463 |
+
$body = preg_replace('#^\.#', '..', $body);
|
1464 |
+
}
|
1465 |
+
|
1466 |
+
if ($parameters) {
|
1467 |
+
$error = !mail($to, $subject, $body, $headers, $parameters);
|
1468 |
+
} else {
|
1469 |
+
$error = !mail($to, $subject, $body, $headers);
|
1470 |
+
}
|
1471 |
+
}
|
1472 |
+
|
1473 |
+
if (fCore::checkOS('windows') && $this->bounce_to_email) {
|
1474 |
+
ini_set('sendmail_from', $old_sendmail_from);
|
1475 |
+
}
|
1476 |
+
|
1477 |
+
if ($error) {
|
1478 |
+
throw new fConnectivityException(
|
1479 |
+
'An error occured while trying to send the email entitled %s',
|
1480 |
+
$this->subject
|
1481 |
+
);
|
1482 |
+
}
|
1483 |
+
|
1484 |
+
return $this->message_id;
|
1485 |
+
}
|
1486 |
+
|
1487 |
+
|
1488 |
+
/**
|
1489 |
+
* Sets the plaintext version of the email body
|
1490 |
+
*
|
1491 |
+
* This method accepts either ASCII or UTF-8 encoded text. Please see
|
1492 |
+
* http://flourishlib.com/docs/UTF-8 for more information.
|
1493 |
+
*
|
1494 |
+
* @param string $plaintext The plaintext version of the email body
|
1495 |
+
* @param boolean $unindent_expand_constants If this is `TRUE`, the body will be unindented as much as possible and {CONSTANT_NAME} will be replaced with the value of the constant
|
1496 |
+
* @return fEmail The email object, to allow for method chaining
|
1497 |
+
*/
|
1498 |
+
public function setBody($plaintext, $unindent_expand_constants=FALSE)
|
1499 |
+
{
|
1500 |
+
if ($unindent_expand_constants) {
|
1501 |
+
$plaintext = self::unindentExpand($plaintext);
|
1502 |
+
}
|
1503 |
+
|
1504 |
+
$this->plaintext_body = $plaintext;
|
1505 |
+
|
1506 |
+
return $this;
|
1507 |
+
}
|
1508 |
+
|
1509 |
+
|
1510 |
+
/**
|
1511 |
+
* Adds the email address the email will be bounced to
|
1512 |
+
*
|
1513 |
+
* This email address will be set to the `Return-Path` header.
|
1514 |
+
*
|
1515 |
+
* @param string $email The email address to bounce to
|
1516 |
+
* @return fEmail The email object, to allow for method chaining
|
1517 |
+
*/
|
1518 |
+
public function setBounceToEmail($email)
|
1519 |
+
{
|
1520 |
+
if (ini_get('safe_mode') && !fCore::checkOS('windows')) {
|
1521 |
+
throw new fProgrammerException('It is not possible to set a Bounce-To Email address when safe mode is enabled on a non-Windows server');
|
1522 |
+
}
|
1523 |
+
if (!$email) {
|
1524 |
+
return;
|
1525 |
+
}
|
1526 |
+
|
1527 |
+
$this->bounce_to_email = self::combineNameEmail('', $email);
|
1528 |
+
|
1529 |
+
return $this;
|
1530 |
+
}
|
1531 |
+
|
1532 |
+
|
1533 |
+
/**
|
1534 |
+
* Adds the `From:` email address to the email
|
1535 |
+
*
|
1536 |
+
* @param string $email The email address being sent from
|
1537 |
+
* @param string $name The from email user's name - unfortunately on windows this is ignored
|
1538 |
+
* @return fEmail The email object, to allow for method chaining
|
1539 |
+
*/
|
1540 |
+
public function setFromEmail($email, $name=NULL)
|
1541 |
+
{
|
1542 |
+
if (!$email) {
|
1543 |
+
return;
|
1544 |
+
}
|
1545 |
+
|
1546 |
+
$this->from_email = self::combineNameEmail($name, $email);
|
1547 |
+
|
1548 |
+
return $this;
|
1549 |
+
}
|
1550 |
+
|
1551 |
+
|
1552 |
+
/**
|
1553 |
+
* Sets the HTML version of the email body
|
1554 |
+
*
|
1555 |
+
* This method accepts either ASCII or UTF-8 encoded text. Please see
|
1556 |
+
* http://flourishlib.com/docs/UTF-8 for more information.
|
1557 |
+
*
|
1558 |
+
* @param string $html The HTML version of the email body
|
1559 |
+
* @return fEmail The email object, to allow for method chaining
|
1560 |
+
*/
|
1561 |
+
public function setHTMLBody($html)
|
1562 |
+
{
|
1563 |
+
$this->html_body = $html;
|
1564 |
+
|
1565 |
+
return $this;
|
1566 |
+
}
|
1567 |
+
|
1568 |
+
|
1569 |
+
/**
|
1570 |
+
* Adds the `Reply-To:` email address to the email
|
1571 |
+
*
|
1572 |
+
* @param string $email The email address to reply to
|
1573 |
+
* @param string $name The reply-to email user's name
|
1574 |
+
* @return fEmail The email object, to allow for method chaining
|
1575 |
+
*/
|
1576 |
+
public function setReplyToEmail($email, $name=NULL)
|
1577 |
+
{
|
1578 |
+
if (!$email) {
|
1579 |
+
return;
|
1580 |
+
}
|
1581 |
+
|
1582 |
+
$this->reply_to_email = self::combineNameEmail($name, $email);
|
1583 |
+
|
1584 |
+
return $this;
|
1585 |
+
}
|
1586 |
+
|
1587 |
+
|
1588 |
+
/**
|
1589 |
+
* Adds the `Sender:` email address to the email
|
1590 |
+
*
|
1591 |
+
* The `Sender:` header is used to indicate someone other than the `From:`
|
1592 |
+
* address is actually submitting the message to the network.
|
1593 |
+
*
|
1594 |
+
* @param string $email The email address the message is actually being sent from
|
1595 |
+
* @param string $name The sender email user's name
|
1596 |
+
* @return fEmail The email object, to allow for method chaining
|
1597 |
+
*/
|
1598 |
+
public function setSenderEmail($email, $name=NULL)
|
1599 |
+
{
|
1600 |
+
if (!$email) {
|
1601 |
+
return;
|
1602 |
+
}
|
1603 |
+
|
1604 |
+
$this->sender_email = self::combineNameEmail($name, $email);
|
1605 |
+
|
1606 |
+
return $this;
|
1607 |
+
}
|
1608 |
+
|
1609 |
+
|
1610 |
+
/**
|
1611 |
+
* Sets the subject of the email
|
1612 |
+
*
|
1613 |
+
* This method accepts either ASCII or UTF-8 encoded text. Please see
|
1614 |
+
* http://flourishlib.com/docs/UTF-8 for more information.
|
1615 |
+
*
|
1616 |
+
* @param string $subject The subject of the email
|
1617 |
+
* @return fEmail The email object, to allow for method chaining
|
1618 |
+
*/
|
1619 |
+
public function setSubject($subject)
|
1620 |
+
{
|
1621 |
+
$this->subject = $subject;
|
1622 |
+
|
1623 |
+
return $this;
|
1624 |
+
}
|
1625 |
+
|
1626 |
+
|
1627 |
+
/**
|
1628 |
+
* Sets the email to be signed with S/MIME
|
1629 |
+
*
|
1630 |
+
* @param string $senders_smime_cert_file The file path to the sender's PEM-encoded S/MIME certificate
|
1631 |
+
* @param string $senders_smime_pk_file The file path to the sender's S/MIME private key
|
1632 |
+
* @param string $senders_smime_pk_password The password for the sender's S/MIME private key
|
1633 |
+
* @return fEmail The email object, to allow for method chaining
|
1634 |
+
*/
|
1635 |
+
public function sign($senders_smime_cert_file, $senders_smime_pk_file, $senders_smime_pk_password)
|
1636 |
+
{
|
1637 |
+
if (!extension_loaded('openssl')) {
|
1638 |
+
throw new fEnvironmentException(
|
1639 |
+
'An S/MIME signature was requested for an email, but the %s extension is not installed',
|
1640 |
+
'openssl'
|
1641 |
+
);
|
1642 |
+
}
|
1643 |
+
|
1644 |
+
if (!self::stringlike($senders_smime_cert_file)) {
|
1645 |
+
throw new fProgrammerException(
|
1646 |
+
"The sender's S/MIME certificate file specified, %s, does not appear to be a valid filename",
|
1647 |
+
$senders_smime_cert_file
|
1648 |
+
);
|
1649 |
+
}
|
1650 |
+
if (!file_exists($senders_smime_cert_file) || !is_readable($senders_smime_cert_file)) {
|
1651 |
+
throw new fEnvironmentException(
|
1652 |
+
"The sender's S/MIME certificate file specified, %s, does not exist or could not be read",
|
1653 |
+
$senders_smime_cert_file
|
1654 |
+
);
|
1655 |
+
}
|
1656 |
+
|
1657 |
+
if (!self::stringlike($senders_smime_pk_file)) {
|
1658 |
+
throw new fProgrammerException(
|
1659 |
+
"The sender's S/MIME primary key file specified, %s, does not appear to be a valid filename",
|
1660 |
+
$senders_smime_pk_file
|
1661 |
+
);
|
1662 |
+
}
|
1663 |
+
if (!file_exists($senders_smime_pk_file) || !is_readable($senders_smime_pk_file)) {
|
1664 |
+
throw new fEnvironmentException(
|
1665 |
+
"The sender's S/MIME primary key file specified, %s, does not exist or could not be read",
|
1666 |
+
$senders_smime_pk_file
|
1667 |
+
);
|
1668 |
+
}
|
1669 |
+
|
1670 |
+
$this->smime_sign = TRUE;
|
1671 |
+
$this->senders_smime_cert_file = $senders_smime_cert_file;
|
1672 |
+
$this->senders_smime_pk_file = $senders_smime_pk_file;
|
1673 |
+
$this->senders_smime_pk_password = $senders_smime_pk_password;
|
1674 |
+
|
1675 |
+
return $this;
|
1676 |
+
}
|
1677 |
+
|
1678 |
+
|
1679 |
+
/**
|
1680 |
+
* Validates that all of the parts of the email are valid
|
1681 |
+
*
|
1682 |
+
* @throws fValidationException When part of the email is missing or formatted incorrectly
|
1683 |
+
*
|
1684 |
+
* @return void
|
1685 |
+
*/
|
1686 |
+
private function validate()
|
1687 |
+
{
|
1688 |
+
$validation_messages = array();
|
1689 |
+
|
1690 |
+
// Check all multi-address email field
|
1691 |
+
$multi_address_field_list = array(
|
1692 |
+
'to_emails' => self::compose('recipient'),
|
1693 |
+
'cc_emails' => self::compose('CC recipient'),
|
1694 |
+
'bcc_emails' => self::compose('BCC recipient')
|
1695 |
+
);
|
1696 |
+
|
1697 |
+
foreach ($multi_address_field_list as $field => $name) {
|
1698 |
+
foreach ($this->$field as $email) {
|
1699 |
+
if ($email && !preg_match(self::NAME_EMAIL_REGEX, $email) && !preg_match(self::EMAIL_REGEX, $email)) {
|
1700 |
+
$validation_messages[] = htmlspecialchars(self::compose(
|
1701 |
+
'The %1$s %2$s is not a valid email address. Should be like "John Smith" <name@example.com> or name@example.com.',
|
1702 |
+
$name,
|
1703 |
+
$email
|
1704 |
+
), ENT_QUOTES, 'UTF-8');
|
1705 |
+
}
|
1706 |
+
}
|
1707 |
+
}
|
1708 |
+
|
1709 |
+
// Check all single-address email fields
|
1710 |
+
$single_address_field_list = array(
|
1711 |
+
'from_email' => self::compose('From email address'),
|
1712 |
+
'reply_to_email' => self::compose('Reply-To email address'),
|
1713 |
+
'sender_email' => self::compose('Sender email address'),
|
1714 |
+
'bounce_to_email' => self::compose('Bounce-To email address')
|
1715 |
+
);
|
1716 |
+
|
1717 |
+
foreach ($single_address_field_list as $field => $name) {
|
1718 |
+
if ($this->$field && !preg_match(self::NAME_EMAIL_REGEX, $this->$field) && !preg_match(self::EMAIL_REGEX, $this->$field)) {
|
1719 |
+
$validation_messages[] = htmlspecialchars(self::compose(
|
1720 |
+
'The %1$s %2$s is not a valid email address. Should be like "John Smith" <name@example.com> or name@example.com.',
|
1721 |
+
$name,
|
1722 |
+
$this->$field
|
1723 |
+
), ENT_QUOTES, 'UTF-8');
|
1724 |
+
}
|
1725 |
+
}
|
1726 |
+
|
1727 |
+
// Make sure the required fields are all set
|
1728 |
+
if (!$this->to_emails) {
|
1729 |
+
$validation_messages[] = self::compose(
|
1730 |
+
"Please provide at least one recipient"
|
1731 |
+
);
|
1732 |
+
}
|
1733 |
+
|
1734 |
+
if (!$this->from_email) {
|
1735 |
+
$validation_messages[] = self::compose(
|
1736 |
+
"Please provide the from email address"
|
1737 |
+
);
|
1738 |
+
}
|
1739 |
+
|
1740 |
+
if (!self::stringlike($this->subject)) {
|
1741 |
+
$validation_messages[] = self::compose(
|
1742 |
+
"Please provide an email subject"
|
1743 |
+
);
|
1744 |
+
}
|
1745 |
+
|
1746 |
+
if (strpos($this->subject, "\n") !== FALSE) {
|
1747 |
+
$validation_messages[] = self::compose(
|
1748 |
+
"The subject contains one or more newline characters"
|
1749 |
+
);
|
1750 |
+
}
|
1751 |
+
|
1752 |
+
if (!self::stringlike($this->plaintext_body)) {
|
1753 |
+
$validation_messages[] = self::compose(
|
1754 |
+
"Please provide a plaintext email body"
|
1755 |
+
);
|
1756 |
+
}
|
1757 |
+
|
1758 |
+
// Make sure the attachments look good
|
1759 |
+
foreach ($this->attachments as $filename => $file_info) {
|
1760 |
+
if (!self::stringlike($file_info['mime-type'])) {
|
1761 |
+
$validation_messages[] = self::compose(
|
1762 |
+
"No mime-type was specified for the attachment %s",
|
1763 |
+
$filename
|
1764 |
+
);
|
1765 |
+
}
|
1766 |
+
if (!self::stringlike($file_info['contents'])) {
|
1767 |
+
$validation_messages[] = self::compose(
|
1768 |
+
"The attachment %s appears to be a blank file",
|
1769 |
+
$filename
|
1770 |
+
);
|
1771 |
+
}
|
1772 |
+
}
|
1773 |
+
|
1774 |
+
if ($validation_messages) {
|
1775 |
+
throw new fValidationException(
|
1776 |
+
'The email could not be sent because:',
|
1777 |
+
$validation_messages
|
1778 |
+
);
|
1779 |
+
}
|
1780 |
+
}
|
1781 |
+
}
|
1782 |
+
|
1783 |
+
|
1784 |
+
|
1785 |
+
/**
|
1786 |
+
* Copyright (c) 2008-2011 Will Bond <will@flourishlib.com>, others
|
1787 |
+
*
|
1788 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
1789 |
+
* of this software and associated documentation files (the "Software"), to deal
|
1790 |
+
* in the Software without restriction, including without limitation the rights
|
1791 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
1792 |
+
* copies of the Software, and to permit persons to whom the Software is
|
1793 |
+
* furnished to do so, subject to the following conditions:
|
1794 |
+
*
|
1795 |
+
* The above copyright notice and this permission notice shall be included in
|
1796 |
+
* all copies or substantial portions of the Software.
|
1797 |
+
*
|
1798 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
1799 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
1800 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
1801 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
1802 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
1803 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
1804 |
+
* THE SOFTWARE.
|
1805 |
+
*/
|
lib/fEnvironmentException.php
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* An exception caused by an environment error such a file permissions
|
4 |
+
*
|
5 |
+
* @copyright Copyright (c) 2007-2008 Will Bond
|
6 |
+
* @author Will Bond [wb] <will@flourishlib.com>
|
7 |
+
* @license http://flourishlib.com/license
|
8 |
+
*
|
9 |
+
* @package Flourish
|
10 |
+
* @link http://flourishlib.com/fEnvironmentException
|
11 |
+
*
|
12 |
+
* @version 1.0.0b
|
13 |
+
* @changes 1.0.0b The initial implementation [wb, 2007-06-14]
|
14 |
+
*/
|
15 |
+
class fEnvironmentException extends fUnexpectedException
|
16 |
+
{
|
17 |
+
}
|
18 |
+
|
19 |
+
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Copyright (c) 2007-2008 Will Bond <will@flourishlib.com>
|
23 |
+
*
|
24 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
25 |
+
* of this software and associated documentation files (the "Software"), to deal
|
26 |
+
* in the Software without restriction, including without limitation the rights
|
27 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
28 |
+
* copies of the Software, and to permit persons to whom the Software is
|
29 |
+
* furnished to do so, subject to the following conditions:
|
30 |
+
*
|
31 |
+
* The above copyright notice and this permission notice shall be included in
|
32 |
+
* all copies or substantial portions of the Software.
|
33 |
+
*
|
34 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
35 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
36 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
37 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
38 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
39 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
40 |
+
* THE SOFTWARE.
|
41 |
+
*/
|
lib/fException.php
ADDED
@@ -0,0 +1,593 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* An exception that allows for easy l10n, printing, tracing and hooking
|
4 |
+
*
|
5 |
+
* @copyright Copyright (c) 2007-2009 Will Bond
|
6 |
+
* @author Will Bond [wb] <will@flourishlib.com>
|
7 |
+
* @license http://flourishlib.com/license
|
8 |
+
*
|
9 |
+
* @package Flourish
|
10 |
+
* @link http://flourishlib.com/fException
|
11 |
+
*
|
12 |
+
* @version 1.0.0b8
|
13 |
+
* @changes 1.0.0b8 Added a missing line of backtrace to ::formatTrace() [wb, 2009-06-28]
|
14 |
+
* @changes 1.0.0b7 Updated ::__construct() to no longer require a message, like the Exception class, and allow for non-integer codes [wb, 2009-06-26]
|
15 |
+
* @changes 1.0.0b6 Fixed ::splitMessage() so that the original message is returned if no list items are found, added ::reorderMessage() [wb, 2009-06-02]
|
16 |
+
* @changes 1.0.0b5 Added ::splitMessage() to replace fCRUD::removeListItems() and fCRUD::reorderListItems() [wb, 2009-05-08]
|
17 |
+
* @changes 1.0.0b4 Added a check to ::__construct() to ensure that the `$code` parameter is numeric [wb, 2009-05-04]
|
18 |
+
* @changes 1.0.0b3 Fixed a bug with ::printMessage() messing up some HTML messages [wb, 2009-03-27]
|
19 |
+
* @changes 1.0.0b2 ::compose() more robustly handles `$components` passed as an array, ::__construct() now detects stray `%` characters [wb, 2009-02-05]
|
20 |
+
* @changes 1.0.0b The initial implementation [wb, 2007-06-14]
|
21 |
+
*/
|
22 |
+
abstract class fException extends Exception
|
23 |
+
{
|
24 |
+
/**
|
25 |
+
* Callbacks for when exceptions are created
|
26 |
+
*
|
27 |
+
* @var array
|
28 |
+
*/
|
29 |
+
static private $callbacks = array();
|
30 |
+
|
31 |
+
|
32 |
+
/**
|
33 |
+
* Composes text using fText if loaded
|
34 |
+
*
|
35 |
+
* @param string $message The message to compose
|
36 |
+
* @param mixed $component A string or number to insert into the message
|
37 |
+
* @param mixed ...
|
38 |
+
* @return string The composed and possible translated message
|
39 |
+
*/
|
40 |
+
static protected function compose($message)
|
41 |
+
{
|
42 |
+
$components = array_slice(func_get_args(), 1);
|
43 |
+
|
44 |
+
// Handles components passed as an array
|
45 |
+
if (sizeof($components) == 1 && is_array($components[0])) {
|
46 |
+
$components = $components[0];
|
47 |
+
}
|
48 |
+
|
49 |
+
// If fText is loaded, use it
|
50 |
+
if (class_exists('fText', FALSE)) {
|
51 |
+
return call_user_func_array(
|
52 |
+
array('fText', 'compose'),
|
53 |
+
array($message, $components)
|
54 |
+
);
|
55 |
+
|
56 |
+
} else {
|
57 |
+
return vsprintf($message, $components);
|
58 |
+
}
|
59 |
+
}
|
60 |
+
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Creates a string representation of any variable using predefined strings for booleans, `NULL` and empty strings
|
64 |
+
*
|
65 |
+
* The string output format of this method is very similar to the output of
|
66 |
+
* [http://php.net/print_r print_r()] except that the following values
|
67 |
+
* are represented as special strings:
|
68 |
+
*
|
69 |
+
* - `TRUE`: `'{true}'`
|
70 |
+
* - `FALSE`: `'{false}'`
|
71 |
+
* - `NULL`: `'{null}'`
|
72 |
+
* - `''`: `'{empty_string}'`
|
73 |
+
*
|
74 |
+
* @param mixed $data The value to dump
|
75 |
+
* @return string The string representation of the value
|
76 |
+
*/
|
77 |
+
static protected function dump($data)
|
78 |
+
{
|
79 |
+
if (is_bool($data)) {
|
80 |
+
return ($data) ? '{true}' : '{false}';
|
81 |
+
|
82 |
+
} elseif (is_null($data)) {
|
83 |
+
return '{null}';
|
84 |
+
|
85 |
+
} elseif ($data === '') {
|
86 |
+
return '{empty_string}';
|
87 |
+
|
88 |
+
} elseif (is_array($data) || is_object($data)) {
|
89 |
+
|
90 |
+
ob_start();
|
91 |
+
var_dump($data);
|
92 |
+
$output = ob_get_contents();
|
93 |
+
ob_end_clean();
|
94 |
+
|
95 |
+
// Make the var dump more like a print_r
|
96 |
+
$output = preg_replace('#=>\n( )+(?=[a-zA-Z]|&)#m', ' => ', $output);
|
97 |
+
$output = str_replace('string(0) ""', '{empty_string}', $output);
|
98 |
+
$output = preg_replace('#=> (&)?NULL#', '=> \1{null}', $output);
|
99 |
+
$output = preg_replace('#=> (&)?bool\((false|true)\)#', '=> \1{\2}', $output);
|
100 |
+
$output = preg_replace('#string\(\d+\) "#', '', $output);
|
101 |
+
$output = preg_replace('#"(\n( )*)(?=\[|\})#', '\1', $output);
|
102 |
+
$output = preg_replace('#(?:float|int)\((-?\d+(?:.\d+)?)\)#', '\1', $output);
|
103 |
+
$output = preg_replace('#((?: )+)\["(.*?)"\]#', '\1[\2]', $output);
|
104 |
+
$output = preg_replace('#(?:&)?array\(\d+\) \{\n((?: )*)((?: )(?=\[)|(?=\}))#', "Array\n\\1(\n\\1\\2", $output);
|
105 |
+
$output = preg_replace('/object\((\w+)\)#\d+ \(\d+\) {\n((?: )*)((?: )(?=\[)|(?=\}))/', "\\1 Object\n\\2(\n\\2\\3", $output);
|
106 |
+
$output = preg_replace('#^((?: )+)}(?=\n|$)#m', "\\1)\n", $output);
|
107 |
+
$output = substr($output, 0, -2) . ')';
|
108 |
+
|
109 |
+
// Fix indenting issues with the var dump output
|
110 |
+
$output_lines = explode("\n", $output);
|
111 |
+
$new_output = array();
|
112 |
+
$stack = 0;
|
113 |
+
foreach ($output_lines as $line) {
|
114 |
+
if (preg_match('#^((?: )*)([^ ])#', $line, $match)) {
|
115 |
+
$spaces = strlen($match[1]);
|
116 |
+
if ($spaces && $match[2] == '(') {
|
117 |
+
$stack += 1;
|
118 |
+
}
|
119 |
+
$new_output[] = str_pad('', ($spaces)+(4*$stack)) . $line;
|
120 |
+
if ($spaces && $match[2] == ')') {
|
121 |
+
$stack -= 1;
|
122 |
+
}
|
123 |
+
} else {
|
124 |
+
$new_output[] = str_pad('', ($spaces)+(4*$stack)) . $line;
|
125 |
+
}
|
126 |
+
}
|
127 |
+
|
128 |
+
return join("\n", $new_output);
|
129 |
+
|
130 |
+
} else {
|
131 |
+
return (string) $data;
|
132 |
+
}
|
133 |
+
}
|
134 |
+
|
135 |
+
|
136 |
+
/**
|
137 |
+
* Adds a callback for when certain types of exceptions are created
|
138 |
+
*
|
139 |
+
* The callback will be called when any exception of this class, or any
|
140 |
+
* child class, specified is tossed. A single parameter will be passed
|
141 |
+
* to the callback, which will be the exception object.
|
142 |
+
*
|
143 |
+
* @param callback $callback The callback
|
144 |
+
* @param string $exception_type The type of exception to call the callback for
|
145 |
+
* @return void
|
146 |
+
*/
|
147 |
+
static public function registerCallback($callback, $exception_type=NULL)
|
148 |
+
{
|
149 |
+
if ($exception_type === NULL) {
|
150 |
+
$exception_type = 'fException';
|
151 |
+
}
|
152 |
+
|
153 |
+
if (!isset(self::$callbacks[$exception_type])) {
|
154 |
+
self::$callbacks[$exception_type] = array();
|
155 |
+
}
|
156 |
+
|
157 |
+
if (is_string($callback) && strpos($callback, '::') !== FALSE) {
|
158 |
+
$callback = explode('::', $callback);
|
159 |
+
}
|
160 |
+
|
161 |
+
self::$callbacks[$exception_type][] = $callback;
|
162 |
+
}
|
163 |
+
|
164 |
+
|
165 |
+
/**
|
166 |
+
* Compares the message matching strings by longest first so that the longest matches are made first
|
167 |
+
*
|
168 |
+
* @param string $a The first string to compare
|
169 |
+
* @param string $b The second string to compare
|
170 |
+
* @return integer `-1` if `$a` is longer than `$b`, `0` if they are equal length, `1` if `$a` is shorter than `$b`
|
171 |
+
*/
|
172 |
+
static private function sortMatchingArray($a, $b)
|
173 |
+
{
|
174 |
+
return -1 * strnatcmp(strlen($a), strlen($b));
|
175 |
+
}
|
176 |
+
|
177 |
+
|
178 |
+
/**
|
179 |
+
* Sets the message for the exception, allowing for string interpolation and internationalization
|
180 |
+
*
|
181 |
+
* The `$message` can contain any number of formatting placeholders for
|
182 |
+
* string and number interpolation via [http://php.net/sprintf `sprintf()`].
|
183 |
+
* Any `%` signs that do not appear to be part of a valid formatting
|
184 |
+
* placeholder will be automatically escaped with a second `%`.
|
185 |
+
*
|
186 |
+
* The following aspects of valid `sprintf()` formatting codes are not
|
187 |
+
* accepted since they are redundant and restrict the non-formatting use of
|
188 |
+
* the `%` sign in exception messages:
|
189 |
+
* - `% 2d`: Using a literal space as a padding character - a space will be used if no padding character is specified
|
190 |
+
* - `%'.d`: Providing a padding character but no width - no padding will be applied without a width
|
191 |
+
*
|
192 |
+
* @param string $message The message for the exception. This accepts a subset of [http://php.net/sprintf `sprintf()`] strings - see method description for more details.
|
193 |
+
* @param mixed $component A string or number to insert into the message
|
194 |
+
* @param mixed ...
|
195 |
+
* @param mixed $code The exception code to set
|
196 |
+
* @return fException
|
197 |
+
*/
|
198 |
+
public function __construct($message='')
|
199 |
+
{
|
200 |
+
$args = array_slice(func_get_args(), 1);
|
201 |
+
$required_args = preg_match_all(
|
202 |
+
'/
|
203 |
+
(?<!%) # Ensure this is not an escaped %
|
204 |
+
%( # The leading %
|
205 |
+
(?:\d+\$)? # Position
|
206 |
+
\+? # Sign specifier
|
207 |
+
(?:(?:0|\'.)?-?\d+|-?) # Padding, alignment and width or just alignment
|
208 |
+
(?:\.\d+)? # Precision
|
209 |
+
[bcdeufFosxX] # Type
|
210 |
+
)/x',
|
211 |
+
$message,
|
212 |
+
$matches
|
213 |
+
);
|
214 |
+
|
215 |
+
// Handle %s that weren't properly escaped
|
216 |
+
$formats = $matches[1];
|
217 |
+
$delimeters = ($formats) ? array_fill(0, sizeof($formats), '#') : array();
|
218 |
+
$lookahead = join(
|
219 |
+
'|',
|
220 |
+
array_map(
|
221 |
+
'preg_quote',
|
222 |
+
$formats,
|
223 |
+
$delimeters
|
224 |
+
)
|
225 |
+
);
|
226 |
+
$lookahead = ($lookahead) ? '|' . $lookahead : '';
|
227 |
+
$message = preg_replace('#(?<!%)%(?!%' . $lookahead . ')#', '%%', $message);
|
228 |
+
|
229 |
+
// If we have an extra argument, it is the exception code
|
230 |
+
$code = NULL;
|
231 |
+
if ($required_args == sizeof($args) - 1) {
|
232 |
+
$code = array_pop($args);
|
233 |
+
}
|
234 |
+
|
235 |
+
if (sizeof($args) != $required_args) {
|
236 |
+
$message = self::compose(
|
237 |
+
'%1$d components were passed to the %2$s constructor, while %3$d were specified in the message',
|
238 |
+
sizeof($args),
|
239 |
+
get_class($this),
|
240 |
+
$required_args
|
241 |
+
);
|
242 |
+
throw new Exception($message);
|
243 |
+
}
|
244 |
+
|
245 |
+
$args = array_map(array('fException', 'dump'), $args);
|
246 |
+
|
247 |
+
parent::__construct(self::compose($message, $args));
|
248 |
+
$this->code = $code;
|
249 |
+
|
250 |
+
foreach (self::$callbacks as $class => $callbacks) {
|
251 |
+
foreach ($callbacks as $callback) {
|
252 |
+
if ($this instanceof $class) {
|
253 |
+
call_user_func($callback, $this);
|
254 |
+
}
|
255 |
+
}
|
256 |
+
}
|
257 |
+
}
|
258 |
+
|
259 |
+
|
260 |
+
/**
|
261 |
+
* All requests that hit this method should be requests for callbacks
|
262 |
+
*
|
263 |
+
* @internal
|
264 |
+
*
|
265 |
+
* @param string $method The method to create a callback for
|
266 |
+
* @return callback The callback for the method requested
|
267 |
+
*/
|
268 |
+
public function __get($method)
|
269 |
+
{
|
270 |
+
return array($this, $method);
|
271 |
+
}
|
272 |
+
|
273 |
+
|
274 |
+
/**
|
275 |
+
* Gets the backtrace to currently called exception
|
276 |
+
*
|
277 |
+
* @return string A nicely formatted backtrace to this exception
|
278 |
+
*/
|
279 |
+
public function formatTrace()
|
280 |
+
{
|
281 |
+
$doc_root = realpath($_SERVER['DOCUMENT_ROOT']);
|
282 |
+
$doc_root .= (substr($doc_root, -1) != DIRECTORY_SEPARATOR) ? DIRECTORY_SEPARATOR : '';
|
283 |
+
|
284 |
+
$backtrace = explode("\n", $this->getTraceAsString());
|
285 |
+
array_unshift($backtrace, $this->file . '(' . $this->line . ')');
|
286 |
+
$backtrace = preg_replace('/^#\d+\s+/', '', $backtrace);
|
287 |
+
$backtrace = str_replace($doc_root, '{doc_root}' . DIRECTORY_SEPARATOR, $backtrace);
|
288 |
+
$backtrace = array_diff($backtrace, array('{main}'));
|
289 |
+
$backtrace = array_reverse($backtrace);
|
290 |
+
|
291 |
+
return join("\n", $backtrace);
|
292 |
+
}
|
293 |
+
|
294 |
+
|
295 |
+
/**
|
296 |
+
* Returns the CSS class name for printing information about the exception
|
297 |
+
*
|
298 |
+
* @return void
|
299 |
+
*/
|
300 |
+
protected function getCSSClass()
|
301 |
+
{
|
302 |
+
$string = preg_replace('#^f#', '', get_class($this));
|
303 |
+
|
304 |
+
do {
|
305 |
+
$old_string = $string;
|
306 |
+
$string = preg_replace('/([a-zA-Z])([0-9])/', '\1_\2', $string);
|
307 |
+
$string = preg_replace('/([a-z0-9A-Z])([A-Z])/', '\1_\2', $string);
|
308 |
+
} while ($old_string != $string);
|
309 |
+
|
310 |
+
return strtolower($string);
|
311 |
+
}
|
312 |
+
|
313 |
+
|
314 |
+
/**
|
315 |
+
* Prepares content for output into HTML
|
316 |
+
*
|
317 |
+
* @return string The prepared content
|
318 |
+
*/
|
319 |
+
protected function prepare($content)
|
320 |
+
{
|
321 |
+
// See if the message has newline characters but not br tags, extracted from fHTML to reduce dependencies
|
322 |
+
static $inline_tags_minus_br = '<a><abbr><acronym><b><big><button><cite><code><del><dfn><em><font><i><img><input><ins><kbd><label><q><s><samp><select><small><span><strike><strong><sub><sup><textarea><tt><u><var>';
|
323 |
+
$content_with_newlines = (strip_tags($content, $inline_tags_minus_br)) ? $content : nl2br($content);
|
324 |
+
|
325 |
+
// Check to see if we have any block-level html, extracted from fHTML to reduce dependencies
|
326 |
+
$inline_tags = $inline_tags_minus_br . '<br>';
|
327 |
+
$no_block_html = strip_tags($content, $inline_tags) == $content;
|
328 |
+
|
329 |
+
// This code ensures the output is properly encoded for display in (X)HTML, extracted from fHTML to reduce dependencies
|
330 |
+
$reg_exp = "/<\s*\/?\s*[\w:]+(?:\s+[\w:]+(?:\s*=\s*(?:\"[^\"]*?\"|'[^']*?'|[^'\">\s]+))?)*\s*\/?\s*>|&(?:#\d+|\w+);|<\!--.*?-->/";
|
331 |
+
preg_match_all($reg_exp, $content, $html_matches, PREG_SET_ORDER);
|
332 |
+
$text_matches = preg_split($reg_exp, $content_with_newlines);
|
333 |
+
|
334 |
+
foreach($text_matches as $key => $value) {
|
335 |
+
$value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
|
336 |
+
}
|
337 |
+
|
338 |
+
for ($i = 0; $i < sizeof($html_matches); $i++) {
|
339 |
+
$text_matches[$i] .= $html_matches[$i][0];
|
340 |
+
}
|
341 |
+
|
342 |
+
$content_with_newlines = implode($text_matches);
|
343 |
+
|
344 |
+
$output = ($no_block_html) ? '<p>' : '';
|
345 |
+
$output .= $content_with_newlines;
|
346 |
+
$output .= ($no_block_html) ? '</p>' : '';
|
347 |
+
|
348 |
+
return $output;
|
349 |
+
}
|
350 |
+
|
351 |
+
|
352 |
+
/**
|
353 |
+
* Prints the message inside of a div with the class being 'exception %THIS_EXCEPTION_CLASS_NAME%'
|
354 |
+
*
|
355 |
+
* @return void
|
356 |
+
*/
|
357 |
+
public function printMessage()
|
358 |
+
{
|
359 |
+
echo '<div class="exception ' . $this->getCSSClass() . '">';
|
360 |
+
echo $this->prepare($this->message);
|
361 |
+
echo '</div>';
|
362 |
+
}
|
363 |
+
|
364 |
+
|
365 |
+
/**
|
366 |
+
* Prints the backtrace to currently called exception inside of a pre tag with the class being 'exception %THIS_EXCEPTION_CLASS_NAME% trace'
|
367 |
+
*
|
368 |
+
* @return void
|
369 |
+
*/
|
370 |
+
public function printTrace()
|
371 |
+
{
|
372 |
+
echo '<pre class="exception ' . $this->getCSSClass() . ' trace">';
|
373 |
+
echo $this->formatTrace();
|
374 |
+
echo '</pre>';
|
375 |
+
}
|
376 |
+
|
377 |
+
|
378 |
+
/**
|
379 |
+
* Reorders list items in the message based on simple string matching
|
380 |
+
*
|
381 |
+
* @param string $match This should be a string to match to one of the list items - whatever the order this is in the parameter list will be the order of the list item in the adjusted message
|
382 |
+
* @param string ...
|
383 |
+
* @return fException The exception object, to allow for method chaining
|
384 |
+
*/
|
385 |
+
public function reorderMessage($match)
|
386 |
+
{
|
387 |
+
// If we can't find a list, don't bother continuing
|
388 |
+
if (!preg_match('#^(.*<(?:ul|ol)[^>]*?>)(.*?)(</(?:ul|ol)>.*)$#isD', $this->message, $message_parts)) {
|
389 |
+
return $this;
|
390 |
+
}
|
391 |
+
|
392 |
+
$matching_array = func_get_args();
|
393 |
+
// This ensures that we match on the longest string first
|
394 |
+
uasort($matching_array, array('self', 'sortMatchingArray'));
|
395 |
+
|
396 |
+
$beginning = $message_parts[1];
|
397 |
+
$list_contents = $message_parts[2];
|
398 |
+
$ending = $message_parts[3];
|
399 |
+
|
400 |
+
preg_match_all('#<li(.*?)</li>#i', $list_contents, $list_items, PREG_SET_ORDER);
|
401 |
+
|
402 |
+
$ordered_items = array_fill(0, sizeof($matching_array), array());
|
403 |
+
$other_items = array();
|
404 |
+
|
405 |
+
foreach ($list_items as $list_item) {
|
406 |
+
foreach ($matching_array as $num => $match_string) {
|
407 |
+
if (strpos($list_item[1], $match_string) !== FALSE) {
|
408 |
+
$ordered_items[$num][] = $list_item[0];
|
409 |
+
continue 2;
|
410 |
+
}
|
411 |
+
}
|
412 |
+
|
413 |
+
$other_items[] = $list_item[0];
|
414 |
+
}
|
415 |
+
|
416 |
+
$final_list = array();
|
417 |
+
foreach ($ordered_items as $ordered_item) {
|
418 |
+
$final_list = array_merge($final_list, $ordered_item);
|
419 |
+
}
|
420 |
+
$final_list = array_merge($final_list, $other_items);
|
421 |
+
|
422 |
+
$this->message = $beginning . join("\n", $final_list) . $ending;
|
423 |
+
|
424 |
+
return $this;
|
425 |
+
}
|
426 |
+
|
427 |
+
|
428 |
+
/**
|
429 |
+
* Allows the message to be overwriten
|
430 |
+
*
|
431 |
+
* @param string $new_message The new message for the exception
|
432 |
+
* @return void
|
433 |
+
*/
|
434 |
+
public function setMessage($new_message)
|
435 |
+
{
|
436 |
+
$this->message = $new_message;
|
437 |
+
}
|
438 |
+
|
439 |
+
|
440 |
+
/**
|
441 |
+
* Splits an exception with an HTML list into multiple strings each containing part of the original message
|
442 |
+
*
|
443 |
+
* This method should be called with two or more parameters of arrays of
|
444 |
+
* string to match. If any of the provided strings are matching in a list
|
445 |
+
* item in the exception message, a new copy of the message will be created
|
446 |
+
* containing just the matching list items.
|
447 |
+
*
|
448 |
+
* Here is an exception message to be split:
|
449 |
+
*
|
450 |
+
* {{{
|
451 |
+
* #!html
|
452 |
+
* <p>The following problems were found:</p>
|
453 |
+
* <ul>
|
454 |
+
* <li>First Name: Please enter a value</li>
|
455 |
+
* <li>Last Name: Please enter a value</li>
|
456 |
+
* <li>Email: Please enter a value</li>
|
457 |
+
* <li>Address: Please enter a value</li>
|
458 |
+
* <li>City: Please enter a value</li>
|
459 |
+
* <li>State: Please enter a value</li>
|
460 |
+
* <li>Zip Code: Please enter a value</li>
|
461 |
+
* </ul>
|
462 |
+
* }}}
|
463 |
+
*
|
464 |
+
* The following PHP would split the exception into two messages:
|
465 |
+
*
|
466 |
+
* {{{
|
467 |
+
* #!php
|
468 |
+
* list ($name_exception, $address_exception) = $exception->splitMessage(
|
469 |
+
* array('First Name', 'Last Name', 'Email'),
|
470 |
+
* array('Address', 'City', 'State', 'Zip Code')
|
471 |
+
* );
|
472 |
+
* }}}
|
473 |
+
*
|
474 |
+
* The resulting messages would be:
|
475 |
+
*
|
476 |
+
* {{{
|
477 |
+
* #!html
|
478 |
+
* <p>The following problems were found:</p>
|
479 |
+
* <ul>
|
480 |
+
* <li>First Name: Please enter a value</li>
|
481 |
+
* <li>Last Name: Please enter a value</li>
|
482 |
+
* <li>Email: Please enter a value</li>
|
483 |
+
* </ul>
|
484 |
+
* }}}
|
485 |
+
*
|
486 |
+
* and
|
487 |
+
*
|
488 |
+
* {{{
|
489 |
+
* #!html
|
490 |
+
* <p>The following problems were found:</p>
|
491 |
+
* <ul>
|
492 |
+
* <li>Address: Please enter a value</li>
|
493 |
+
* <li>City: Please enter a value</li>
|
494 |
+
* <li>State: Please enter a value</li>
|
495 |
+
* <li>Zip Code: Please enter a value</li>
|
496 |
+
* </ul>
|
497 |
+
* }}}
|
498 |
+
*
|
499 |
+
* If no list items match the strings in a parameter, the result will be
|
500 |
+
* an empty string, allowing for simple display:
|
501 |
+
*
|
502 |
+
* {{{
|
503 |
+
* #!php
|
504 |
+
* fHTML::show($name_exception, 'error');
|
505 |
+
* }}}
|
506 |
+
*
|
507 |
+
* An empty string is returned when none of the list items matched the
|
508 |
+
* strings in the parameter. If no list items are found, the first value in
|
509 |
+
* the returned array will be the existing message and all other array
|
510 |
+
* values will be an empty string.
|
511 |
+
*
|
512 |
+
* @param array $list_item_matches An array of strings to filter the list items by, list items will be ordered in the same order as this array
|
513 |
+
* @param array ...
|
514 |
+
* @return array This will contain an array of strings corresponding to the parameters passed - see method description for details
|
515 |
+
*/
|
516 |
+
public function splitMessage($list_item_matches)
|
517 |
+
{
|
518 |
+
$class = get_class($this);
|
519 |
+
|
520 |
+
$matching_arrays = func_get_args();
|
521 |
+
|
522 |
+
if (!preg_match('#^(.*<(?:ul|ol)[^>]*?>)(.*?)(</(?:ul|ol)>.*)$#isD', $this->message, $matches)) {
|
523 |
+
return array_merge(array($this->message), array_fill(0, sizeof($matching_arrays)-1, ''));
|
524 |
+
}
|
525 |
+
|
526 |
+
$beginning_html = $matches[1];
|
527 |
+
$list_items_html = $matches[2];
|
528 |
+
$ending_html = $matches[3];
|
529 |
+
|
530 |
+
preg_match_all('#<li(.*?)</li>#i', $list_items_html, $list_items, PREG_SET_ORDER);
|
531 |
+
|
532 |
+
$output = array();
|
533 |
+
|
534 |
+
foreach ($matching_arrays as $matching_array) {
|
535 |
+
|
536 |
+
// This ensures that we match on the longest string first
|
537 |
+
uasort($matching_array, array('self', 'sortMatchingArray'));
|
538 |
+
|
539 |
+
// We may match more than one list item per matching string, so we need a multi-dimensional array to hold them
|
540 |
+
$matched_list_items = array_fill(0, sizeof($matching_array), array());
|
541 |
+
$found = FALSE;
|
542 |
+
|
543 |
+
foreach ($list_items as $list_item) {
|
544 |
+
foreach ($matching_array as $match_num => $matching_string) {
|
545 |
+
if (strpos($list_item[1], $matching_string) !== FALSE) {
|
546 |
+
$matched_list_items[$match_num][] = $list_item[0];
|
547 |
+
$found = TRUE;
|
548 |
+
continue 2;
|
549 |
+
}
|
550 |
+
}
|
551 |
+
}
|
552 |
+
|
553 |
+
if (!$found) {
|
554 |
+
$output[] = '';
|
555 |
+
continue;
|
556 |
+
}
|
557 |
+
|
558 |
+
// This merges all of the multi-dimensional arrays back to one so we can do a simple join
|
559 |
+
$merged_list_items = array();
|
560 |
+
foreach ($matched_list_items as $match_num => $matched_items) {
|
561 |
+
$merged_list_items = array_merge($merged_list_items, $matched_items);
|
562 |
+
}
|
563 |
+
|
564 |
+
$output[] = $beginning_html . join("\n", $merged_list_items) . $ending_html;
|
565 |
+
}
|
566 |
+
|
567 |
+
return $output;
|
568 |
+
}
|
569 |
+
}
|
570 |
+
|
571 |
+
|
572 |
+
|
573 |
+
/**
|
574 |
+
* Copyright (c) 2007-2009 Will Bond <will@flourishlib.com>
|
575 |
+
*
|
576 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
577 |
+
* of this software and associated documentation files (the "Software"), to deal
|
578 |
+
* in the Software without restriction, including without limitation the rights
|
579 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
580 |
+
* copies of the Software, and to permit persons to whom the Software is
|
581 |
+
* furnished to do so, subject to the following conditions:
|
582 |
+
*
|
583 |
+
* The above copyright notice and this permission notice shall be included in
|
584 |
+
* all copies or substantial portions of the Software.
|
585 |
+
*
|
586 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
587 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
588 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
589 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
590 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
591 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
592 |
+
* THE SOFTWARE.
|
593 |
+
*/
|
lib/fExpectedException.php
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* An exception that should be handled by the display code
|
4 |
+
*
|
5 |
+
* @copyright Copyright (c) 2007-2008 Will Bond
|
6 |
+
* @author Will Bond [wb] <will@flourishlib.com>
|
7 |
+
* @license http://flourishlib.com/license
|
8 |
+
*
|
9 |
+
* @package Flourish
|
10 |
+
* @link http://flourishlib.com/fExpectedException
|
11 |
+
*
|
12 |
+
* @version 1.0.0b
|
13 |
+
* @changes 1.0.0b The initial implementation [wb, 2007-06-14]
|
14 |
+
*/
|
15 |
+
class fExpectedException extends fException
|
16 |
+
{
|
17 |
+
}
|
18 |
+
|
19 |
+
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Copyright (c) 2007-2008 Will Bond <will@flourishlib.com>
|
23 |
+
*
|
24 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
25 |
+
* of this software and associated documentation files (the "Software"), to deal
|
26 |
+
* in the Software without restriction, including without limitation the rights
|
27 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
28 |
+
* copies of the Software, and to permit persons to whom the Software is
|
29 |
+
* furnished to do so, subject to the following conditions:
|
30 |
+
*
|
31 |
+
* The above copyright notice and this permission notice shall be included in
|
32 |
+
* all copies or substantial portions of the Software.
|
33 |
+
*
|
34 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
35 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
36 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
37 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
38 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
39 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
40 |
+
* THE SOFTWARE.
|
41 |
+
*/
|
lib/fMailbox.php
ADDED
@@ -0,0 +1,1086 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Note this class has been modified to add the following methods:
|
5 |
+
* countMessages()
|
6 |
+
* The following methods have been modified
|
7 |
+
* parseHeaders
|
8 |
+
* -bcc field is parsed
|
9 |
+
* handlePart
|
10 |
+
* -inline text/plain is added to the text element
|
11 |
+
*/
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Retrieves and deletes messages from a email account via IMAP or POP3
|
15 |
+
*
|
16 |
+
* All headers, text and html content returned by this class are encoded in
|
17 |
+
* UTF-8. Please see http://flourishlib.com/docs/UTF-8 for more information.
|
18 |
+
*
|
19 |
+
* @copyright Copyright (c) 2010-2012 Will Bond
|
20 |
+
* @author Will Bond [wb] <will@flourishlib.com>
|
21 |
+
* @license http://flourishlib.com/license
|
22 |
+
*
|
23 |
+
* @package Flourish
|
24 |
+
* @link http://flourishlib.com/fMailbox
|
25 |
+
*
|
26 |
+
* @version 1.0.0b18
|
27 |
+
* @changes 1.0.0b18 Fixed a bug in ::fetchMessageSource() where IMAP connections would add an extra `\r\n` to the end of the source [wb, 2012-09-16]
|
28 |
+
* @changes 1.0.0b17 Updated the class to be more forgiving when parsing the response for `STATUS` and `FETCH` IMAP commands [wb, 2012-09-15]
|
29 |
+
* @changes 1.0.0b16 Added method ::fetchMessageSource() [wb, 2012-09-15]
|
30 |
+
* @changes 1.0.0b15 Fixed handling of bounces with no headers [wb, 2012-09-15]
|
31 |
+
* @changes 1.0.0b14 Added a workaround for iconv having issues in MAMP 1.9.4+ [wb, 2011-07-26]
|
32 |
+
* @changes 1.0.0b13 Fixed handling of headers in relation to encoded-words being embedded inside of quoted strings [wb, 2011-07-26]
|
33 |
+
* @changes 1.0.0b12 Enhanced the error checking in ::write() [wb, 2011-06-03]
|
34 |
+
* @changes 1.0.0b11 Added code to work around PHP bug #42682 (http://bugs.php.net/bug.php?id=42682) where `stream_select()` doesn't work on 64bit machines from PHP 5.2.0 to 5.2.5, improved connectivity error handling and timeouts while reading data [wb, 2011-01-10]
|
35 |
+
* @changes 1.0.0b10 Fixed ::parseMessage() to properly handle a header format edge case and properly set the `text` and `html` keys even when the email has an explicit `Content-disposition: inline` header [wb, 2010-11-25]
|
36 |
+
* @changes 1.0.0b9 Fixed a bug in ::parseMessage() that could cause HTML alternate content to be included in the `inline` content array instead of the `html` element [wb, 2010-09-20]
|
37 |
+
* @changes 1.0.0b8 Fixed ::parseMessage() to be able to handle non-text/non-html multipart parts that do not have a `Content-disposition` header [wb, 2010-09-18]
|
38 |
+
* @changes 1.0.0b7 Fixed a typo in ::read() [wb, 2010-09-07]
|
39 |
+
* @changes 1.0.0b6 Fixed a typo from 1.0.0b4 [wb, 2010-07-21]
|
40 |
+
* @changes 1.0.0b5 Fixes for increased compatibility with various IMAP and POP3 servers, hacked around a bug in PHP 5.3 on Windows [wb, 2010-06-22]
|
41 |
+
* @changes 1.0.0b4 Added code to handle emails without an explicit `Content-type` header [wb, 2010-06-04]
|
42 |
+
* @changes 1.0.0b3 Added missing static method callback constants [wb, 2010-05-11]
|
43 |
+
* @changes 1.0.0b2 Added the missing ::enableDebugging() [wb, 2010-05-05]
|
44 |
+
* @changes 1.0.0b The initial implementation [wb, 2010-05-05]
|
45 |
+
*/
|
46 |
+
class fMailbox {
|
47 |
+
|
48 |
+
const addSMIMEPair = 'fMailbox::addSMIMEPair';
|
49 |
+
const parseMessage = 'fMailbox::parseMessage';
|
50 |
+
const reset = 'fMailbox::reset';
|
51 |
+
|
52 |
+
/**
|
53 |
+
* S/MIME certificates and private keys for verification and decryption
|
54 |
+
*
|
55 |
+
* @var array
|
56 |
+
*/
|
57 |
+
static private $smime_pairs = array();
|
58 |
+
|
59 |
+
/**
|
60 |
+
* Adds an S/MIME certificate, or certificate + private key pair for verification and decryption of S/MIME messages
|
61 |
+
*
|
62 |
+
* @param string $email_address The email address the certificate or private key is for
|
63 |
+
* @param fFile|string $certificate_file The file the S/MIME certificate is stored in - required for verification and decryption
|
64 |
+
* @param fFile $private_key_file The file the S/MIME private key is stored in - required for decryption only
|
65 |
+
* @param string $private_key_password The password for the private key
|
66 |
+
* @return void
|
67 |
+
*/
|
68 |
+
static public function addSMIMEPair($email_address, $certificate_file, $private_key_file = NULL, $private_key_password = NULL) {
|
69 |
+
if ($private_key_file !== NULL && !$private_key_file instanceof fFile) {
|
70 |
+
$private_key_file = new fFile($private_key_file);
|
71 |
+
}
|
72 |
+
if (!$certificate_file instanceof fFile) {
|
73 |
+
$certificate_file = new fFile($certificate_file);
|
74 |
+
}
|
75 |
+
self::$smime_pairs[strtolower($email_address)] = array(
|
76 |
+
'certificate' => $certificate_file,
|
77 |
+
'private_key' => $private_key_file,
|
78 |
+
'password' => $private_key_password
|
79 |
+
);
|
80 |
+
}
|
81 |
+
|
82 |
+
/**
|
83 |
+
* Takes a date, removes comments and cleans up some common formatting inconsistencies
|
84 |
+
*
|
85 |
+
* @param string $date The date to clean
|
86 |
+
* @return string The cleaned date
|
87 |
+
*/
|
88 |
+
static private function cleanDate($date) {
|
89 |
+
$date = preg_replace('#\([^)]+\)#', ' ', trim($date));
|
90 |
+
$date = preg_replace('#\s+#', ' ', $date);
|
91 |
+
$date = preg_replace('#(\d+)-([a-z]+)-(\d{4})#i', '\1 \2 \3', $date);
|
92 |
+
$date = preg_replace('#^[a-z]+\s*,\s*#i', '', trim($date));
|
93 |
+
return trim($date);
|
94 |
+
}
|
95 |
+
|
96 |
+
/**
|
97 |
+
* Decodes encoded-word headers of any encoding into raw UTF-8
|
98 |
+
*
|
99 |
+
* @param string $text The header value to decode
|
100 |
+
* @return string The decoded UTF-8
|
101 |
+
*/
|
102 |
+
static private function decodeHeader($text) {
|
103 |
+
$parts = preg_split('#(=\?[^\?]+\?[QB]\?[^\?]+\?=)#i', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
|
104 |
+
|
105 |
+
$part_with_encoding = array();
|
106 |
+
$output = '';
|
107 |
+
$isencoded = false;
|
108 |
+
foreach ($parts as $part) {
|
109 |
+
if ($part === '') {
|
110 |
+
continue;
|
111 |
+
}
|
112 |
+
|
113 |
+
if (preg_match_all('#=\?([^\?]+)\?([QB])\?([^\?]+)\?=#i', $part, $matches, PREG_SET_ORDER)) {
|
114 |
+
DebugEcho("decodeHeader: found encoded value: '$part'");
|
115 |
+
$isencoded = true;
|
116 |
+
foreach ($matches as $match) {
|
117 |
+
if (strtoupper($match[2]) == 'Q') {
|
118 |
+
$part_string = rawurldecode(strtr(
|
119 |
+
$match[3], array(
|
120 |
+
'=' => '%',
|
121 |
+
'_' => ' '
|
122 |
+
)
|
123 |
+
));
|
124 |
+
DebugEcho("decodeHeader: Q encoding: '$part_string'");
|
125 |
+
} else {
|
126 |
+
$part_string = base64_decode($match[3]);
|
127 |
+
DebugEcho("decodeHeader: B encoding: '$part_string'");
|
128 |
+
}
|
129 |
+
$lower_encoding = strtolower($match[1]);
|
130 |
+
$last_key = count($part_with_encoding) - 1;
|
131 |
+
if (isset($part_with_encoding[$last_key]) && $part_with_encoding[$last_key]['encoding'] == $lower_encoding) {
|
132 |
+
$part_with_encoding[$last_key]['string'] .= $part_string;
|
133 |
+
} else {
|
134 |
+
$part_with_encoding[] = array('encoding' => $lower_encoding, 'string' => $part_string);
|
135 |
+
}
|
136 |
+
}
|
137 |
+
} else {
|
138 |
+
DebugEcho("decodeHeader: found non-encoded value: '$part'");
|
139 |
+
if ($isencoded) {
|
140 |
+
$part = trim($part);
|
141 |
+
}
|
142 |
+
if (!($isencoded && $part == '')) {
|
143 |
+
$last_key = count($part_with_encoding) - 1;
|
144 |
+
if (isset($part_with_encoding[$last_key]) && $part_with_encoding[$last_key]['encoding'] == 'iso-8859-1') {
|
145 |
+
$part_with_encoding[$last_key]['string'] .= $part;
|
146 |
+
} else {
|
147 |
+
$part_with_encoding[] = array('encoding' => 'iso-8859-1', 'string' => $part);
|
148 |
+
}
|
149 |
+
} else {
|
150 |
+
DebugEcho("decodeHeader: skipping whitespace in encoded header");
|
151 |
+
}
|
152 |
+
}
|
153 |
+
}
|
154 |
+
|
155 |
+
//DebugEcho("decodeHeader: convert encoding");
|
156 |
+
foreach ($part_with_encoding as $part) {
|
157 |
+
//DebugDump($part);
|
158 |
+
$output .= self::iconv($part['encoding'], 'UTF-8', $part['string']);
|
159 |
+
}
|
160 |
+
DebugEcho("decodeHeader: $output");
|
161 |
+
|
162 |
+
return $output;
|
163 |
+
}
|
164 |
+
|
165 |
+
/**
|
166 |
+
* Handles an individual part of a multipart message
|
167 |
+
*
|
168 |
+
* @param array $info An array of information about the message
|
169 |
+
* @param array $structure An array describing the structure of the message
|
170 |
+
* @return array The modified $info array
|
171 |
+
*/
|
172 |
+
static private function handlePart($info, $structure) {
|
173 |
+
DebugEcho('handlePart: type ' . $structure['type'] . '/' . $structure['subtype']);
|
174 |
+
if ($structure['type'] == 'multipart') {
|
175 |
+
foreach ($structure['parts'] as $part) {
|
176 |
+
$info = self::handlePart($info, $part);
|
177 |
+
}
|
178 |
+
return $info;
|
179 |
+
}
|
180 |
+
|
181 |
+
if ($structure['type'] == 'application' && in_array($structure['subtype'], array('pkcs7-mime', 'x-pkcs7-mime'))) {
|
182 |
+
DebugEcho('handlePart: secure1 ' . $structure['subtype']);
|
183 |
+
$to = NULL;
|
184 |
+
if (isset($info['headers']['to'][0])) {
|
185 |
+
$to = $info['headers']['to'][0]['mailbox'];
|
186 |
+
if (!empty($info['headers']['to'][0]['host'])) {
|
187 |
+
$to .= '@' . $info['headers']['to'][0]['host'];
|
188 |
+
}
|
189 |
+
}
|
190 |
+
if ($to && !empty(self::$smime_pairs[$to]['private_key'])) {
|
191 |
+
if (self::handleSMIMEDecryption($info, $structure, self::$smime_pairs[$to])) {
|
192 |
+
return $info;
|
193 |
+
}
|
194 |
+
}
|
195 |
+
}
|
196 |
+
|
197 |
+
if ($structure['type'] == 'application' && in_array($structure['subtype'], array('pkcs7-signature', 'x-pkcs7-signature'))) {
|
198 |
+
DebugEcho('handlePart: secure2 ' . $structure['subtype']);
|
199 |
+
$from = NULL;
|
200 |
+
if (isset($info['headers']['from'])) {
|
201 |
+
$from = $info['headers']['from']['mailbox'];
|
202 |
+
if (!empty($info['headers']['from']['host'])) {
|
203 |
+
$from .= '@' . $info['headers']['from']['host'];
|
204 |
+
}
|
205 |
+
}
|
206 |
+
if ($from && !empty(self::$smime_pairs[$from]['certificate'])) {
|
207 |
+
if (self::handleSMIMEVerification($info, $structure, self::$smime_pairs[$from])) {
|
208 |
+
return $info;
|
209 |
+
}
|
210 |
+
}
|
211 |
+
}
|
212 |
+
|
213 |
+
$data = $structure['data'];
|
214 |
+
|
215 |
+
if ($structure['encoding'] == 'base64') {
|
216 |
+
DebugEcho('handlePart: base64');
|
217 |
+
$content = '';
|
218 |
+
foreach (explode("\r\n", $data) as $line) {
|
219 |
+
$content .= base64_decode($line);
|
220 |
+
}
|
221 |
+
} elseif ($structure['encoding'] == 'quoted-printable') {
|
222 |
+
DebugEcho('handlePart: quoted-printable');
|
223 |
+
$content = quoted_printable_decode($data);
|
224 |
+
} else {
|
225 |
+
DebugEcho('handlePart: no encoding');
|
226 |
+
$content = $data;
|
227 |
+
}
|
228 |
+
|
229 |
+
if ($structure['type'] == 'text') {
|
230 |
+
|
231 |
+
$charset = 'iso-8859-1';
|
232 |
+
foreach ($structure['type_fields'] as $field => $value) {
|
233 |
+
if (strtolower($field) == 'charset') {
|
234 |
+
$charset = $value;
|
235 |
+
DebugEcho("handlePart: charset $value detected");
|
236 |
+
break;
|
237 |
+
}
|
238 |
+
}
|
239 |
+
DebugEcho("handlePart: converting $charset to UTF-8");
|
240 |
+
$content = self::iconv($charset, 'UTF-8', $content);
|
241 |
+
if ($structure['subtype'] == 'html') {
|
242 |
+
DebugEcho("handlePart: fixing up html");
|
243 |
+
$content = preg_replace('#(content=(["\'])text/html\s*;\s*charset=(["\']?))' . preg_quote($charset, '#') . '(\3\2)#i', '\1utf-8\4', $content);
|
244 |
+
}
|
245 |
+
//DebugDump($content);
|
246 |
+
}
|
247 |
+
|
248 |
+
// This indicates a content-id which is used for multipart/related
|
249 |
+
if ($structure['content_id'] && $structure['type'] != 'text') {
|
250 |
+
DebugEcho('handlePart: multipart/related: ' . $structure['content_id']);
|
251 |
+
if (!isset($info['related'])) {
|
252 |
+
$info['related'] = array();
|
253 |
+
}
|
254 |
+
$cid = $structure['content_id'][0] == '<' ? substr($structure['content_id'], 1, -1) : $structure['content_id'];
|
255 |
+
DebugEcho('handlePart: cid: ' . $cid);
|
256 |
+
$info['related']['cid:' . $cid] = array(
|
257 |
+
'mimetype' => $structure['type'] . '/' . $structure['subtype'],
|
258 |
+
'data' => $content
|
259 |
+
);
|
260 |
+
return $info;
|
261 |
+
}
|
262 |
+
|
263 |
+
|
264 |
+
$has_disposition = !empty($structure['disposition']);
|
265 |
+
$is_text = $structure['type'] == 'text' && $structure['subtype'] == 'plain';
|
266 |
+
$is_html = $structure['type'] == 'text' && $structure['subtype'] == 'html';
|
267 |
+
|
268 |
+
// If the part doesn't have a disposition and is not the default text or html, set the disposition to inline
|
269 |
+
if (!$has_disposition && ((!$is_text || !empty($info['text'])) && (!$is_html || !empty($info['html'])))) {
|
270 |
+
$is_web_image = $structure['type'] == 'image' && in_array($structure['subtype'], array('gif', 'png', 'jpeg', 'pjpeg'));
|
271 |
+
$structure['disposition'] = $is_text || $is_html || $is_web_image ? 'inline' : 'attachment';
|
272 |
+
$structure['disposition_fields'] = array();
|
273 |
+
$has_disposition = TRUE;
|
274 |
+
}
|
275 |
+
|
276 |
+
|
277 |
+
// Attachments or inline content
|
278 |
+
if ($has_disposition) {
|
279 |
+
DebugEcho("handlePart: disposition set to " . $structure['disposition']);
|
280 |
+
|
281 |
+
$filename = '';
|
282 |
+
foreach ($structure['disposition_fields'] as $field => $value) {
|
283 |
+
if (strtolower($field) == 'filename') {
|
284 |
+
$filename = $value;
|
285 |
+
break;
|
286 |
+
}
|
287 |
+
}
|
288 |
+
foreach ($structure['type_fields'] as $field => $value) {
|
289 |
+
if (strtolower($field) == 'name') {
|
290 |
+
$filename = $value;
|
291 |
+
break;
|
292 |
+
}
|
293 |
+
}
|
294 |
+
|
295 |
+
// This automatically handles primary content that has a content-disposition header on it
|
296 |
+
if ($structure['disposition'] == 'inline' && $filename === '') {
|
297 |
+
DebugEcho("handlePart: inline un-named");
|
298 |
+
if (!isset($info['text'])) {
|
299 |
+
$info['text'] = '';
|
300 |
+
}
|
301 |
+
if ($is_text) {
|
302 |
+
if (empty($info['text'])) {
|
303 |
+
$info['text'] = $content;
|
304 |
+
return $info;
|
305 |
+
} else {
|
306 |
+
$info['text'] .= "\r\n$content";
|
307 |
+
return $info;
|
308 |
+
}
|
309 |
+
}
|
310 |
+
|
311 |
+
|
312 |
+
if ($is_html && !isset($info['html'])) {
|
313 |
+
$info['html'] = $content;
|
314 |
+
return $info;
|
315 |
+
}
|
316 |
+
}
|
317 |
+
|
318 |
+
if ($structure['disposition'] == 'inline' && $structure['type'] == 'image') {
|
319 |
+
$info['text'] .= "<:inline $filename inline:>";
|
320 |
+
}
|
321 |
+
|
322 |
+
if (!isset($info[$structure['disposition']])) {
|
323 |
+
$info[$structure['disposition']] = array();
|
324 |
+
}
|
325 |
+
|
326 |
+
$info[$structure['disposition']][] = array(
|
327 |
+
'filename' => $filename,
|
328 |
+
'mimetype' => $structure['type'] . '/' . $structure['subtype'],
|
329 |
+
'data' => $content
|
330 |
+
);
|
331 |
+
return $info;
|
332 |
+
} else {
|
333 |
+
DebugEcho("handlePart: no disposition set");
|
334 |
+
}
|
335 |
+
|
336 |
+
if ($is_text) {
|
337 |
+
DebugEcho("handlePart: text extracted");
|
338 |
+
$info['text'] = $content;
|
339 |
+
return $info;
|
340 |
+
}
|
341 |
+
|
342 |
+
if ($is_html) {
|
343 |
+
DebugEcho("handlePart: html extracted");
|
344 |
+
$info['html'] = $content;
|
345 |
+
return $info;
|
346 |
+
}
|
347 |
+
}
|
348 |
+
|
349 |
+
/**
|
350 |
+
* Tries to decrypt an S/MIME message using a private key
|
351 |
+
*
|
352 |
+
* @param array &$info The array of information about a message
|
353 |
+
* @param array $structure The structure of this part
|
354 |
+
* @param array $smime_pair An associative array containing an S/MIME certificate, private key and password
|
355 |
+
* @return boolean If the message was decrypted
|
356 |
+
*/
|
357 |
+
static private function handleSMIMEDecryption(&$info, $structure, $smime_pair) {
|
358 |
+
$plaintext_file = tempnam('', '__fMailbox_');
|
359 |
+
$ciphertext_file = tempnam('', '__fMailbox_');
|
360 |
+
|
361 |
+
$headers = array();
|
362 |
+
$headers[] = "Content-Type: " . $structure['type'] . '/' . $structure['subtype'];
|
363 |
+
$headers[] = "Content-Transfer-Encoding: " . $structure['encoding'];
|
364 |
+
$header = "Content-Disposition: " . $structure['disposition'];
|
365 |
+
foreach ($structure['disposition_fields'] as $field => $value) {
|
366 |
+
$header .= '; ' . $field . '="' . $value . '"';
|
367 |
+
}
|
368 |
+
$headers[] = $header;
|
369 |
+
|
370 |
+
file_put_contents($ciphertext_file, join("\r\n", $headers) . "\r\n\r\n" . $structure['data']);
|
371 |
+
|
372 |
+
$private_key = openssl_pkey_get_private(
|
373 |
+
$smime_pair['private_key']->read(), $smime_pair['password']
|
374 |
+
);
|
375 |
+
$certificate = $smime_pair['certificate']->read();
|
376 |
+
|
377 |
+
$result = openssl_pkcs7_decrypt($ciphertext_file, $plaintext_file, $certificate, $private_key);
|
378 |
+
unlink($ciphertext_file);
|
379 |
+
|
380 |
+
if (!$result) {
|
381 |
+
unlink($plaintext_file);
|
382 |
+
return FALSE;
|
383 |
+
}
|
384 |
+
|
385 |
+
$contents = file_get_contents($plaintext_file);
|
386 |
+
$info['raw_message'] = $contents;
|
387 |
+
$info = self::handlePart($info, self::parseStructure($contents));
|
388 |
+
$info['decrypted'] = TRUE;
|
389 |
+
|
390 |
+
unlink($plaintext_file);
|
391 |
+
return TRUE;
|
392 |
+
}
|
393 |
+
|
394 |
+
/**
|
395 |
+
* Takes a message with an S/MIME signature and verifies it if possible
|
396 |
+
*
|
397 |
+
* @param array &$info The array of information about a message
|
398 |
+
* @param array $structure
|
399 |
+
* @param array $smime_pair An associative array containing an S/MIME certificate file
|
400 |
+
* @return boolean If the message was verified
|
401 |
+
*/
|
402 |
+
static private function handleSMIMEVerification(&$info, $structure, $smime_pair) {
|
403 |
+
$certificates_file = tempnam('', '__fMailbox_');
|
404 |
+
$ciphertext_file = tempnam('', '__fMailbox_');
|
405 |
+
|
406 |
+
file_put_contents($ciphertext_file, $info['raw_message']);
|
407 |
+
|
408 |
+
$result = openssl_pkcs7_verify(
|
409 |
+
$ciphertext_file, PKCS7_NOINTERN | PKCS7_NOVERIFY, $certificates_file, array(), $smime_pair['certificate']->getPath()
|
410 |
+
);
|
411 |
+
unlink($ciphertext_file);
|
412 |
+
unlink($certificates_file);
|
413 |
+
|
414 |
+
if (!$result || $result === -1) {
|
415 |
+
return FALSE;
|
416 |
+
}
|
417 |
+
|
418 |
+
$info['verified'] = TRUE;
|
419 |
+
|
420 |
+
return TRUE;
|
421 |
+
}
|
422 |
+
|
423 |
+
/**
|
424 |
+
* This works around a bug in MAMP 1.9.4+ and PHP 5.3 where iconv()
|
425 |
+
* does not seem to properly assign the return value to a variable, but
|
426 |
+
* does work when returning the value.
|
427 |
+
*
|
428 |
+
* @param string $in_charset The incoming character encoding
|
429 |
+
* @param string $out_charset The outgoing character encoding
|
430 |
+
* @param string $string The string to convert
|
431 |
+
* @return string The converted string
|
432 |
+
*/
|
433 |
+
static private function iconv($in_charset, $out_charset, $string) {
|
434 |
+
return iconv($in_charset, "$out_charset//IGNORE", $string);
|
435 |
+
}
|
436 |
+
|
437 |
+
/**
|
438 |
+
* Parses a string representation of an email into the persona, mailbox and host parts
|
439 |
+
*
|
440 |
+
* @param string $string The email string to parse
|
441 |
+
* @return array An associative array with the key `mailbox`, and possibly `host` and `personal`
|
442 |
+
*/
|
443 |
+
static private function parseEmail($string) {
|
444 |
+
$email_regex = '((?:[^\x00-\x20\(\)<>@,;:\\\\"\.\[\]]+|"[^"\\\\\n\r]+")(?:\.[ \t]*(?:[^\x00-\x20\(\)<>@,;:\\\\"\.\[\]]+|"[^"\\\\\n\r]+"[ \t]*))*)@((?:[a-z0-9\\-]+\.)+[a-z]{2,}|\[(?:(?:[01]?\d?\d|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d?\d|2[0-4]\d|25[0-5])\])';
|
445 |
+
$name_regex = '((?:[^\x00-\x20\(\)<>@,;:\\\\"\.\[\]]+[ \t]*|"[^"\\\\\n\r]+"[ \t]*)(?:\.?[ \t]*(?:[^\x00-\x20\(\)<>@,;:\\\\"\.\[\]]+[ \t]*|"[^"\\\\\n\r]+"[ \t]*))*)';
|
446 |
+
|
447 |
+
if (preg_match('~^[ \t]*' . $name_regex . '[ \t]*<[ \t]*' . $email_regex . '[ \t]*>[ \t]*$~ixD', $string, $match)) {
|
448 |
+
$match[1] = trim($match[1]);
|
449 |
+
if ($match[1][0] == '"' && substr($match[1], -1) == '"') {
|
450 |
+
$match[1] = substr($match[1], 1, -1);
|
451 |
+
}
|
452 |
+
return array(
|
453 |
+
'personal' => self::decodeHeader($match[1]),
|
454 |
+
'mailbox' => self::decodeHeader($match[2]),
|
455 |
+
'host' => self::decodeHeader($match[3])
|
456 |
+
);
|
457 |
+
} elseif (preg_match('~^[ \t]*(?:<[ \t]*)?' . $email_regex . '(?:[ \t]*>)?[ \t]*$~ixD', $string, $match)) {
|
458 |
+
return array(
|
459 |
+
'mailbox' => self::decodeHeader($match[1]),
|
460 |
+
'host' => self::decodeHeader($match[2])
|
461 |
+
);
|
462 |
+
|
463 |
+
// This handles the outdated practice of including the personal
|
464 |
+
// part of the email in a comment after the email address
|
465 |
+
} elseif (preg_match('~^[ \t]*(?:<[ \t]*)?' . $email_regex . '(?:[ \t]*>)?[ \t]*\(([^)]+)\)[ \t]*$~ixD', $string, $match)) {
|
466 |
+
$match[3] = trim($match[1]);
|
467 |
+
if ($match[3][0] == '"' && substr($match[3], -1) == '"') {
|
468 |
+
$match[3] = substr($match[3], 1, -1);
|
469 |
+
}
|
470 |
+
|
471 |
+
return array(
|
472 |
+
'personal' => self::decodeHeader($match[3]),
|
473 |
+
'mailbox' => self::decodeHeader($match[1]),
|
474 |
+
'host' => self::decodeHeader($match[2])
|
475 |
+
);
|
476 |
+
}
|
477 |
+
|
478 |
+
if (strpos($string, '@') !== FALSE) {
|
479 |
+
list ($mailbox, $host) = explode('@', $string, 2);
|
480 |
+
return array(
|
481 |
+
'mailbox' => self::decodeHeader($mailbox),
|
482 |
+
'host' => self::decodeHeader($host)
|
483 |
+
);
|
484 |
+
}
|
485 |
+
|
486 |
+
return array(
|
487 |
+
'mailbox' => self::decodeHeader($string),
|
488 |
+
'host' => ''
|
489 |
+
);
|
490 |
+
}
|
491 |
+
|
492 |
+
/**
|
493 |
+
* Parses full email headers into an associative array
|
494 |
+
*
|
495 |
+
* @param string $headers The header to parse
|
496 |
+
* @param string $filter Remove any headers that match this
|
497 |
+
* @return array The parsed headers
|
498 |
+
*/
|
499 |
+
static public function parseHeaders($headers, $filter = NULL) {
|
500 |
+
$headers = trim($headers);
|
501 |
+
if (!strlen($headers)) {
|
502 |
+
return array();
|
503 |
+
}
|
504 |
+
$header_lines = preg_split("#\r\n(?!\s)#", $headers);
|
505 |
+
|
506 |
+
$single_email_fields = array('from', 'sender', 'reply-to', 'bcc');
|
507 |
+
$multi_email_fields = array('to', 'cc');
|
508 |
+
$additional_info_fields = array('content-type', 'content-disposition');
|
509 |
+
|
510 |
+
$headers = array();
|
511 |
+
foreach ($header_lines as $header_line) {
|
512 |
+
$header_line = preg_replace("#\r\n\s+#", '', $header_line);
|
513 |
+
|
514 |
+
if (false !== strpos($header_line, ':')) {
|
515 |
+
list ($header, $value) = preg_split('#:\s*#', $header_line, 2);
|
516 |
+
$header = strtolower($header);
|
517 |
+
|
518 |
+
if (strpos($header, $filter) !== FALSE) {
|
519 |
+
continue;
|
520 |
+
}
|
521 |
+
|
522 |
+
$is_single_email = in_array($header, $single_email_fields);
|
523 |
+
$is_multi_email = in_array($header, $multi_email_fields);
|
524 |
+
$is_additional_info_field = in_array($header, $additional_info_fields);
|
525 |
+
|
526 |
+
if ($is_additional_info_field) {
|
527 |
+
$pieces = preg_split('#;\s*#', $value, 2);
|
528 |
+
$value = $pieces[0];
|
529 |
+
|
530 |
+
$headers[$header] = array('value' => self::decodeHeader($value));
|
531 |
+
|
532 |
+
$fields = array();
|
533 |
+
if (!empty($pieces[1])) {
|
534 |
+
preg_match_all('#(\w+)=("([^"]+)"|([^\s;]+))(?=;|$)#', $pieces[1], $matches, PREG_SET_ORDER);
|
535 |
+
foreach ($matches as $match) {
|
536 |
+
$fields[$match[1]] = self::decodeHeader(!empty($match[4]) ? $match[4] : $match[3]);
|
537 |
+
}
|
538 |
+
}
|
539 |
+
$headers[$header]['fields'] = $fields;
|
540 |
+
} elseif ($is_single_email) {
|
541 |
+
//DebugEcho("parseHeaders: $header:$value");
|
542 |
+
$headers[$header] = self::parseEmail($value);
|
543 |
+
} elseif ($is_multi_email) {
|
544 |
+
$strings = array();
|
545 |
+
|
546 |
+
preg_match_all('#"[^"]+?"#', $value, $matches, PREG_SET_ORDER);
|
547 |
+
foreach ($matches as $i => $match) {
|
548 |
+
$strings[] = $match[0];
|
549 |
+
$value = preg_replace('#' . preg_quote($match[0], '#') . '#', ':string' . sizeof($strings), $value, 1);
|
550 |
+
}
|
551 |
+
preg_match_all('#\([^)]+?\)#', $value, $matches, PREG_SET_ORDER);
|
552 |
+
foreach ($matches as $i => $match) {
|
553 |
+
$strings[] = $match[0];
|
554 |
+
$value = preg_replace('#' . preg_quote($match[0], '#') . '#', ':string' . sizeof($strings), $value, 1);
|
555 |
+
}
|
556 |
+
|
557 |
+
$emails = explode(',', $value);
|
558 |
+
array_map('trim', $emails);
|
559 |
+
foreach ($strings as $i => $string) {
|
560 |
+
$emails = preg_replace(
|
561 |
+
'#:string' . ($i + 1) . '\b#', strtr($string, array('\\' => '\\\\', '$' => '\\$')), $emails, 1
|
562 |
+
);
|
563 |
+
}
|
564 |
+
|
565 |
+
$headers[$header] = array();
|
566 |
+
foreach ($emails as $email) {
|
567 |
+
$headers[$header][] = self::parseEmail($email);
|
568 |
+
}
|
569 |
+
} elseif ($header == 'references') {
|
570 |
+
$headers[$header] = array_map(array('fMailbox', 'decodeHeader'), preg_split('#(?<=>)\s+(?=<)#', $value));
|
571 |
+
} elseif ($header == 'received') {
|
572 |
+
if (!isset($headers[$header])) {
|
573 |
+
$headers[$header] = array();
|
574 |
+
}
|
575 |
+
$headers[$header][] = preg_replace('#\s+#', ' ', self::decodeHeader($value));
|
576 |
+
} else {
|
577 |
+
$headers[$header] = self::decodeHeader($value);
|
578 |
+
}
|
579 |
+
}
|
580 |
+
}
|
581 |
+
return $headers;
|
582 |
+
}
|
583 |
+
|
584 |
+
/**
|
585 |
+
* Parses a MIME message into an associative array of information
|
586 |
+
*
|
587 |
+
* The output includes the following keys:
|
588 |
+
*
|
589 |
+
* - `'received'`: The date the message was received by the server
|
590 |
+
* - `'headers'`: An associative array of mail headers, the keys are the header names, in lowercase
|
591 |
+
*
|
592 |
+
* And one or more of the following:
|
593 |
+
*
|
594 |
+
* - `'text'`: The plaintext body
|
595 |
+
* - `'html'`: The HTML body
|
596 |
+
* - `'attachment'`: An array of attachments, each containing:
|
597 |
+
* - `'filename'`: The name of the file
|
598 |
+
* - `'mimetype'`: The mimetype of the file
|
599 |
+
* - `'data'`: The raw contents of the file
|
600 |
+
* - `'inline'`: An array of inline files, each containing:
|
601 |
+
* - `'filename'`: The name of the file
|
602 |
+
* - `'mimetype'`: The mimetype of the file
|
603 |
+
* - `'data'`: The raw contents of the file
|
604 |
+
* - `'related'`: An associative array of related files, such as embedded images, with the key `'cid:{content-id}'` and an array value containing:
|
605 |
+
* - `'mimetype'`: The mimetype of the file
|
606 |
+
* - `'data'`: The raw contents of the file
|
607 |
+
* - `'verified'`: If the message contents were verified via an S/MIME certificate - if not verified the smime.p7s will be listed as an attachment
|
608 |
+
* - `'decrypted'`: If the message contents were decrypted via an S/MIME private key - if not decrypted the smime.p7m will be listed as an attachment
|
609 |
+
*
|
610 |
+
* All values in `headers`, `text` and `body` will have been decoded to
|
611 |
+
* UTF-8. Files in the `attachment`, `inline` and `related` array will all
|
612 |
+
* retain their original encodings.
|
613 |
+
*
|
614 |
+
* @param string $message The full source of the email message
|
615 |
+
* @param boolean $convert_newlines If `\r\n` should be converted to `\n` in the `text` and `html` parts the message
|
616 |
+
* @return array The parsed email message - see method description for details
|
617 |
+
*/
|
618 |
+
static public function parseMessage($message, $convert_newlines = FALSE) {
|
619 |
+
$info = array();
|
620 |
+
list ($headers, $body) = explode("\r\n\r\n", $message, 2);
|
621 |
+
$parsed_headers = self::parseHeaders($headers);
|
622 |
+
$info['received'] = self::cleanDate(preg_replace('#^.*;\s*([^;]+)$#', '\1', $parsed_headers['received'][0]));
|
623 |
+
$info['headers'] = array();
|
624 |
+
foreach ($parsed_headers as $header => $value) {
|
625 |
+
if (substr($header, 0, 8) == 'content-') {
|
626 |
+
continue;
|
627 |
+
}
|
628 |
+
$info['headers'][$header] = $value;
|
629 |
+
}
|
630 |
+
$info['raw_headers'] = $headers;
|
631 |
+
$info['raw_message'] = $message;
|
632 |
+
|
633 |
+
$info = self::handlePart($info, self::parseStructure($body, $parsed_headers));
|
634 |
+
|
635 |
+
unset($info['raw_message']);
|
636 |
+
unset($info['raw_headers']);
|
637 |
+
|
638 |
+
if ($convert_newlines) {
|
639 |
+
DebugEcho("parseMessage: converting newlines");
|
640 |
+
if (isset($info['text'])) {
|
641 |
+
$info['text'] = str_replace("\r\n", "\n", $info['text']);
|
642 |
+
}
|
643 |
+
if (isset($info['html'])) {
|
644 |
+
$info['html'] = str_replace("\r\n", "\n", $info['html']);
|
645 |
+
}
|
646 |
+
}
|
647 |
+
|
648 |
+
if (isset($info['text'])) {
|
649 |
+
$info['text'] = preg_replace('#\r?\n$#D', '', $info['text']);
|
650 |
+
} else {
|
651 |
+
$info['text'] = '';
|
652 |
+
}
|
653 |
+
if (isset($info['html'])) {
|
654 |
+
$info['html'] = preg_replace('#\r?\n$#D', '', $info['html']);
|
655 |
+
} else {
|
656 |
+
$info['html'] = '';
|
657 |
+
}
|
658 |
+
|
659 |
+
return $info;
|
660 |
+
}
|
661 |
+
|
662 |
+
/**
|
663 |
+
* Takes a response from an IMAP command and parses it into a
|
664 |
+
* multi-dimensional array
|
665 |
+
*
|
666 |
+
* @param string $text The IMAP command response
|
667 |
+
* @param boolean $top_level If we are parsing the top level
|
668 |
+
* @return array The parsed representation of the response text
|
669 |
+
*/
|
670 |
+
static private function parseResponse($text, $top_level = FALSE) {
|
671 |
+
$regex = '[\\\\\w.\[\]]+|"([^"\\\\]+|\\\\"|\\\\\\\\)*"|\((?:(?1)[ \t]*)*\)';
|
672 |
+
|
673 |
+
if (preg_match('#\{(\d+)\}#', $text, $match)) {
|
674 |
+
$regex = '\{' . $match[1] . '\}\r\n.{' . ($match[1]) . '}|' . $regex;
|
675 |
+
}
|
676 |
+
|
677 |
+
preg_match_all('#(' . $regex . ')#s', $text, $matches, PREG_SET_ORDER);
|
678 |
+
$output = array();
|
679 |
+
foreach ($matches as $match) {
|
680 |
+
if (substr($match[0], 0, 1) == '"') {
|
681 |
+
$output[] = str_replace('\\"', '"', substr($match[0], 1, -1));
|
682 |
+
} elseif (substr($match[0], 0, 1) == '(') {
|
683 |
+
$output[] = self::parseResponse(substr($match[0], 1, -1));
|
684 |
+
} elseif (substr($match[0], 0, 1) == '{') {
|
685 |
+
$output[] = preg_replace('#^[^\r]+\r\n#', '', $match[0]);
|
686 |
+
} else {
|
687 |
+
$output[] = $match[0];
|
688 |
+
}
|
689 |
+
}
|
690 |
+
|
691 |
+
if ($top_level) {
|
692 |
+
$new_output = array();
|
693 |
+
$total_size = count($output);
|
694 |
+
for ($i = 0; $i < $total_size; $i = $i + 2) {
|
695 |
+
$new_output[strtolower($output[$i])] = $output[$i + 1];
|
696 |
+
}
|
697 |
+
$output = $new_output;
|
698 |
+
}
|
699 |
+
|
700 |
+
return $output;
|
701 |
+
}
|
702 |
+
|
703 |
+
/**
|
704 |
+
* Takes the raw contents of a MIME message and creates an array that
|
705 |
+
* describes the structure of the message
|
706 |
+
*
|
707 |
+
* @param string $data The contents to get the structure of
|
708 |
+
* @param string $headers The parsed headers for the message - if not present they will be extracted from the `$data`
|
709 |
+
* @return array The multi-dimensional, associative array containing the message structure
|
710 |
+
*/
|
711 |
+
static private function parseStructure($data, $headers = NULL) {
|
712 |
+
if (!$headers) {
|
713 |
+
list ($headers, $data) = preg_split("#^\r\n|\r\n\r\n#", $data, 2);
|
714 |
+
$headers = self::parseHeaders($headers);
|
715 |
+
}
|
716 |
+
|
717 |
+
if (!isset($headers['content-type'])) {
|
718 |
+
$headers['content-type'] = array(
|
719 |
+
'value' => 'text/plain',
|
720 |
+
'fields' => array()
|
721 |
+
);
|
722 |
+
}
|
723 |
+
|
724 |
+
list ($type, $subtype) = explode('/', strtolower($headers['content-type']['value']), 2);
|
725 |
+
|
726 |
+
if ($type == 'multipart') {
|
727 |
+
$structure = array(
|
728 |
+
'type' => $type,
|
729 |
+
'subtype' => $subtype,
|
730 |
+
'parts' => array()
|
731 |
+
);
|
732 |
+
$boundary = $headers['content-type']['fields']['boundary'];
|
733 |
+
$start_pos = strpos($data, '--' . $boundary) + strlen($boundary) + 4;
|
734 |
+
$end_pos = strrpos($data, '--' . $boundary . '--') - 2;
|
735 |
+
$sub_contents = explode("\r\n--" . $boundary . "\r\n", substr(
|
736 |
+
$data, $start_pos, $end_pos - $start_pos
|
737 |
+
));
|
738 |
+
foreach ($sub_contents as $sub_content) {
|
739 |
+
$structure['parts'][] = self::parseStructure($sub_content);
|
740 |
+
}
|
741 |
+
} else {
|
742 |
+
$structure = array(
|
743 |
+
'type' => $type,
|
744 |
+
'type_fields' => !empty($headers['content-type']['fields']) ? $headers['content-type']['fields'] : array(),
|
745 |
+
'subtype' => $subtype,
|
746 |
+
'content_id' => isset($headers['content-id']) ? $headers['content-id'] : NULL,
|
747 |
+
'encoding' => isset($headers['content-transfer-encoding']) ? strtolower($headers['content-transfer-encoding']) : '8bit',
|
748 |
+
'disposition' => isset($headers['content-disposition']) ? strtolower($headers['content-disposition']['value']) : NULL,
|
749 |
+
'disposition_fields' => isset($headers['content-disposition']) ? $headers['content-disposition']['fields'] : array(),
|
750 |
+
'data' => $data
|
751 |
+
);
|
752 |
+
}
|
753 |
+
|
754 |
+
return $structure;
|
755 |
+
}
|
756 |
+
|
757 |
+
/**
|
758 |
+
* Resets the configuration of the class
|
759 |
+
*
|
760 |
+
* @internal
|
761 |
+
*
|
762 |
+
* @return void
|
763 |
+
*/
|
764 |
+
static public function reset() {
|
765 |
+
self::$smime_pairs = array();
|
766 |
+
}
|
767 |
+
|
768 |
+
/**
|
769 |
+
* Takes an associative array and unfolds the keys and values so that the
|
770 |
+
* result in an integer-indexed array of `0 => key1, 1 => value1, 2 => key2,
|
771 |
+
* 3 => value2, ...`.
|
772 |
+
*
|
773 |
+
* @param array $array The array to unfold
|
774 |
+
* @return array The unfolded array
|
775 |
+
*/
|
776 |
+
static private function unfoldAssociativeArray($array) {
|
777 |
+
$new_array = array();
|
778 |
+
foreach ($array as $key => $value) {
|
779 |
+
$new_array[] = $key;
|
780 |
+
$new_array[] = $value;
|
781 |
+
}
|
782 |
+
return $new_array;
|
783 |
+
}
|
784 |
+
|
785 |
+
/**
|
786 |
+
* A counter to use for generating command keys
|
787 |
+
*
|
788 |
+
* @var integer
|
789 |
+
*/
|
790 |
+
|
791 |
+
/**
|
792 |
+
* The connection resource
|
793 |
+
*
|
794 |
+
* @var resource
|
795 |
+
*/
|
796 |
+
private $connection;
|
797 |
+
|
798 |
+
/**
|
799 |
+
* If debugging has been enabled
|
800 |
+
*
|
801 |
+
* @var boolean
|
802 |
+
*/
|
803 |
+
private $debug;
|
804 |
+
|
805 |
+
/**
|
806 |
+
* The server hostname or IP address
|
807 |
+
*
|
808 |
+
* @var string
|
809 |
+
*/
|
810 |
+
private $host;
|
811 |
+
|
812 |
+
/**
|
813 |
+
* The password for the account
|
814 |
+
*
|
815 |
+
* @var string
|
816 |
+
*/
|
817 |
+
private $password;
|
818 |
+
|
819 |
+
/**
|
820 |
+
* The port for the server
|
821 |
+
*
|
822 |
+
* @var integer
|
823 |
+
*/
|
824 |
+
private $port;
|
825 |
+
|
826 |
+
/**
|
827 |
+
* If the connection to the server should be secure
|
828 |
+
*
|
829 |
+
* @var boolean
|
830 |
+
*/
|
831 |
+
private $secure;
|
832 |
+
|
833 |
+
/**
|
834 |
+
* The timeout for the connection
|
835 |
+
*
|
836 |
+
* @var integer
|
837 |
+
*/
|
838 |
+
private $timeout = 5;
|
839 |
+
|
840 |
+
/**
|
841 |
+
* The type of mailbox, `'imap'` or `'pop3'`
|
842 |
+
*
|
843 |
+
* @var string
|
844 |
+
*/
|
845 |
+
private $type;
|
846 |
+
|
847 |
+
/**
|
848 |
+
* The username for the account
|
849 |
+
*
|
850 |
+
* @var string
|
851 |
+
*/
|
852 |
+
private $username;
|
853 |
+
private $server;
|
854 |
+
|
855 |
+
/**
|
856 |
+
* Configures the connection to the server
|
857 |
+
*
|
858 |
+
* Please note that the GMail POP3 server does not act like other POP3
|
859 |
+
* servers and the GMail IMAP server should be used instead. GMail POP3 only
|
860 |
+
* allows retrieving a message once - during future connections the email
|
861 |
+
* in question will no longer be available.
|
862 |
+
*
|
863 |
+
* @param string $type The type of mailbox, `'pop3'` or `'imap'`
|
864 |
+
* @connection pConnection
|
865 |
+
* @return fMailbox
|
866 |
+
*/
|
867 |
+
public function __construct($type, $connection, $server) {
|
868 |
+
|
869 |
+
$valid_types = array('imap', 'pop3');
|
870 |
+
if (!in_array($type, $valid_types)) {
|
871 |
+
throw new fProgrammerException('The mailbox type specified, %1$s, in invalid. Must be one of: %2$s.', $type, join(', ', $valid_types));
|
872 |
+
}
|
873 |
+
|
874 |
+
$this->type = $type;
|
875 |
+
$this->connection = $connection;
|
876 |
+
$this->server = $server;
|
877 |
+
}
|
878 |
+
|
879 |
+
/**
|
880 |
+
* Disconnects from the server
|
881 |
+
*
|
882 |
+
* @return void
|
883 |
+
*/
|
884 |
+
public function __destruct() {
|
885 |
+
$this->close();
|
886 |
+
}
|
887 |
+
|
888 |
+
/**
|
889 |
+
* Closes the connection to the server
|
890 |
+
*
|
891 |
+
* @return void
|
892 |
+
*/
|
893 |
+
public function close() {
|
894 |
+
if (!$this->connection) {
|
895 |
+
return;
|
896 |
+
}
|
897 |
+
$this->server->close();
|
898 |
+
|
899 |
+
$this->connection->close();
|
900 |
+
$this->connection = NULL;
|
901 |
+
}
|
902 |
+
|
903 |
+
/**
|
904 |
+
* Connects to the server
|
905 |
+
*
|
906 |
+
* @return void
|
907 |
+
*/
|
908 |
+
private function connect() {
|
909 |
+
$this->connection->open();
|
910 |
+
}
|
911 |
+
|
912 |
+
/**
|
913 |
+
* Deletes one or more messages from the server
|
914 |
+
*
|
915 |
+
* Passing more than one UID at a time is more efficient for IMAP mailboxes,
|
916 |
+
* whereas POP3 mailboxes will see no difference in performance.
|
917 |
+
*
|
918 |
+
* @param integer|array $uid The UID(s) of the message(s) to delete
|
919 |
+
* @return void
|
920 |
+
*/
|
921 |
+
public function deleteMessages($uid) {
|
922 |
+
$this->connect();
|
923 |
+
$this->server->deleteMessages($uid);
|
924 |
+
}
|
925 |
+
|
926 |
+
/**
|
927 |
+
* Sets if debug messages should be shown
|
928 |
+
*
|
929 |
+
* @param boolean $flag If debugging messages should be shown
|
930 |
+
* @return void
|
931 |
+
*/
|
932 |
+
public function enableDebugging($flag) {
|
933 |
+
$this->debug = (boolean) $flag;
|
934 |
+
}
|
935 |
+
|
936 |
+
/**
|
937 |
+
* Retrieves a single message from the server
|
938 |
+
*
|
939 |
+
* The output includes the following keys:
|
940 |
+
*
|
941 |
+
* - `'uid'`: The UID of the message
|
942 |
+
* - `'received'`: The date the message was received by the server
|
943 |
+
* - `'headers'`: An associative array of mail headers, the keys are the header names, in lowercase
|
944 |
+
*
|
945 |
+
* And one or more of the following:
|
946 |
+
*
|
947 |
+
* - `'text'`: The plaintext body
|
948 |
+
* - `'html'`: The HTML body
|
949 |
+
* - `'attachment'`: An array of attachments, each containing:
|
950 |
+
* - `'filename'`: The name of the file
|
951 |
+
* - `'mimetype'`: The mimetype of the file
|
952 |
+
* - `'data'`: The raw contents of the file
|
953 |
+
* - `'inline'`: An array of inline files, each containing:
|
954 |
+
* - `'filename'`: The name of the file
|
955 |
+
* - `'mimetype'`: The mimetype of the file
|
956 |
+
* - `'data'`: The raw contents of the file
|
957 |
+
* - `'related'`: An associative array of related files, such as embedded images, with the key `'cid:{content-id}'` and an array value containing:
|
958 |
+
* - `'mimetype'`: The mimetype of the file
|
959 |
+
* - `'data'`: The raw contents of the file
|
960 |
+
* - `'verified'`: If the message contents were verified via an S/MIME certificate - if not verified the smime.p7s will be listed as an attachment
|
961 |
+
* - `'decrypted'`: If the message contents were decrypted via an S/MIME private key - if not decrypted the smime.p7m will be listed as an attachment
|
962 |
+
*
|
963 |
+
* All values in `headers`, `text` and `body` will have been decoded to
|
964 |
+
* UTF-8. Files in the `attachment`, `inline` and `related` array will all
|
965 |
+
* retain their original encodings.
|
966 |
+
*
|
967 |
+
* @param integer $uid The UID of the message to retrieve
|
968 |
+
* @param boolean $convert_newlines If `\r\n` should be converted to `\n` in the `text` and `html` parts the message
|
969 |
+
* @return array The parsed email message - see method description for details
|
970 |
+
*/
|
971 |
+
public function fetchMessage($uid, $convert_newlines = FALSE) {
|
972 |
+
$this->connect();
|
973 |
+
|
974 |
+
$source = $this->fetchMessageSource($uid);
|
975 |
+
|
976 |
+
$info = self::parseMessage($source, $convert_newlines);
|
977 |
+
$info['uid'] = $uid;
|
978 |
+
|
979 |
+
return $info;
|
980 |
+
}
|
981 |
+
|
982 |
+
/**
|
983 |
+
* Retrieves the raw source of a single message from the server
|
984 |
+
*
|
985 |
+
* This method is primarily useful for storing the raw source of a message.
|
986 |
+
* Normal use of fMailbox would involved calling ::fetchMessage(), which
|
987 |
+
* calls this method and then ::parseMessage().
|
988 |
+
*
|
989 |
+
* @param integer $uid The UID of the message to retrieve
|
990 |
+
* @return string The raw message source of the email
|
991 |
+
*/
|
992 |
+
public function fetchMessageSource($uid) {
|
993 |
+
return $this->server->fetchMessageSource($uid);
|
994 |
+
}
|
995 |
+
|
996 |
+
/**
|
997 |
+
* Count the number of unread messages
|
998 |
+
*
|
999 |
+
* @return integer The number of unread messages
|
1000 |
+
*/
|
1001 |
+
public function countMessages() {
|
1002 |
+
return $this->server->countMessages();
|
1003 |
+
}
|
1004 |
+
|
1005 |
+
/**
|
1006 |
+
* Gets a list of messages from the server
|
1007 |
+
*
|
1008 |
+
* The structure of the returned array is:
|
1009 |
+
*
|
1010 |
+
* {{{
|
1011 |
+
* array(
|
1012 |
+
* (integer) {uid} => array(
|
1013 |
+
* 'uid' => (integer) {a unique identifier for this message on this server},
|
1014 |
+
* 'received' => (string) {date message was received},
|
1015 |
+
* 'size' => (integer) {size of message in bytes},
|
1016 |
+
* 'date' => (string) {date message was sent},
|
1017 |
+
* 'from' => (string) {the from header value},
|
1018 |
+
* 'subject' => (string) {the message subject},
|
1019 |
+
* 'message_id' => (string) {optional - the message-id header value, should be globally unique},
|
1020 |
+
* 'to' => (string) {optional - the to header value},
|
1021 |
+
* 'in_reply_to' => (string) {optional - the in-reply-to header value}
|
1022 |
+
* ), ...
|
1023 |
+
* )
|
1024 |
+
* }}}
|
1025 |
+
*
|
1026 |
+
* All values will have been decoded to UTF-8.
|
1027 |
+
*
|
1028 |
+
* @param integer $limit The number of messages to retrieve
|
1029 |
+
* @param integer $page The page of messages to retrieve
|
1030 |
+
* @return array A list of messages on the server - see method description for details
|
1031 |
+
*/
|
1032 |
+
public function listMessages($limit = NULL, $page = NULL) {
|
1033 |
+
return $this->server->listMessages($limit, $page);
|
1034 |
+
}
|
1035 |
+
|
1036 |
+
/**
|
1037 |
+
* Reads responses from the server
|
1038 |
+
*
|
1039 |
+
* @param integer|string $expect The expected number of lines of response or a regex of the last line
|
1040 |
+
* @return array The lines of response from the server
|
1041 |
+
*/
|
1042 |
+
private function read($expect = NULL) {
|
1043 |
+
if (!$this->connection) {
|
1044 |
+
throw new fProgrammerException('Unable to send data since the connection has already been closed');
|
1045 |
+
}
|
1046 |
+
return $this->connection->read($expect);
|
1047 |
+
}
|
1048 |
+
|
1049 |
+
/**
|
1050 |
+
* Sends commands to the IMAP or POP3 server
|
1051 |
+
*
|
1052 |
+
* @param string $command The command to send
|
1053 |
+
* @param integer $expected The number of lines or regex expected for a POP3 command
|
1054 |
+
* @return array The response from the server
|
1055 |
+
*/
|
1056 |
+
private function write($command, $expected = NULL) {
|
1057 |
+
if (!$this->connection) {
|
1058 |
+
throw new fProgrammerException('Unable to send data since the connection has already been closed');
|
1059 |
+
}
|
1060 |
+
|
1061 |
+
return $this->connection->write($command, $expected);
|
1062 |
+
}
|
1063 |
+
|
1064 |
+
}
|
1065 |
+
|
1066 |
+
/**
|
1067 |
+
* Copyright (c) 2010-2012 Will Bond <will@flourishlib.com>
|
1068 |
+
*
|
1069 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
1070 |
+
* of this software and associated documentation files (the "Software"), to deal
|
1071 |
+
* in the Software without restriction, including without limitation the rights
|
1072 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
1073 |
+
* copies of the Software, and to permit persons to whom the Software is
|
1074 |
+
* furnished to do so, subject to the following conditions:
|
1075 |
+
*
|
1076 |
+
* The above copyright notice and this permission notice shall be included in
|
1077 |
+
* all copies or substantial portions of the Software.
|
1078 |
+
*
|
1079 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
1080 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
1081 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
1082 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
1083 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
1084 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
1085 |
+
* THE SOFTWARE.
|
1086 |
+
*/
|
lib/fProgrammerException.php
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* An exception caused by programmer error
|
4 |
+
*
|
5 |
+
* @copyright Copyright (c) 2007-2008 Will Bond
|
6 |
+
* @author Will Bond [wb] <will@flourishlib.com>
|
7 |
+
* @license http://flourishlib.com/license
|
8 |
+
*
|
9 |
+
* @package Flourish
|
10 |
+
* @link http://flourishlib.com/fProgrammerException
|
11 |
+
*
|
12 |
+
* @version 1.0.0b
|
13 |
+
* @changes 1.0.0b The initial implementation [wb, 2007-06-14]
|
14 |
+
*/
|
15 |
+
class fProgrammerException extends fUnexpectedException
|
16 |
+
{
|
17 |
+
}
|
18 |
+
|
19 |
+
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Copyright (c) 2007-2008 Will Bond <will@flourishlib.com>
|
23 |
+
*
|
24 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
25 |
+
* of this software and associated documentation files (the "Software"), to deal
|
26 |
+
* in the Software without restriction, including without limitation the rights
|
27 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
28 |
+
* copies of the Software, and to permit persons to whom the Software is
|
29 |
+
* furnished to do so, subject to the following conditions:
|
30 |
+
*
|
31 |
+
* The above copyright notice and this permission notice shall be included in
|
32 |
+
* all copies or substantial portions of the Software.
|
33 |
+
*
|
34 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
35 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
36 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
37 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
38 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
39 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
40 |
+
* THE SOFTWARE.
|
41 |
+
*/
|
lib/fUnexpectedException.php
ADDED
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* An exception that should probably not be handled by the display code, fCore::enableExceptionHandler() is recommended
|
4 |
+
*
|
5 |
+
* @copyright Copyright (c) 2007-2011 Will Bond
|
6 |
+
* @author Will Bond [wb] <will@flourishlib.com>
|
7 |
+
* @license http://flourishlib.com/license
|
8 |
+
*
|
9 |
+
* @package Flourish
|
10 |
+
* @link http://flourishlib.com/fUnexpectedException
|
11 |
+
*
|
12 |
+
* @version 1.0.0b2
|
13 |
+
* @changes 1.0.0b2 Updated ::printMessage() to use an ASCII dash to prevent encoding issues when an output encoding is not specified [wb, 2011-05-09]
|
14 |
+
* @changes 1.0.0b The initial implementation [wb, 2007-06-14]
|
15 |
+
*/
|
16 |
+
class fUnexpectedException extends fException
|
17 |
+
{
|
18 |
+
/**
|
19 |
+
* Prints out a generic error message inside of a `div` with the class being `'exception {exception_class_name}'`
|
20 |
+
*
|
21 |
+
* @return void
|
22 |
+
*/
|
23 |
+
public function printMessage()
|
24 |
+
{
|
25 |
+
echo '<div class="exception ' . $this->getCSSClass() . '"><p>';
|
26 |
+
echo self::compose(
|
27 |
+
'It appears an error has occurred - we apologize for the inconvenience. The problem may be resolved if you try again.'
|
28 |
+
);
|
29 |
+
echo '</p></div>';
|
30 |
+
}
|
31 |
+
}
|
32 |
+
|
33 |
+
|
34 |
+
|
35 |
+
/**
|
36 |
+
* Copyright (c) 2007-2011 Will Bond <will@flourishlib.com>
|
37 |
+
*
|
38 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
39 |
+
* of this software and associated documentation files (the "Software"), to deal
|
40 |
+
* in the Software without restriction, including without limitation the rights
|
41 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
42 |
+
* copies of the Software, and to permit persons to whom the Software is
|
43 |
+
* furnished to do so, subject to the following conditions:
|
44 |
+
*
|
45 |
+
* The above copyright notice and this permission notice shall be included in
|
46 |
+
* all copies or substantial portions of the Software.
|
47 |
+
*
|
48 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
49 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
50 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
51 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
52 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
53 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
54 |
+
* THE SOFTWARE.
|
55 |
+
*/
|
lib/fValidationException.php
ADDED
@@ -0,0 +1,211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* An exception caused by a data not matching a rule or set of rules
|
4 |
+
*
|
5 |
+
* @copyright Copyright (c) 2007-2010 Will Bond, others
|
6 |
+
* @author Will Bond [wb] <will@flourishlib.com>
|
7 |
+
* @author Will Bond, iMarc LLC [wb-imarc] <will@imarc.net>
|
8 |
+
* @license http://flourishlib.com/license
|
9 |
+
*
|
10 |
+
* @package Flourish
|
11 |
+
* @link http://flourishlib.com/fValidationException
|
12 |
+
*
|
13 |
+
* @version 1.0.0b4
|
14 |
+
* @changes 1.0.0b4 Added support for nested error arrays [wb-imarc, 2010-10-03]
|
15 |
+
* @changes 1.0.0b3 Added ::removeFieldNames() [wb, 2010-05-26]
|
16 |
+
* @changes 1.0.0b2 Added a custom ::__construct() to handle arrays of messages [wb, 2009-09-17]
|
17 |
+
* @changes 1.0.0b The initial implementation [wb, 2007-06-14]
|
18 |
+
*/
|
19 |
+
class fValidationException extends fExpectedException
|
20 |
+
{
|
21 |
+
const formatField = 'fValidationException::formatField';
|
22 |
+
const removeFieldNames = 'fValidationException::removeFieldNames';
|
23 |
+
const setFieldFormat = 'fValidationException::setFieldFormat';
|
24 |
+
|
25 |
+
/**
|
26 |
+
* The formatting string to use for field names
|
27 |
+
*
|
28 |
+
* @var string
|
29 |
+
*/
|
30 |
+
static protected $field_format = '%s: ';
|
31 |
+
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Accepts a field name and formats it based on the formatting string set via ::setFieldFormat()
|
35 |
+
*
|
36 |
+
* @param string $field The name of the field to format
|
37 |
+
* @return string The formatted field name
|
38 |
+
*/
|
39 |
+
static public function formatField($field)
|
40 |
+
{
|
41 |
+
return sprintf(self::$field_format, $field);
|
42 |
+
}
|
43 |
+
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Removes the field names from normal validation messages, leaving just the message part
|
47 |
+
*
|
48 |
+
* @param array $messages The messages to remove the field names from
|
49 |
+
* @return array The messages without field names
|
50 |
+
*/
|
51 |
+
static public function removeFieldNames($messages)
|
52 |
+
{
|
53 |
+
$token_field = self::formatField('__TOKEN__');
|
54 |
+
$replace_regex = '#^' . str_replace('__TOKEN__', '(.*?)', preg_quote($token_field, '#')) . '#';
|
55 |
+
|
56 |
+
$output = array();
|
57 |
+
foreach ($messages as $column => $message) {
|
58 |
+
if (is_array($message)) {
|
59 |
+
$message['errors'] = self::removeFieldNames($message['errors']);
|
60 |
+
$output[$column] = $message;
|
61 |
+
} else {
|
62 |
+
$output[$column] = preg_replace($replace_regex, '', $message);
|
63 |
+
}
|
64 |
+
}
|
65 |
+
|
66 |
+
return $output;
|
67 |
+
}
|
68 |
+
|
69 |
+
|
70 |
+
/**
|
71 |
+
* Set the format to be applied to all field names used in fValidationExceptions
|
72 |
+
*
|
73 |
+
* The format should contain exactly one `%s`
|
74 |
+
* [http://php.net/sprintf sprintf()] conversion specification, which will
|
75 |
+
* be replaced with the field name. Any literal `%` characters should be
|
76 |
+
* written as `%%`.
|
77 |
+
*
|
78 |
+
* The default format is just `%s: `, which simply inserts a `:` and space
|
79 |
+
* after the field name.
|
80 |
+
*
|
81 |
+
* @param string $format A string to format the field name with - `%s` will be replaced with the field name
|
82 |
+
* @return void
|
83 |
+
*/
|
84 |
+
static public function setFieldFormat($format)
|
85 |
+
{
|
86 |
+
if (substr_count(str_replace('%%', '', $format), '%') != 1 || strpos($format, '%s') === FALSE) {
|
87 |
+
throw new fProgrammerException(
|
88 |
+
'The format, %s, has more or less than exactly one %%s sprintf() conversion specification',
|
89 |
+
$format
|
90 |
+
);
|
91 |
+
}
|
92 |
+
self::$field_format = $format;
|
93 |
+
}
|
94 |
+
|
95 |
+
|
96 |
+
/**
|
97 |
+
* Sets the message for the exception, allowing for custom formatting beyond fException
|
98 |
+
*
|
99 |
+
* If this method receives exactly two parameters, a string and an array,
|
100 |
+
* the string will be used as a message in a HTML `<p>` tag and the array
|
101 |
+
* will be turned into an unorder list `<ul>` tag with each element in the
|
102 |
+
* array being an `<li>` tag. It is possible to pass an optional exception
|
103 |
+
* code as a third parameter.
|
104 |
+
*
|
105 |
+
* The following PHP:
|
106 |
+
*
|
107 |
+
* {{{
|
108 |
+
* #!php
|
109 |
+
* throw new fValidationException(
|
110 |
+
* 'The following problems were found:',
|
111 |
+
* array(
|
112 |
+
* 'Please provide your name',
|
113 |
+
* 'Please provide your email address'
|
114 |
+
* )
|
115 |
+
* );
|
116 |
+
* }}}
|
117 |
+
*
|
118 |
+
* Would create the message:
|
119 |
+
*
|
120 |
+
* {{{
|
121 |
+
* #!text/html
|
122 |
+
* <p>The following problems were found:</p>
|
123 |
+
* <ul>
|
124 |
+
* <li>Please provide your name</li>
|
125 |
+
* <li>Please provide your email address</li>
|
126 |
+
* </ul>
|
127 |
+
* }}}
|
128 |
+
*
|
129 |
+
* If the parameters are anything else, they will be passed to
|
130 |
+
* fException::__construct().
|
131 |
+
*
|
132 |
+
* @param string $message The beginning message for the exception. This will be placed in a `<p>` tag.
|
133 |
+
* @param array $sub_messages An array of strings to place in a `<ul>` tag
|
134 |
+
* @param mixed $code The optional exception code
|
135 |
+
* @return fException
|
136 |
+
*/
|
137 |
+
public function __construct($message='')
|
138 |
+
{
|
139 |
+
$params = func_get_args();
|
140 |
+
|
141 |
+
if ((count($params) == 2 || count($params) == 3) && is_string($params[0]) && is_array($params[1])) {
|
142 |
+
|
143 |
+
|
144 |
+
$message = sprintf(
|
145 |
+
"<p>%1\$s</p>\n<ul>\n<li>%2\$s</li>\n</ul>",
|
146 |
+
self::compose($params[0]),
|
147 |
+
join("</li>\n<li>", $this->formatErrorArray($params[1]))
|
148 |
+
);
|
149 |
+
|
150 |
+
$params = array_merge(
|
151 |
+
// This escapes % signs since fException is going to look for sprintf formatting codes
|
152 |
+
array(str_replace('%', '%%', $message)),
|
153 |
+
// This grabs the exception code if one is defined
|
154 |
+
array_slice($params, 2)
|
155 |
+
);
|
156 |
+
}
|
157 |
+
|
158 |
+
call_user_func_array(
|
159 |
+
array($this, 'fException::__construct'),
|
160 |
+
$params
|
161 |
+
);
|
162 |
+
}
|
163 |
+
|
164 |
+
|
165 |
+
/**
|
166 |
+
* Takes an error array that may or may not be nested and returns a HTML string representation
|
167 |
+
*
|
168 |
+
* @param array $errors An array of (possibly nested) child record errors
|
169 |
+
* @return array An array of string error messages
|
170 |
+
*/
|
171 |
+
private function formatErrorArray($errors)
|
172 |
+
{
|
173 |
+
$new_errors = array();
|
174 |
+
foreach ($errors as $error) {
|
175 |
+
if (!is_array($error)) {
|
176 |
+
$new_errors[] = $error;
|
177 |
+
} else {
|
178 |
+
$new_errors[] = sprintf(
|
179 |
+
"<span>%1\$s</span>\n<ul>\n<li>%2\$s</li>\n</ul>",
|
180 |
+
$error['name'],
|
181 |
+
join("</li>\n<li>", $this->formatErrorArray($error['errors']))
|
182 |
+
);
|
183 |
+
}
|
184 |
+
}
|
185 |
+
return $new_errors;
|
186 |
+
}
|
187 |
+
}
|
188 |
+
|
189 |
+
|
190 |
+
|
191 |
+
/**
|
192 |
+
* Copyright (c) 2007-2010 Will Bond <will@flourishlib.com>, others
|
193 |
+
*
|
194 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
195 |
+
* of this software and associated documentation files (the "Software"), to deal
|
196 |
+
* in the Software without restriction, including without limitation the rights
|
197 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
198 |
+
* copies of the Software, and to permit persons to whom the Software is
|
199 |
+
* furnished to do so, subject to the following conditions:
|
200 |
+
*
|
201 |
+
* The above copyright notice and this permission notice shall be included in
|
202 |
+
* all copies or substantial portions of the Software.
|
203 |
+
*
|
204 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
205 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
206 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
207 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
208 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
209 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
210 |
+
* THE SOFTWARE.
|
211 |
+
*/
|
lib/pConnection.php
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
abstract class pConnection {
|
4 |
+
|
5 |
+
protected $username;
|
6 |
+
protected $host;
|
7 |
+
protected $password;
|
8 |
+
protected $port;
|
9 |
+
protected $secure;
|
10 |
+
protected $timeout;
|
11 |
+
|
12 |
+
public function __construct($type, $host, $username, $password, $port, $secure = FALSE, $timeout = NULL) {
|
13 |
+
|
14 |
+
$valid_types = array('imap', 'pop3');
|
15 |
+
if (!in_array($type, $valid_types)) {
|
16 |
+
throw new fProgrammerException('The mailbox type specified, %1$s, in invalid. Must be one of: %2$s.', $type, join(', ', $valid_types));
|
17 |
+
}
|
18 |
+
|
19 |
+
if ($secure && !extension_loaded('openssl')) {
|
20 |
+
throw new fEnvironmentException('A secure connection was requested, but the %s extension is not installed', 'openssl');
|
21 |
+
}
|
22 |
+
|
23 |
+
if ($timeout === NULL) {
|
24 |
+
$timeout = ini_get('default_socket_timeout');
|
25 |
+
}
|
26 |
+
|
27 |
+
$this->type = $type;
|
28 |
+
$this->host = $host;
|
29 |
+
$this->username = $username;
|
30 |
+
$this->password = $password;
|
31 |
+
$this->port = $port;
|
32 |
+
$this->secure = $secure;
|
33 |
+
$this->timeout = $timeout;
|
34 |
+
}
|
35 |
+
|
36 |
+
abstract function read($expect = NULL);
|
37 |
+
|
38 |
+
abstract function write($command, $expected = null);
|
39 |
+
|
40 |
+
abstract function close();
|
41 |
+
|
42 |
+
abstract function open();
|
43 |
+
|
44 |
+
abstract function isPersistant();
|
45 |
+
}
|
lib/pCurlConnection.php
ADDED
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class pCurlConnection extends pConnection {
|
4 |
+
|
5 |
+
private $buffer = array();
|
6 |
+
|
7 |
+
public function __construct($type, $host, $username, $password, $port = NULL, $secure = FALSE, $timeout = NULL) {
|
8 |
+
parent::__construct($type, $host, $username, $password, $port, $secure, $timeout);
|
9 |
+
DebugEcho("Connecting via cURL");
|
10 |
+
DebugDump(curl_version());
|
11 |
+
}
|
12 |
+
|
13 |
+
public function close() {
|
14 |
+
|
15 |
+
}
|
16 |
+
|
17 |
+
public function open() {
|
18 |
+
|
19 |
+
}
|
20 |
+
|
21 |
+
public function write($command, $expected = null) {
|
22 |
+
|
23 |
+
|
24 |
+
$suffix = '';
|
25 |
+
if ($this->type == 'imap') {
|
26 |
+
if ($command == 'LOGOUT') {
|
27 |
+
return;
|
28 |
+
}
|
29 |
+
|
30 |
+
$suffix = '/INBOX';
|
31 |
+
}
|
32 |
+
|
33 |
+
$url = ($this->type == 'imap' ? 'imap' : 'pop3') . ($this->secure ? 's' : '') . '://' . $this->host . $suffix;
|
34 |
+
|
35 |
+
DebugEcho("curl write: $url - $command");
|
36 |
+
|
37 |
+
$ch = curl_init();
|
38 |
+
curl_setopt($ch, CURLOPT_USERPWD, "$this->username:$this->password");
|
39 |
+
curl_setopt($ch, CURLOPT_URL, $url);
|
40 |
+
curl_setopt($ch, CURLOPT_PORT, $this->port);
|
41 |
+
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
42 |
+
curl_setopt($ch, CURLOPT_HEADER, false);
|
43 |
+
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
44 |
+
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
45 |
+
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->timeout);
|
46 |
+
curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
|
47 |
+
curl_setopt($ch, CURLOPT_NOBODY, true);
|
48 |
+
|
49 |
+
curl_setopt($ch, CURLOPT_VERBOSE, true);
|
50 |
+
$verbose = fopen('php://temp', 'w+');
|
51 |
+
curl_setopt($ch, CURLOPT_STDERR, $verbose);
|
52 |
+
|
53 |
+
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $command);
|
54 |
+
|
55 |
+
$res = curl_exec($ch);
|
56 |
+
|
57 |
+
rewind($verbose);
|
58 |
+
$verboseLog = stream_get_contents($verbose);
|
59 |
+
//DebugEcho($verboseLog);
|
60 |
+
fclose($verbose);
|
61 |
+
|
62 |
+
if (false === $res) {
|
63 |
+
DebugEcho("Curl: error - " . curl_error($ch) . ' (' . curl_errno($ch) . ')');
|
64 |
+
throw new fConnectivityException('Unable to write data to %1$s server %2$s on port %3$s', strtoupper($this->type), $this->host, $this->port);
|
65 |
+
} else {
|
66 |
+
$res = explode("\r\n", $verboseLog);
|
67 |
+
|
68 |
+
$res = array_filter($res, function($l) {
|
69 |
+
return strlen($l) > 1 && substr($l, 0, 2) == '< ';
|
70 |
+
});
|
71 |
+
|
72 |
+
array_walk($res, function(&$l) {
|
73 |
+
$l = substr($l, 2);
|
74 |
+
});
|
75 |
+
//DebugDump($res);
|
76 |
+
|
77 |
+
$this->buffer = array_merge($this->buffer, $res);
|
78 |
+
}
|
79 |
+
curl_close($ch);
|
80 |
+
|
81 |
+
//DebugDump($this->buffer);
|
82 |
+
|
83 |
+
return $this->read($expected);
|
84 |
+
}
|
85 |
+
|
86 |
+
public function read($expect = NULL) {
|
87 |
+
$response = array();
|
88 |
+
while (count($this->buffer) > 0) {
|
89 |
+
$line = array_shift($this->buffer);
|
90 |
+
|
91 |
+
$response[] = $line;
|
92 |
+
//DebugEcho("curl read: $line");
|
93 |
+
// Automatically stop at the termination octet or a bad response
|
94 |
+
if ($this->type == 'pop3' && (count($response) == 1 && $response[0][0] == '-')) {
|
95 |
+
break;
|
96 |
+
}
|
97 |
+
|
98 |
+
if ($expect !== NULL) {
|
99 |
+
$matched_number = is_int($expect) && sizeof($response) == $expect;
|
100 |
+
$matched_regex = is_string($expect) && preg_match($expect, $line);
|
101 |
+
if ($matched_number || $matched_regex) {
|
102 |
+
DebugEcho("curl read: matched expect");
|
103 |
+
break;
|
104 |
+
}
|
105 |
+
}
|
106 |
+
}
|
107 |
+
|
108 |
+
if ($this->type == 'pop3') {
|
109 |
+
// Remove the termination octet
|
110 |
+
if ($response && $response[sizeof($response) - 1] == '.') {
|
111 |
+
$response = array_slice($response, 0, -1);
|
112 |
+
}
|
113 |
+
// Remove byte-stuffing
|
114 |
+
$lines = count($response);
|
115 |
+
for ($i = 0; $i < $lines; $i++) {
|
116 |
+
if (strlen($response[$i]) && $response[$i][0] == '.') {
|
117 |
+
$response[$i] = substr($response[$i], 1);
|
118 |
+
}
|
119 |
+
}
|
120 |
+
}
|
121 |
+
|
122 |
+
DebugEcho("curl read (" . count($response) . ') :' . array_pop((array_slice($response, -1))));
|
123 |
+
return $response;
|
124 |
+
}
|
125 |
+
|
126 |
+
public function isPersistant() {
|
127 |
+
return false;
|
128 |
+
}
|
129 |
+
|
130 |
+
}
|
lib/pImapMailServer.php
ADDED
@@ -0,0 +1,189 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class pImapMailServer extends pMailServer {
|
4 |
+
|
5 |
+
private $connection = null;
|
6 |
+
|
7 |
+
public function __construct($connection) {
|
8 |
+
$this->connection = $connection;
|
9 |
+
$this->connection->open();
|
10 |
+
}
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Takes a response from an IMAP command and parses it into a
|
14 |
+
* multi-dimensional array
|
15 |
+
*
|
16 |
+
* @param string $text The IMAP command response
|
17 |
+
* @param boolean $top_level If we are parsing the top level
|
18 |
+
* @return array The parsed representation of the response text
|
19 |
+
*/
|
20 |
+
private function parseResponse($text, $top_level = FALSE) {
|
21 |
+
$regex = '[\\\\\w.\[\]]+|"([^"\\\\]+|\\\\"|\\\\\\\\)*"|\((?:(?1)[ \t]*)*\)';
|
22 |
+
|
23 |
+
if (preg_match('#\{(\d+)\}#', $text, $match)) {
|
24 |
+
$regex = '\{' . $match[1] . '\}\r\n.{' . ($match[1]) . '}|' . $regex;
|
25 |
+
}
|
26 |
+
|
27 |
+
preg_match_all('#(' . $regex . ')#s', $text, $matches, PREG_SET_ORDER);
|
28 |
+
$output = array();
|
29 |
+
foreach ($matches as $match) {
|
30 |
+
if (substr($match[0], 0, 1) == '"') {
|
31 |
+
$output[] = str_replace('\\"', '"', substr($match[0], 1, -1));
|
32 |
+
} elseif (substr($match[0], 0, 1) == '(') {
|
33 |
+
$output[] = self::parseResponse(substr($match[0], 1, -1));
|
34 |
+
} elseif (substr($match[0], 0, 1) == '{') {
|
35 |
+
$output[] = preg_replace('#^[^\r]+\r\n#', '', $match[0]);
|
36 |
+
} else {
|
37 |
+
$output[] = $match[0];
|
38 |
+
}
|
39 |
+
}
|
40 |
+
|
41 |
+
if ($top_level) {
|
42 |
+
$new_output = array();
|
43 |
+
$total_size = count($output);
|
44 |
+
for ($i = 0; $i < $total_size; $i = $i + 2) {
|
45 |
+
$new_output[strtolower($output[$i])] = $output[$i + 1];
|
46 |
+
}
|
47 |
+
$output = $new_output;
|
48 |
+
}
|
49 |
+
|
50 |
+
return $output;
|
51 |
+
}
|
52 |
+
|
53 |
+
function deleteMessages($uid) {
|
54 |
+
settype($uid, 'array');
|
55 |
+
$this->connection->write('UID STORE ' . join(',', $uid) . ' +FLAGS (\\Deleted)');
|
56 |
+
$this->connection->write('EXPUNGE');
|
57 |
+
}
|
58 |
+
|
59 |
+
function countMessages() {
|
60 |
+
$total_messages = 0;
|
61 |
+
|
62 |
+
$response = $this->connection->write('STATUS "INBOX" (MESSAGES)');
|
63 |
+
foreach ($response as $line) {
|
64 |
+
if (preg_match('#^\s*\*\s+STATUS\s+"?INBOX"?\s+\((.*)\)\s*$#', $line, $match)) {
|
65 |
+
$details = $this->parseResponse($match[1], TRUE);
|
66 |
+
$total_messages = $details['messages'];
|
67 |
+
}
|
68 |
+
}
|
69 |
+
|
70 |
+
return (int) $total_messages;
|
71 |
+
}
|
72 |
+
|
73 |
+
function fetchMessageSource($uid) {
|
74 |
+
|
75 |
+
$response = $this->connection->write('UID FETCH ' . $uid . ' (BODY[])');
|
76 |
+
|
77 |
+
$j = 0;
|
78 |
+
foreach ($response as $line) {
|
79 |
+
//DebugEcho("fetchMessageSource: [$j] $line");
|
80 |
+
if (1 === preg_match('#\{(\d+)\}$#', $response[$j], $match)) {
|
81 |
+
//DebugEcho("fetchMessageSource: found " . $match[1]);
|
82 |
+
break;
|
83 |
+
}
|
84 |
+
$j++;
|
85 |
+
}
|
86 |
+
//preg_match('#\{(\d+)\}$#', $response[0], $match);
|
87 |
+
//DebugEcho("fetchMessageSource: reading {$match[1]} charaters");
|
88 |
+
|
89 |
+
$message = '';
|
90 |
+
foreach ($response as $i => $line) {
|
91 |
+
if ($i <= $j) {
|
92 |
+
continue;
|
93 |
+
}
|
94 |
+
if (trim($line) == ')') {
|
95 |
+
break;
|
96 |
+
}
|
97 |
+
if (strlen($message) + strlen($line) + 2 > $match[1]) {
|
98 |
+
//DebugEcho("fetchMessageSource: last ($i) $line");
|
99 |
+
$message .= substr($line . "\r\n", 0, $match[1] - strlen($message));
|
100 |
+
break;
|
101 |
+
} else {
|
102 |
+
$message .= $line . "\r\n";
|
103 |
+
//DebugEcho("fetchMessageSource: ($i-" . strlen($message) . ") $line");
|
104 |
+
}
|
105 |
+
}
|
106 |
+
|
107 |
+
// Removes the extra trailing \r\n added above to the last line
|
108 |
+
return substr($message, 0, -2);
|
109 |
+
}
|
110 |
+
|
111 |
+
function listMessages($limit = NULL, $page = NULL) {
|
112 |
+
|
113 |
+
if (!$limit) {
|
114 |
+
$start = 1;
|
115 |
+
$end = '*';
|
116 |
+
} else {
|
117 |
+
if (!$page) {
|
118 |
+
$page = 1;
|
119 |
+
}
|
120 |
+
$start = ($limit * ($page - 1)) + 1;
|
121 |
+
$end = $start + $limit - 1;
|
122 |
+
}
|
123 |
+
|
124 |
+
$total_messages = 0;
|
125 |
+
$response = $this->connection->write('STATUS "INBOX" (MESSAGES)');
|
126 |
+
foreach ($response as $line) {
|
127 |
+
if (preg_match('#^\s*\*\s+STATUS\s+"?INBOX"?\s+\((.*)\)\s*$#', $line, $match)) {
|
128 |
+
$details = self::parseResponse($match[1], TRUE);
|
129 |
+
$total_messages = $details['messages'];
|
130 |
+
}
|
131 |
+
}
|
132 |
+
|
133 |
+
if ($start > $total_messages) {
|
134 |
+
return array();
|
135 |
+
}
|
136 |
+
|
137 |
+
if ($end > $total_messages) {
|
138 |
+
$end = $total_messages;
|
139 |
+
}
|
140 |
+
|
141 |
+
$output = array();
|
142 |
+
$response = $this->connection->write('FETCH ' . $start . ':' . $end . ' (UID INTERNALDATE RFC822.SIZE ENVELOPE)');
|
143 |
+
foreach ($response as $line) {
|
144 |
+
if (preg_match('#^\s*\*\s+(\d+)\s+FETCH\s+\((.*)\)\s*$#', $line, $match)) {
|
145 |
+
$details = self::parseResponse($match[2], TRUE);
|
146 |
+
$info = array();
|
147 |
+
|
148 |
+
$info['uid'] = $details['uid'];
|
149 |
+
$info['received'] = $this->cleanDate($details['internaldate']);
|
150 |
+
$info['size'] = $details['rfc822.size'];
|
151 |
+
|
152 |
+
$envelope = $details['envelope'];
|
153 |
+
$info['date'] = $envelope[0] != 'NIL' ? $envelope[0] : '';
|
154 |
+
$info['from'] = $this->joinEmails($envelope[2]);
|
155 |
+
if (preg_match('#=\?[^\?]+\?[QB]\?[^\?]+\?=#', $envelope[1])) {
|
156 |
+
do {
|
157 |
+
$last_subject = $envelope[1];
|
158 |
+
$envelope[1] = preg_replace('#(=\?([^\?]+)\?[QB]\?[^\?]+\?=) (\s*=\?\2)#', '\1\3', $envelope[1]);
|
159 |
+
} while ($envelope[1] != $last_subject);
|
160 |
+
$info['subject'] = $this->decodeHeader($envelope[1]);
|
161 |
+
} else {
|
162 |
+
$info['subject'] = $envelope[1] == 'NIL' ? '' : $this->decodeHeader($envelope[1]);
|
163 |
+
}
|
164 |
+
if ($envelope[9] != 'NIL') {
|
165 |
+
$info['message_id'] = $envelope[9];
|
166 |
+
}
|
167 |
+
if ($envelope[5] != 'NIL') {
|
168 |
+
$info['to'] = $this->joinEmails($envelope[5]);
|
169 |
+
}
|
170 |
+
if ($envelope[8] != 'NIL') {
|
171 |
+
$info['in_reply_to'] = $envelope[8];
|
172 |
+
}
|
173 |
+
|
174 |
+
$output[$info['uid']] = $info;
|
175 |
+
}
|
176 |
+
}
|
177 |
+
return $output;
|
178 |
+
}
|
179 |
+
|
180 |
+
public function close() {
|
181 |
+
|
182 |
+
if ($this->connection->isPersistant()) {
|
183 |
+
$this->connection->write('LOGOUT');
|
184 |
+
}
|
185 |
+
|
186 |
+
$this->connection->close();
|
187 |
+
}
|
188 |
+
|
189 |
+
}
|
lib/pMailServer.php
ADDED
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
abstract class pMailServer {
|
4 |
+
|
5 |
+
abstract function deleteMessages($uids);
|
6 |
+
|
7 |
+
abstract function countMessages();
|
8 |
+
|
9 |
+
abstract function close();
|
10 |
+
|
11 |
+
abstract function fetchMessageSource($uid);
|
12 |
+
|
13 |
+
abstract function listMessages($limit = NULL, $page = NULL);
|
14 |
+
|
15 |
+
protected function cleanDate($date) {
|
16 |
+
$date = preg_replace('#\([^)]+\)#', ' ', trim($date));
|
17 |
+
$date = preg_replace('#\s+#', ' ', $date);
|
18 |
+
$date = preg_replace('#(\d+)-([a-z]+)-(\d{4})#i', '\1 \2 \3', $date);
|
19 |
+
$date = preg_replace('#^[a-z]+\s*,\s*#i', '', trim($date));
|
20 |
+
return trim($date);
|
21 |
+
}
|
22 |
+
|
23 |
+
/**
|
24 |
+
* Joins parsed emails into a comma-delimited string
|
25 |
+
*
|
26 |
+
* @param array $emails An array of emails split into personal, mailbox and host parts
|
27 |
+
* @return string An comma-delimited list of emails
|
28 |
+
*/
|
29 |
+
protected function joinEmails($emails) {
|
30 |
+
$output = '';
|
31 |
+
foreach ($emails as $email) {
|
32 |
+
if ($output) {
|
33 |
+
$output .= ', ';
|
34 |
+
}
|
35 |
+
|
36 |
+
if (!isset($email[0])) {
|
37 |
+
$email[0] = !empty($email['personal']) ? $email['personal'] : '';
|
38 |
+
$email[2] = $email['mailbox'];
|
39 |
+
$email[3] = !empty($email['host']) ? $email['host'] : '';
|
40 |
+
}
|
41 |
+
|
42 |
+
$address = $email[2];
|
43 |
+
if (!empty($email[3])) {
|
44 |
+
$address .= '@' . $email[3];
|
45 |
+
}
|
46 |
+
$output .= fEmail::combineNameEmail($email[0], $address);
|
47 |
+
}
|
48 |
+
return $output;
|
49 |
+
}
|
50 |
+
|
51 |
+
protected function decodeHeader($text) {
|
52 |
+
$parts = preg_split('#(=\?[^\?]+\?[QB]\?[^\?]+\?=)#i', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
|
53 |
+
|
54 |
+
$part_with_encoding = array();
|
55 |
+
$output = '';
|
56 |
+
foreach ($parts as $part) {
|
57 |
+
if ($part === '') {
|
58 |
+
continue;
|
59 |
+
}
|
60 |
+
|
61 |
+
if (preg_match_all('#=\?([^\?]+)\?([QB])\?([^\?]+)\?=#i', $part, $matches, PREG_SET_ORDER)) {
|
62 |
+
foreach ($matches as $match) {
|
63 |
+
if (strtoupper($match[2]) == 'Q') {
|
64 |
+
$part_string = rawurldecode(strtr(
|
65 |
+
$match[3], array(
|
66 |
+
'=' => '%',
|
67 |
+
'_' => ' '
|
68 |
+
)
|
69 |
+
));
|
70 |
+
} else {
|
71 |
+
$part_string = base64_decode($match[3]);
|
72 |
+
}
|
73 |
+
$lower_encoding = strtolower($match[1]);
|
74 |
+
$last_key = count($part_with_encoding) - 1;
|
75 |
+
if (isset($part_with_encoding[$last_key]) && $part_with_encoding[$last_key]['encoding'] == $lower_encoding) {
|
76 |
+
$part_with_encoding[$last_key]['string'] .= $part_string;
|
77 |
+
} else {
|
78 |
+
$part_with_encoding[] = array('encoding' => $lower_encoding, 'string' => $part_string);
|
79 |
+
}
|
80 |
+
}
|
81 |
+
} else {
|
82 |
+
$last_key = count($part_with_encoding) - 1;
|
83 |
+
if (isset($part_with_encoding[$last_key]) && $part_with_encoding[$last_key]['encoding'] == 'iso-8859-1') {
|
84 |
+
$part_with_encoding[$last_key]['string'] .= $part;
|
85 |
+
} else {
|
86 |
+
$part_with_encoding[] = array('encoding' => 'iso-8859-1', 'string' => $part);
|
87 |
+
}
|
88 |
+
}
|
89 |
+
}
|
90 |
+
|
91 |
+
foreach ($part_with_encoding as $part) {
|
92 |
+
$output .= $this->iconv($part['encoding'], 'UTF-8', $part['string']);
|
93 |
+
}
|
94 |
+
|
95 |
+
return $output;
|
96 |
+
}
|
97 |
+
|
98 |
+
/**
|
99 |
+
* This works around a bug in MAMP 1.9.4+ and PHP 5.3 where iconv()
|
100 |
+
* does not seem to properly assign the return value to a variable, but
|
101 |
+
* does work when returning the value.
|
102 |
+
*
|
103 |
+
* @param string $in_charset The incoming character encoding
|
104 |
+
* @param string $out_charset The outgoing character encoding
|
105 |
+
* @param string $string The string to convert
|
106 |
+
* @return string The converted string
|
107 |
+
*/
|
108 |
+
protected function iconv($in_charset, $out_charset, $string) {
|
109 |
+
return iconv($in_charset, "$out_charset//IGNORE", $string);
|
110 |
+
}
|
111 |
+
|
112 |
+
}
|
lib/pPop3MailServer.php
ADDED
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class pPop3MailServer extends pMailServer {
|
4 |
+
|
5 |
+
private $connection = null;
|
6 |
+
|
7 |
+
public function __construct($connection) {
|
8 |
+
$this->connection = $connection;
|
9 |
+
$this->connection->open();
|
10 |
+
}
|
11 |
+
|
12 |
+
public function countMessages() {
|
13 |
+
$total_messages = 0;
|
14 |
+
|
15 |
+
$response = $this->connection->write('STAT', '#^\+OK\s+(\d+)\s+#');
|
16 |
+
DebugEcho("pop count: $response");
|
17 |
+
preg_match('#^\+OK\s+(\d+)\s+#', $response[count($response) - 1], $match);
|
18 |
+
$total_messages = $match[1];
|
19 |
+
|
20 |
+
return (int) $total_messages;
|
21 |
+
}
|
22 |
+
|
23 |
+
public function deleteMessages($uid) {
|
24 |
+
settype($uid, 'array');
|
25 |
+
foreach ($uid as $id) {
|
26 |
+
$this->connection->write('DELE ' . $id, 1);
|
27 |
+
}
|
28 |
+
}
|
29 |
+
|
30 |
+
public function fetchMessageSource($uid) {
|
31 |
+
$response = $this->connection->write('RETR ' . $uid);
|
32 |
+
array_shift($response);
|
33 |
+
$response = join("\r\n", $response);
|
34 |
+
|
35 |
+
return $response;
|
36 |
+
}
|
37 |
+
|
38 |
+
public function listMessages($limit = NULL, $page = NULL) {
|
39 |
+
|
40 |
+
if (!$limit) {
|
41 |
+
$start = 1;
|
42 |
+
$end = NULL;
|
43 |
+
} else {
|
44 |
+
if (!$page) {
|
45 |
+
$page = 1;
|
46 |
+
}
|
47 |
+
$start = ($limit * ($page - 1)) + 1;
|
48 |
+
$end = $start + $limit - 1;
|
49 |
+
}
|
50 |
+
|
51 |
+
$total_messages = $this->countMessages();
|
52 |
+
|
53 |
+
if ($start > $total_messages) {
|
54 |
+
return array();
|
55 |
+
}
|
56 |
+
|
57 |
+
if ($end === NULL || $end > $total_messages) {
|
58 |
+
$end = $total_messages;
|
59 |
+
}
|
60 |
+
|
61 |
+
$sizes = array();
|
62 |
+
$response = $this->connection->write('LIST');
|
63 |
+
array_shift($response);
|
64 |
+
foreach ($response as $line) {
|
65 |
+
preg_match('#^(\d+)\s+(\d+)$#', $line, $match);
|
66 |
+
$sizes[$match[1]] = $match[2];
|
67 |
+
}
|
68 |
+
|
69 |
+
$output = array();
|
70 |
+
for ($i = $start; $i <= $end; $i++) {
|
71 |
+
$response = $this->connection->write('TOP ' . $i . ' 1');
|
72 |
+
array_shift($response);
|
73 |
+
$value = array_pop($response);
|
74 |
+
// Some servers add an extra blank line after the 1 requested line
|
75 |
+
if (trim($value) == '') {
|
76 |
+
array_pop($response);
|
77 |
+
}
|
78 |
+
|
79 |
+
$response = trim(join("\r\n", $response));
|
80 |
+
$headers = fMailbox::parseHeaders($response);
|
81 |
+
$output[$i] = array(
|
82 |
+
'uid' => $i,
|
83 |
+
'received' => self::cleanDate(preg_replace('#^.*;\s*([^;]+)$#', '\1', $headers['received'][0])),
|
84 |
+
'size' => $sizes[$i],
|
85 |
+
'date' => $headers['date'],
|
86 |
+
'from' => self::joinEmails(array($headers['from'])),
|
87 |
+
'subject' => isset($headers['subject']) ? $headers['subject'] : ''
|
88 |
+
);
|
89 |
+
if (isset($headers['message-id'])) {
|
90 |
+
$output[$i]['message_id'] = $headers['message-id'];
|
91 |
+
}
|
92 |
+
if (isset($headers['to'])) {
|
93 |
+
$output[$i]['to'] = self::joinEmails($headers['to']);
|
94 |
+
}
|
95 |
+
if (isset($headers['in-reply-to'])) {
|
96 |
+
$output[$i]['in_reply_to'] = $headers['in-reply-to'];
|
97 |
+
}
|
98 |
+
}
|
99 |
+
return $output;
|
100 |
+
}
|
101 |
+
|
102 |
+
public function close() {
|
103 |
+
if ($this->connection->isPersistant()) {
|
104 |
+
$this->connection->write('QUIT', 1);
|
105 |
+
}
|
106 |
+
$this->connection->close();
|
107 |
+
}
|
108 |
+
|
109 |
+
}
|
lib/pSocketConnection.php
ADDED
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class pSocketConnection extends pConnection {
|
4 |
+
|
5 |
+
private $socket = null;
|
6 |
+
private $command_num = 1;
|
7 |
+
|
8 |
+
public function __construct($type, $host, $username, $password, $port = NULL, $secure = FALSE, $timeout = NULL) {
|
9 |
+
parent::__construct($type, $host, $username, $password, $port, $secure, $timeout);
|
10 |
+
DebugEcho("Connecting via Socket");
|
11 |
+
}
|
12 |
+
|
13 |
+
public function __destruct() {
|
14 |
+
$this->close();
|
15 |
+
}
|
16 |
+
|
17 |
+
public function close() {
|
18 |
+
if ($this->socket) {
|
19 |
+
fclose($this->socket);
|
20 |
+
$this->socket = null;
|
21 |
+
}
|
22 |
+
}
|
23 |
+
|
24 |
+
public function open() {
|
25 |
+
if ($this->socket) {
|
26 |
+
return;
|
27 |
+
}
|
28 |
+
|
29 |
+
$connstr = $this->secure ? 'tls://' . $this->host : $this->host;
|
30 |
+
DebugEcho("Socket: $connstr:$this->port");
|
31 |
+
$this->socket = fsockopen($connstr, $this->port, $error_number, $error_string, $this->timeout);
|
32 |
+
DebugEcho("Socket error: $error_number - $error_string");
|
33 |
+
|
34 |
+
if (!$this->socket) {
|
35 |
+
throw new fConnectivityException('There was an error connecting to the server ' . $error_string);
|
36 |
+
}
|
37 |
+
|
38 |
+
stream_set_timeout($this->socket, $this->timeout);
|
39 |
+
|
40 |
+
if ($this->type == 'imap') {
|
41 |
+
if (!$this->secure && extension_loaded('openssl')) {
|
42 |
+
$response = $this->write('CAPABILITY');
|
43 |
+
if (preg_match('#\bstarttls\b#i', $response[0])) {
|
44 |
+
$this->write('STARTTLS');
|
45 |
+
do {
|
46 |
+
if (isset($res)) {
|
47 |
+
sleep(0.1);
|
48 |
+
}
|
49 |
+
$res = stream_socket_enable_crypto($this->socket, TRUE, STREAM_CRYPTO_METHOD_TLS_CLIENT);
|
50 |
+
} while ($res === 0);
|
51 |
+
}
|
52 |
+
}
|
53 |
+
$response = $this->write('LOGIN ' . $this->username . ' ' . $this->password);
|
54 |
+
if (!$response || !preg_match('#^[^ ]+\s+OK#', $response[count($response) - 1])) {
|
55 |
+
throw new fValidationException('The username and password provided were not accepted for the %1$s server %2$s on port %3$s', strtoupper($this->type), $this->host, $this->port);
|
56 |
+
}
|
57 |
+
$this->write('SELECT "INBOX"');
|
58 |
+
} elseif ($this->type == 'pop3') {
|
59 |
+
$response = $this->read(1);
|
60 |
+
if (isset($response[0])) {
|
61 |
+
if ($response[0][0] == '-') {
|
62 |
+
throw new fConnectivityException(
|
63 |
+
'There was an error connecting to the POP3 server %1$s on port %2$s', $this->host, $this->port
|
64 |
+
);
|
65 |
+
}
|
66 |
+
preg_match('#<[^@]+@[^>]+>#', $response[0], $match);
|
67 |
+
}
|
68 |
+
|
69 |
+
if (!$this->secure && extension_loaded('openssl')) {
|
70 |
+
$response = $this->write('STLS', 1);
|
71 |
+
if ($response[0][0] == '+') {
|
72 |
+
do {
|
73 |
+
if (isset($res)) {
|
74 |
+
sleep(0.1);
|
75 |
+
}
|
76 |
+
$res = stream_socket_enable_crypto($this->socket, TRUE, STREAM_CRYPTO_METHOD_TLS_CLIENT);
|
77 |
+
} while ($res === 0);
|
78 |
+
if ($res === FALSE) {
|
79 |
+
throw new fConnectivityException('Error establishing secure connection');
|
80 |
+
}
|
81 |
+
}
|
82 |
+
}
|
83 |
+
|
84 |
+
$authenticated = FALSE;
|
85 |
+
if (isset($match[0])) {
|
86 |
+
$response = $this->write('APOP ' . $this->username . ' ' . md5($match[0] . $this->password), 1);
|
87 |
+
if (isset($response[0]) && $response[0][0] == '+') {
|
88 |
+
$authenticated = TRUE;
|
89 |
+
}
|
90 |
+
}
|
91 |
+
|
92 |
+
if (!$authenticated) {
|
93 |
+
$response = $this->write('USER ' . $this->username, 1);
|
94 |
+
if ($response[0][0] == '+') {
|
95 |
+
$response = $this->write('PASS ' . $this->password, 1);
|
96 |
+
if (isset($response[0][0]) && $response[0][0] == '+') {
|
97 |
+
$authenticated = TRUE;
|
98 |
+
}
|
99 |
+
}
|
100 |
+
}
|
101 |
+
|
102 |
+
if (!$authenticated) {
|
103 |
+
throw new fValidationException('The username and password provided were not accepted for the %1$s server %2$s on port %3$s', strtoupper($this->type), $this->host, $this->port);
|
104 |
+
}
|
105 |
+
}
|
106 |
+
}
|
107 |
+
|
108 |
+
public function write($command, $expected = null) {
|
109 |
+
|
110 |
+
if ($this->type == 'imap') {
|
111 |
+
$identifier = 'A' . str_pad($this->command_num++, 4, '0', STR_PAD_LEFT);
|
112 |
+
$command = $identifier . ' ' . $command;
|
113 |
+
}
|
114 |
+
|
115 |
+
if (substr($command, -2) != "\r\n") {
|
116 |
+
$command .= "\r\n";
|
117 |
+
}
|
118 |
+
|
119 |
+
DebugEcho("socket write: $command");
|
120 |
+
|
121 |
+
$res = fwrite($this->socket, $command);
|
122 |
+
|
123 |
+
if ($res === FALSE || $res === 0) {
|
124 |
+
DebugEcho("Socket: error");
|
125 |
+
DebugDump(error_get_last());
|
126 |
+
throw new fConnectivityException('Unable to write data to %1$s server %2$s on port %3$s', strtoupper($this->type), $this->host, $this->port);
|
127 |
+
}
|
128 |
+
|
129 |
+
if ($this->type == 'imap') {
|
130 |
+
return $this->read('#^' . $identifier . '#');
|
131 |
+
} elseif ($this->type == 'pop3') {
|
132 |
+
return $this->read($expected);
|
133 |
+
}
|
134 |
+
}
|
135 |
+
|
136 |
+
public function read($expect = NULL) {
|
137 |
+
|
138 |
+
$read = array($this->socket);
|
139 |
+
$write = NULL;
|
140 |
+
$except = NULL;
|
141 |
+
$response = array();
|
142 |
+
|
143 |
+
// PHP 5.2.0 to 5.2.5 has a bug on amd64 linux where stream_select()
|
144 |
+
// fails, so we have to fake it - http://bugs.php.net/bug.php?id=42682
|
145 |
+
static $broken_select = NULL;
|
146 |
+
if ($broken_select === NULL) {
|
147 |
+
$broken_select = strpos(php_uname('m'), '64') !== FALSE && fCore::checkVersion('5.2.0') && !fCore::checkVersion('5.2.6');
|
148 |
+
}
|
149 |
+
|
150 |
+
// Fixes an issue with stream_select throwing a warning on PHP 5.3 on Windows
|
151 |
+
if (fCore::checkOS('windows') && fCore::checkVersion('5.3.0')) {
|
152 |
+
$select = @stream_select($read, $write, $except, $this->timeout);
|
153 |
+
} elseif ($broken_select) {
|
154 |
+
$broken_select_buffer = NULL;
|
155 |
+
$start_time = microtime(TRUE);
|
156 |
+
$i = 0;
|
157 |
+
do {
|
158 |
+
if ($i) {
|
159 |
+
usleep(50000);
|
160 |
+
}
|
161 |
+
$char = fgetc($this->socket);
|
162 |
+
if ($char != "\x00" && $char !== FALSE) {
|
163 |
+
$broken_select_buffer = $char;
|
164 |
+
}
|
165 |
+
$i++;
|
166 |
+
} while ($broken_select_buffer === NULL && microtime(TRUE) - $start_time < $this->timeout);
|
167 |
+
$select = $broken_select_buffer !== NULL;
|
168 |
+
} else {
|
169 |
+
$select = stream_select($read, $write, $except, $this->timeout);
|
170 |
+
}
|
171 |
+
|
172 |
+
if ($select) {
|
173 |
+
while (!feof($this->socket)) {
|
174 |
+
$line = fgets($this->socket);
|
175 |
+
if ($line === FALSE) {
|
176 |
+
break;
|
177 |
+
}
|
178 |
+
$line = substr($line, 0, -2);
|
179 |
+
|
180 |
+
// When we fake select, we have to handle what we've retrieved
|
181 |
+
if ($broken_select && $broken_select_buffer !== NULL) {
|
182 |
+
$line = $broken_select_buffer . $line;
|
183 |
+
$broken_select_buffer = NULL;
|
184 |
+
}
|
185 |
+
|
186 |
+
$response[] = $line;
|
187 |
+
|
188 |
+
// Automatically stop at the termination octet or a bad response
|
189 |
+
if ($this->type == 'pop3' && ($line == '.' || (count($response) == 1 && $response[0][0] == '-'))) {
|
190 |
+
break;
|
191 |
+
}
|
192 |
+
|
193 |
+
if ($expect !== NULL) {
|
194 |
+
$matched_number = is_int($expect) && sizeof($response) == $expect;
|
195 |
+
$matched_regex = is_string($expect) && preg_match($expect, $line);
|
196 |
+
if ($matched_number || $matched_regex) {
|
197 |
+
break;
|
198 |
+
}
|
199 |
+
}
|
200 |
+
}
|
201 |
+
}
|
202 |
+
// if (fCore::getDebug($this->debug)) {
|
203 |
+
// fCore::debug("Received:\n" . join("\r\n", $response), $this->debug);
|
204 |
+
// }
|
205 |
+
DebugEcho("socket read (" . count($response) . ') :' . $response[0]);
|
206 |
+
//DebugDump($response);
|
207 |
+
|
208 |
+
if ($this->type == 'pop3') {
|
209 |
+
// Remove the termination octet
|
210 |
+
if ($response && $response[sizeof($response) - 1] == '.') {
|
211 |
+
$response = array_slice($response, 0, -1);
|
212 |
+
}
|
213 |
+
// Remove byte-stuffing
|
214 |
+
$lines = count($response);
|
215 |
+
for ($i = 0; $i < $lines; $i++) {
|
216 |
+
if (strlen($response[$i]) && $response[$i][0] == '.') {
|
217 |
+
$response[$i] = substr($response[$i], 1);
|
218 |
+
}
|
219 |
+
}
|
220 |
+
}
|
221 |
+
|
222 |
+
return $response;
|
223 |
+
}
|
224 |
+
|
225 |
+
public function isPersistant() {
|
226 |
+
return true;
|
227 |
+
}
|
228 |
+
|
229 |
+
}
|
mimedecode.php
DELETED
@@ -1,985 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
// NOTE this class has been modified from the original at http://pear.php.net/package/Mail_mimeDecode.
|
4 |
-
|
5 |
-
/**
|
6 |
-
* The Mail_mimeDecode class is used to decode mail/mime messages
|
7 |
-
*
|
8 |
-
* This class will parse a raw mime email and return
|
9 |
-
* the structure. Returned structure is similar to
|
10 |
-
* that returned by imap_fetchstructure().
|
11 |
-
*
|
12 |
-
* +----------------------------- IMPORTANT ------------------------------+
|
13 |
-
* | Usage of this class compared to native php extensions such as |
|
14 |
-
* | mailparse or imap, is slow and may be feature deficient. If available|
|
15 |
-
* | you are STRONGLY recommended to use the php extensions. |
|
16 |
-
* +----------------------------------------------------------------------+
|
17 |
-
*
|
18 |
-
* Compatible with PHP versions 4 and 5
|
19 |
-
*
|
20 |
-
* LICENSE: This LICENSE is in the BSD license style.
|
21 |
-
* Copyright (c) 2002-2003, Richard Heyes <richard@phpguru.org>
|
22 |
-
* Copyright (c) 2003-2006, PEAR <pear-group@php.net>
|
23 |
-
* All rights reserved.
|
24 |
-
*
|
25 |
-
* Redistribution and use in source and binary forms, with or
|
26 |
-
* without modification, are permitted provided that the following
|
27 |
-
* conditions are met:
|
28 |
-
*
|
29 |
-
* - Redistributions of source code must retain the above copyright
|
30 |
-
* notice, this list of conditions and the following disclaimer.
|
31 |
-
* - Redistributions in binary form must reproduce the above copyright
|
32 |
-
* notice, this list of conditions and the following disclaimer in the
|
33 |
-
* documentation and/or other materials provided with the distribution.
|
34 |
-
* - Neither the name of the authors, nor the names of its contributors
|
35 |
-
* may be used to endorse or promote products derived from this
|
36 |
-
* software without specific prior written permission.
|
37 |
-
*
|
38 |
-
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
39 |
-
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
40 |
-
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
41 |
-
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
42 |
-
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
43 |
-
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
44 |
-
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
45 |
-
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
46 |
-
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
47 |
-
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
48 |
-
* THE POSSIBILITY OF SUCH DAMAGE.
|
49 |
-
*
|
50 |
-
* @category Mail
|
51 |
-
* @package Mail_Mime
|
52 |
-
* @author Richard Heyes <richard@phpguru.org>
|
53 |
-
* @author George Schlossnagle <george@omniti.com>
|
54 |
-
* @author Cipriano Groenendal <cipri@php.net>
|
55 |
-
* @author Sean Coates <sean@php.net>
|
56 |
-
* @copyright 2003-2006 PEAR <pear-group@php.net>
|
57 |
-
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
|
58 |
-
* @version CVS: $Id: mimeDecode.php 305875 2010-12-01 07:17:10Z alan_k $
|
59 |
-
* @link http://pear.php.net/package/Mail_mime
|
60 |
-
*/
|
61 |
-
|
62 |
-
/**
|
63 |
-
* The Mail_mimeDecode class is used to decode mail/mime messages
|
64 |
-
*
|
65 |
-
* This class will parse a raw mime email and return the structure.
|
66 |
-
* Returned structure is similar to that returned by imap_fetchstructure().
|
67 |
-
*
|
68 |
-
* +----------------------------- IMPORTANT ------------------------------+
|
69 |
-
* | Usage of this class compared to native php extensions such as |
|
70 |
-
* | mailparse or imap, is slow and may be feature deficient. If available|
|
71 |
-
* | you are STRONGLY recommended to use the php extensions. |
|
72 |
-
* +----------------------------------------------------------------------+
|
73 |
-
*
|
74 |
-
* @category Mail
|
75 |
-
* @package Mail_Mime
|
76 |
-
* @author Richard Heyes <richard@phpguru.org>
|
77 |
-
* @author George Schlossnagle <george@omniti.com>
|
78 |
-
* @author Cipriano Groenendal <cipri@php.net>
|
79 |
-
* @author Sean Coates <sean@php.net>
|
80 |
-
* @copyright 2003-2006 PEAR <pear-group@php.net>
|
81 |
-
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
|
82 |
-
* @version Release: @package_version@
|
83 |
-
* @link http://pear.php.net/package/Mail_mimeDecode
|
84 |
-
*/
|
85 |
-
class Mail_mimeDecode {
|
86 |
-
|
87 |
-
/**
|
88 |
-
* The raw email to decode
|
89 |
-
*
|
90 |
-
* @var string
|
91 |
-
* @access private
|
92 |
-
*/
|
93 |
-
var $_input;
|
94 |
-
|
95 |
-
/**
|
96 |
-
* The header part of the input
|
97 |
-
*
|
98 |
-
* @var string
|
99 |
-
* @access private
|
100 |
-
*/
|
101 |
-
var $_header;
|
102 |
-
|
103 |
-
/**
|
104 |
-
* The body part of the input
|
105 |
-
*
|
106 |
-
* @var string
|
107 |
-
* @access private
|
108 |
-
*/
|
109 |
-
var $_body;
|
110 |
-
|
111 |
-
/**
|
112 |
-
* If an error occurs, this is used to store the message
|
113 |
-
*
|
114 |
-
* @var string
|
115 |
-
* @access private
|
116 |
-
*/
|
117 |
-
var $_error;
|
118 |
-
|
119 |
-
/**
|
120 |
-
* Flag to determine whether to include bodies in the
|
121 |
-
* returned object.
|
122 |
-
*
|
123 |
-
* @var boolean
|
124 |
-
* @access private
|
125 |
-
*/
|
126 |
-
var $_include_bodies;
|
127 |
-
|
128 |
-
/**
|
129 |
-
* Flag to determine whether to decode bodies
|
130 |
-
*
|
131 |
-
* @var boolean
|
132 |
-
* @access private
|
133 |
-
*/
|
134 |
-
var $_decode_bodies;
|
135 |
-
|
136 |
-
/**
|
137 |
-
* Flag to determine whether to decode headers
|
138 |
-
*
|
139 |
-
* @var boolean
|
140 |
-
* @access private
|
141 |
-
*/
|
142 |
-
var $_decode_headers;
|
143 |
-
|
144 |
-
/**
|
145 |
-
* Flag to determine whether to include attached messages
|
146 |
-
* as body in the returned object. Depends on $_include_bodies
|
147 |
-
*
|
148 |
-
* @var boolean
|
149 |
-
* @access private
|
150 |
-
*/
|
151 |
-
var $_rfc822_bodies;
|
152 |
-
|
153 |
-
/**
|
154 |
-
* Constructor.
|
155 |
-
*
|
156 |
-
* Sets up the object, initialise the variables, and splits and
|
157 |
-
* stores the header and body of the input.
|
158 |
-
*
|
159 |
-
* @param string The input to decode
|
160 |
-
* @access public
|
161 |
-
*/
|
162 |
-
function Mail_mimeDecode($input) {
|
163 |
-
list($header, $body) = $this->_splitBodyHeader($input);
|
164 |
-
|
165 |
-
$this->_input = $input;
|
166 |
-
$this->_header = $header;
|
167 |
-
$this->_body = $body;
|
168 |
-
$this->_decode_bodies = false;
|
169 |
-
$this->_include_bodies = true;
|
170 |
-
$this->_rfc822_bodies = false;
|
171 |
-
}
|
172 |
-
|
173 |
-
/**
|
174 |
-
* Begins the decoding process. If called statically
|
175 |
-
* it will create an object and call the decode() method
|
176 |
-
* of it.
|
177 |
-
*
|
178 |
-
* @param array An array of various parameters that determine
|
179 |
-
* various things:
|
180 |
-
* include_bodies - Whether to include the body in the returned
|
181 |
-
* object.
|
182 |
-
* decode_bodies - Whether to decode the bodies
|
183 |
-
* of the parts. (Transfer encoding)
|
184 |
-
* decode_headers - Whether to decode headers
|
185 |
-
* input - If called statically, this will be treated
|
186 |
-
* as the input
|
187 |
-
* @return object Decoded results
|
188 |
-
* @access public
|
189 |
-
*/
|
190 |
-
function decode($params = null) {
|
191 |
-
// determine if this method has been called statically
|
192 |
-
$isStatic = empty($this) || !is_a($this, __CLASS__);
|
193 |
-
|
194 |
-
// Have we been called statically?
|
195 |
-
// If so, create an object and pass details to that.
|
196 |
-
if ($isStatic AND isset($params['input'])) {
|
197 |
-
|
198 |
-
$obj = new Mail_mimeDecode($params['input']);
|
199 |
-
$structure = $obj->decode($params);
|
200 |
-
|
201 |
-
// Called statically but no input
|
202 |
-
} elseif ($isStatic) {
|
203 |
-
throw new Exception('Called statically and no input given');
|
204 |
-
|
205 |
-
// Called via an object
|
206 |
-
} else {
|
207 |
-
$this->_include_bodies = isset($params['include_bodies']) ?
|
208 |
-
$params['include_bodies'] : false;
|
209 |
-
$this->_decode_bodies = isset($params['decode_bodies']) ?
|
210 |
-
$params['decode_bodies'] : false;
|
211 |
-
$this->_decode_headers = isset($params['decode_headers']) ?
|
212 |
-
$params['decode_headers'] : false;
|
213 |
-
$this->_rfc822_bodies = isset($params['rfc_822bodies']) ?
|
214 |
-
$params['rfc_822bodies'] : false;
|
215 |
-
|
216 |
-
$structure = $this->_decode($this->_header, $this->_body);
|
217 |
-
if ($structure === false) {
|
218 |
-
throw new Exception($this->_error);
|
219 |
-
}
|
220 |
-
}
|
221 |
-
|
222 |
-
return $structure;
|
223 |
-
}
|
224 |
-
|
225 |
-
/**
|
226 |
-
* Performs the decoding. Decodes the body string passed to it
|
227 |
-
* If it finds certain content-types it will call itself in a
|
228 |
-
* recursive fashion
|
229 |
-
*
|
230 |
-
* @param string Header section
|
231 |
-
* @param string Body section
|
232 |
-
* @return object Results of decoding process
|
233 |
-
* @access private
|
234 |
-
*/
|
235 |
-
function _decode($headers, $body, $default_ctype = 'text/plain') {
|
236 |
-
$return = new stdClass;
|
237 |
-
$return->headers = array();
|
238 |
-
$headers = $this->_parseHeaders($headers);
|
239 |
-
|
240 |
-
foreach ($headers as $value) {
|
241 |
-
$value['value'] = $this->_decode_headers ? $this->_decodeHeader($value['value']) : $value['value'];
|
242 |
-
if (isset($return->headers[strtolower($value['name'])]) AND ! is_array($return->headers[strtolower($value['name'])])) {
|
243 |
-
$return->headers[strtolower($value['name'])] = array($return->headers[strtolower($value['name'])]);
|
244 |
-
$return->headers[strtolower($value['name'])][] = $value['value'];
|
245 |
-
} elseif (isset($return->headers[strtolower($value['name'])])) {
|
246 |
-
$return->headers[strtolower($value['name'])][] = $value['value'];
|
247 |
-
} else {
|
248 |
-
$return->headers[strtolower($value['name'])] = $value['value'];
|
249 |
-
}
|
250 |
-
}
|
251 |
-
|
252 |
-
foreach ($headers as $key => $value) {
|
253 |
-
$headers[$key]['name'] = strtolower($headers[$key]['name']);
|
254 |
-
switch ($headers[$key]['name']) {
|
255 |
-
|
256 |
-
case 'content-type':
|
257 |
-
$content_type = $this->_parseHeaderValue($headers[$key]['value']);
|
258 |
-
|
259 |
-
if (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) {
|
260 |
-
$return->ctype_primary = $regs[1];
|
261 |
-
$return->ctype_secondary = $regs[2];
|
262 |
-
}
|
263 |
-
|
264 |
-
if (isset($content_type['other'])) {
|
265 |
-
foreach ($content_type['other'] as $p_name => $p_value) {
|
266 |
-
$return->ctype_parameters[$p_name] = $p_value;
|
267 |
-
}
|
268 |
-
}
|
269 |
-
break;
|
270 |
-
|
271 |
-
case 'content-disposition':
|
272 |
-
$content_disposition = $this->_parseHeaderValue($headers[$key]['value']);
|
273 |
-
$return->disposition = $content_disposition['value'];
|
274 |
-
if (isset($content_disposition['other'])) {
|
275 |
-
foreach ($content_disposition['other'] as $p_name => $p_value) {
|
276 |
-
$return->d_parameters[$p_name] = $p_value;
|
277 |
-
}
|
278 |
-
}
|
279 |
-
break;
|
280 |
-
|
281 |
-
case 'content-transfer-encoding':
|
282 |
-
$content_transfer_encoding = $this->_parseHeaderValue($headers[$key]['value']);
|
283 |
-
break;
|
284 |
-
}
|
285 |
-
}
|
286 |
-
|
287 |
-
if (isset($content_type)) {
|
288 |
-
switch (strtolower($content_type['value'])) {
|
289 |
-
case 'text/plain':
|
290 |
-
$encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
|
291 |
-
$this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;
|
292 |
-
break;
|
293 |
-
|
294 |
-
case 'text/html':
|
295 |
-
$encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
|
296 |
-
$this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;
|
297 |
-
break;
|
298 |
-
|
299 |
-
case 'multipart/parallel':
|
300 |
-
case 'multipart/appledouble': // Appledouble mail
|
301 |
-
case 'multipart/report': // RFC1892
|
302 |
-
case 'multipart/signed': // PGP
|
303 |
-
case 'multipart/digest':
|
304 |
-
case 'multipart/alternative':
|
305 |
-
case 'multipart/related':
|
306 |
-
case 'multipart/mixed':
|
307 |
-
case 'application/vnd.wap.multipart.related':
|
308 |
-
if (!isset($content_type['other']['boundary'])) {
|
309 |
-
$this->_error = 'No boundary found for ' . $content_type['value'] . ' part';
|
310 |
-
return false;
|
311 |
-
}
|
312 |
-
|
313 |
-
$default_ctype = (strtolower($content_type['value']) === 'multipart/digest') ? 'message/rfc822' : 'text/plain';
|
314 |
-
|
315 |
-
$parts = $this->_boundarySplit($body, $content_type['other']['boundary']);
|
316 |
-
for ($i = 0; $i < count($parts); $i++) {
|
317 |
-
list($part_header, $part_body) = $this->_splitBodyHeader($parts[$i]);
|
318 |
-
$part = $this->_decode($part_header, $part_body, $default_ctype);
|
319 |
-
if ($part === false) {
|
320 |
-
$part = $this->raiseError($this->_error);
|
321 |
-
}
|
322 |
-
$return->parts[] = $part;
|
323 |
-
}
|
324 |
-
break;
|
325 |
-
|
326 |
-
case 'message/rfc822':
|
327 |
-
if ($this->_rfc822_bodies) {
|
328 |
-
$encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
|
329 |
-
$return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body);
|
330 |
-
}
|
331 |
-
$obj = new Mail_mimeDecode($body);
|
332 |
-
$return->parts[] = $obj->decode(array('include_bodies' => $this->_include_bodies,
|
333 |
-
'decode_bodies' => $this->_decode_bodies,
|
334 |
-
'decode_headers' => $this->_decode_headers));
|
335 |
-
unset($obj);
|
336 |
-
break;
|
337 |
-
|
338 |
-
default:
|
339 |
-
if (!isset($content_transfer_encoding['value']))
|
340 |
-
$content_transfer_encoding['value'] = '7bit';
|
341 |
-
$this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $content_transfer_encoding['value']) : $body) : null;
|
342 |
-
break;
|
343 |
-
}
|
344 |
-
} else {
|
345 |
-
$ctype = explode('/', $default_ctype);
|
346 |
-
$return->ctype_primary = $ctype[0];
|
347 |
-
$return->ctype_secondary = $ctype[1];
|
348 |
-
$this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body) : $body) : null;
|
349 |
-
}
|
350 |
-
|
351 |
-
return $return;
|
352 |
-
}
|
353 |
-
|
354 |
-
/**
|
355 |
-
* Given the output of the above function, this will return an
|
356 |
-
* array of references to the parts, indexed by mime number.
|
357 |
-
*
|
358 |
-
* @param object $structure The structure to go through
|
359 |
-
* @param string $mime_number Internal use only.
|
360 |
-
* @return array Mime numbers
|
361 |
-
*/
|
362 |
-
function &getMimeNumbers(&$structure, $no_refs = false, $mime_number = '', $prepend = '') {
|
363 |
-
$return = array();
|
364 |
-
if (!empty($structure->parts)) {
|
365 |
-
if ($mime_number != '') {
|
366 |
-
$structure->mime_id = $prepend . $mime_number;
|
367 |
-
$return[$prepend . $mime_number] = &$structure;
|
368 |
-
}
|
369 |
-
for ($i = 0; $i < count($structure->parts); $i++) {
|
370 |
-
|
371 |
-
|
372 |
-
if (!empty($structure->headers['content-type']) AND substr(strtolower($structure->headers['content-type']), 0, 8) == 'message/') {
|
373 |
-
$prepend = $prepend . $mime_number . '.';
|
374 |
-
$_mime_number = '';
|
375 |
-
} else {
|
376 |
-
$_mime_number = ($mime_number == '' ? $i + 1 : sprintf('%s.%s', $mime_number, $i + 1));
|
377 |
-
}
|
378 |
-
|
379 |
-
$arr = &Mail_mimeDecode::getMimeNumbers($structure->parts[$i], $no_refs, $_mime_number, $prepend);
|
380 |
-
foreach ($arr as $key => $val) {
|
381 |
-
$no_refs ? $return[$key] = '' : $return[$key] = &$arr[$key];
|
382 |
-
}
|
383 |
-
}
|
384 |
-
} else {
|
385 |
-
if ($mime_number == '') {
|
386 |
-
$mime_number = '1';
|
387 |
-
}
|
388 |
-
$structure->mime_id = $prepend . $mime_number;
|
389 |
-
$no_refs ? $return[$prepend . $mime_number] = '' : $return[$prepend . $mime_number] = &$structure;
|
390 |
-
}
|
391 |
-
|
392 |
-
return $return;
|
393 |
-
}
|
394 |
-
|
395 |
-
/**
|
396 |
-
* Given a string containing a header and body
|
397 |
-
* section, this function will split them (at the first
|
398 |
-
* blank line) and return them.
|
399 |
-
*
|
400 |
-
* @param string Input to split apart
|
401 |
-
* @return array Contains header and body section
|
402 |
-
* @access private
|
403 |
-
*/
|
404 |
-
function _splitBodyHeader($input) {
|
405 |
-
if (preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $input, $match)) {
|
406 |
-
return array($match[1], $match[2]);
|
407 |
-
}
|
408 |
-
// bug #17325 - empty bodies are allowed. - we just check that at least one line
|
409 |
-
// of headers exist..
|
410 |
-
if (count(explode("\n", $input))) {
|
411 |
-
return array($input, '');
|
412 |
-
}
|
413 |
-
$this->_error = 'Could not split header and body';
|
414 |
-
return false;
|
415 |
-
}
|
416 |
-
|
417 |
-
/**
|
418 |
-
* Parse headers given in $input and return
|
419 |
-
* as assoc array.
|
420 |
-
*
|
421 |
-
* @param string Headers to parse
|
422 |
-
* @return array Contains parsed headers
|
423 |
-
* @access private
|
424 |
-
*/
|
425 |
-
function _parseHeaders($input) {
|
426 |
-
|
427 |
-
if ($input !== '') {
|
428 |
-
// Unfold the input
|
429 |
-
$input = preg_replace("/\r?\n/", "\r\n", $input);
|
430 |
-
//#7065 - wrapping.. with encoded stuff.. - probably not needed,
|
431 |
-
// wrapping space should only get removed if the trailing item on previous line is a
|
432 |
-
// encoded character
|
433 |
-
$input = preg_replace("/=\r\n(\t| )+/", '=', $input);
|
434 |
-
$input = preg_replace("/\r\n(\t| )+/", ' ', $input);
|
435 |
-
|
436 |
-
$headers = explode("\r\n", trim($input));
|
437 |
-
|
438 |
-
foreach ($headers as $value) {
|
439 |
-
$hdr_name = substr($value, 0, $pos = strpos($value, ':'));
|
440 |
-
$hdr_value = substr($value, $pos + 1);
|
441 |
-
if ($hdr_value[0] == ' ') {
|
442 |
-
$hdr_value = substr($hdr_value, 1);
|
443 |
-
}
|
444 |
-
|
445 |
-
$return[] = array(
|
446 |
-
'name' => $hdr_name,
|
447 |
-
'value' => $hdr_value
|
448 |
-
);
|
449 |
-
}
|
450 |
-
} else {
|
451 |
-
$return = array();
|
452 |
-
}
|
453 |
-
|
454 |
-
return $return;
|
455 |
-
}
|
456 |
-
|
457 |
-
/**
|
458 |
-
* Function to parse a header value,
|
459 |
-
* extract first part, and any secondary
|
460 |
-
* parts (after ;) This function is not as
|
461 |
-
* robust as it could be. Eg. header comments
|
462 |
-
* in the wrong place will probably break it.
|
463 |
-
*
|
464 |
-
* @param string Header value to parse
|
465 |
-
* @return array Contains parsed result
|
466 |
-
* @access private
|
467 |
-
*/
|
468 |
-
function _parseHeaderValue($input) {
|
469 |
-
|
470 |
-
if (($pos = strpos($input, ';')) === false) {
|
471 |
-
$input = $this->_decode_headers ? $this->_decodeHeader($input) : $input;
|
472 |
-
$return['value'] = trim($input);
|
473 |
-
return $return;
|
474 |
-
}
|
475 |
-
|
476 |
-
|
477 |
-
|
478 |
-
$value = substr($input, 0, $pos);
|
479 |
-
$value = $this->_decode_headers ? $this->_decodeHeader($value) : $value;
|
480 |
-
$return['value'] = trim($value);
|
481 |
-
$input = trim(substr($input, $pos + 1));
|
482 |
-
|
483 |
-
if (!strlen($input) > 0) {
|
484 |
-
return $return;
|
485 |
-
}
|
486 |
-
// at this point input contains xxxx=".....";zzzz="...."
|
487 |
-
// since we are dealing with quoted strings, we need to handle this properly..
|
488 |
-
$i = 0;
|
489 |
-
$l = strlen($input);
|
490 |
-
$key = '';
|
491 |
-
$val = false; // our string - including quotes..
|
492 |
-
$q = false; // in quote..
|
493 |
-
$lq = ''; // last quote..
|
494 |
-
|
495 |
-
while ($i < $l) {
|
496 |
-
|
497 |
-
$c = $input[$i];
|
498 |
-
//var_dump(array('i'=>$i,'c'=>$c,'q'=>$q, 'lq'=>$lq, 'key'=>$key, 'val' =>$val));
|
499 |
-
|
500 |
-
$escaped = false;
|
501 |
-
if ($c == '\\') {
|
502 |
-
$i++;
|
503 |
-
if ($i == $l - 1) { // end of string.
|
504 |
-
break;
|
505 |
-
}
|
506 |
-
$escaped = true;
|
507 |
-
$c = $input[$i];
|
508 |
-
}
|
509 |
-
|
510 |
-
|
511 |
-
// state - in key..
|
512 |
-
if ($val === false) {
|
513 |
-
if (!$escaped && $c == '=') {
|
514 |
-
$val = '';
|
515 |
-
$key = trim($key);
|
516 |
-
$i++;
|
517 |
-
continue;
|
518 |
-
}
|
519 |
-
if (!$escaped && $c == ';') {
|
520 |
-
if ($key) { // a key without a value..
|
521 |
-
$key = trim($key);
|
522 |
-
$return['other'][$key] = '';
|
523 |
-
$return['other'][strtolower($key)] = '';
|
524 |
-
}
|
525 |
-
$key = '';
|
526 |
-
}
|
527 |
-
$key .= $c;
|
528 |
-
$i++;
|
529 |
-
continue;
|
530 |
-
}
|
531 |
-
|
532 |
-
// state - in value.. (as $val is set..)
|
533 |
-
|
534 |
-
if ($q === false) {
|
535 |
-
// not in quote yet.
|
536 |
-
if ((!strlen($val) || $lq !== false) && $c == ' ' || $c == "\t") {
|
537 |
-
$i++;
|
538 |
-
continue; // skip leading spaces after '=' or after '"'
|
539 |
-
}
|
540 |
-
if (!$escaped && ($c == '"' || $c == "'")) {
|
541 |
-
// start quoted area..
|
542 |
-
$q = $c;
|
543 |
-
// in theory should not happen raw text in value part..
|
544 |
-
// but we will handle it as a merged part of the string..
|
545 |
-
$val = !strlen(trim($val)) ? '' : trim($val);
|
546 |
-
$i++;
|
547 |
-
continue;
|
548 |
-
}
|
549 |
-
// got end....
|
550 |
-
if (!$escaped && $c == ';') {
|
551 |
-
|
552 |
-
$val = trim($val);
|
553 |
-
$added = false;
|
554 |
-
if (preg_match('/\*[0-9]+$/', $key)) {
|
555 |
-
// this is the extended aaa*0=...;aaa*1=.... code
|
556 |
-
// it assumes the pieces arrive in order, and are valid...
|
557 |
-
$key = preg_replace('/\*[0-9]+$/', '', $key);
|
558 |
-
if (isset($return['other'][$key])) {
|
559 |
-
$return['other'][$key] .= $val;
|
560 |
-
if (strtolower($key) != $key) {
|
561 |
-
$return['other'][strtolower($key)] .= $val;
|
562 |
-
}
|
563 |
-
$added = true;
|
564 |
-
}
|
565 |
-
// continue and use standard setters..
|
566 |
-
}
|
567 |
-
if (!$added) {
|
568 |
-
$return['other'][$key] = $val;
|
569 |
-
$return['other'][strtolower($key)] = $val;
|
570 |
-
}
|
571 |
-
$val = false;
|
572 |
-
$key = '';
|
573 |
-
$lq = false;
|
574 |
-
$i++;
|
575 |
-
continue;
|
576 |
-
}
|
577 |
-
|
578 |
-
$val .= $c;
|
579 |
-
$i++;
|
580 |
-
continue;
|
581 |
-
}
|
582 |
-
|
583 |
-
// state - in quote..
|
584 |
-
if (!$escaped && $c == $q) { // potential exit state..
|
585 |
-
// end of quoted string..
|
586 |
-
$lq = $q;
|
587 |
-
$q = false;
|
588 |
-
$i++;
|
589 |
-
continue;
|
590 |
-
}
|
591 |
-
|
592 |
-
// normal char inside of quoted string..
|
593 |
-
$val.= $c;
|
594 |
-
$i++;
|
595 |
-
}
|
596 |
-
|
597 |
-
// do we have anything left..
|
598 |
-
if (strlen(trim($key)) || $val !== false) {
|
599 |
-
|
600 |
-
$val = trim($val);
|
601 |
-
$added = false;
|
602 |
-
if ($val !== false && preg_match('/\*[0-9]+$/', $key)) {
|
603 |
-
// no dupes due to our crazy regexp.
|
604 |
-
$key = preg_replace('/\*[0-9]+$/', '', $key);
|
605 |
-
if (isset($return['other'][$key])) {
|
606 |
-
$return['other'][$key] .= $val;
|
607 |
-
if (strtolower($key) != $key) {
|
608 |
-
$return['other'][strtolower($key)] .= $val;
|
609 |
-
}
|
610 |
-
$added = true;
|
611 |
-
}
|
612 |
-
// continue and use standard setters..
|
613 |
-
}
|
614 |
-
if (!$added) {
|
615 |
-
$return['other'][$key] = $val;
|
616 |
-
$return['other'][strtolower($key)] = $val;
|
617 |
-
}
|
618 |
-
}
|
619 |
-
// decode values.
|
620 |
-
foreach ($return['other'] as $key => $val) {
|
621 |
-
$return['other'][$key] = $this->_decode_headers ? $this->_decodeHeader($val) : $val;
|
622 |
-
}
|
623 |
-
//print_r($return);
|
624 |
-
return $return;
|
625 |
-
}
|
626 |
-
|
627 |
-
/**
|
628 |
-
* This function splits the input based
|
629 |
-
* on the given boundary
|
630 |
-
*
|
631 |
-
* @param string Input to parse
|
632 |
-
* @return array Contains array of resulting mime parts
|
633 |
-
* @access private
|
634 |
-
*/
|
635 |
-
function _boundarySplit($input, $boundary) {
|
636 |
-
$parts = array();
|
637 |
-
|
638 |
-
$bs_possible = substr($boundary, 2, -2);
|
639 |
-
$bs_check = '\"' . $bs_possible . '\"';
|
640 |
-
|
641 |
-
if ($boundary == $bs_check) {
|
642 |
-
$boundary = $bs_possible;
|
643 |
-
}
|
644 |
-
$tmp = preg_split("/--" . preg_quote($boundary, '/') . "((?=\s)|--)/", $input);
|
645 |
-
|
646 |
-
$len = count($tmp) - 1;
|
647 |
-
for ($i = 1; $i < $len; $i++) {
|
648 |
-
if (strlen(trim($tmp[$i]))) {
|
649 |
-
$parts[] = $tmp[$i];
|
650 |
-
}
|
651 |
-
}
|
652 |
-
|
653 |
-
// add the last part on if it does not end with the 'closing indicator'
|
654 |
-
if (!empty($tmp[$len]) && strlen(trim($tmp[$len])) && $tmp[$len][0] != '-') {
|
655 |
-
$parts[] = $tmp[$len];
|
656 |
-
}
|
657 |
-
return $parts;
|
658 |
-
}
|
659 |
-
|
660 |
-
/**
|
661 |
-
* Given a header, this function will decode it
|
662 |
-
* according to RFC2047. Probably not *exactly*
|
663 |
-
* conformant, but it does pass all the given
|
664 |
-
* examples (in RFC2047).
|
665 |
-
*
|
666 |
-
* @param string Input header value to decode
|
667 |
-
* @return string Decoded header value
|
668 |
-
* @access private
|
669 |
-
*/
|
670 |
-
function _decodeHeader($input) {
|
671 |
-
// Remove white space between encoded-words
|
672 |
-
$input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?', $input);
|
673 |
-
|
674 |
-
// For each encoded-word...
|
675 |
-
while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i', $input, $matches)) {
|
676 |
-
|
677 |
-
$encoded = $matches[1];
|
678 |
-
$charset = $matches[2];
|
679 |
-
$encoding = $matches[3];
|
680 |
-
$text = $matches[4];
|
681 |
-
|
682 |
-
switch (strtolower($encoding)) {
|
683 |
-
case 'b':
|
684 |
-
$text = iconv($charset, "UTF-8//TRANSLIT", base64_decode($text));
|
685 |
-
break;
|
686 |
-
|
687 |
-
case 'q':
|
688 |
-
$text = iconv($charset, "UTF-8//TRANSLIT", $this->_decode_Q($text));
|
689 |
-
break;
|
690 |
-
}
|
691 |
-
|
692 |
-
$input = str_replace($encoded, $text, $input);
|
693 |
-
}
|
694 |
-
|
695 |
-
return $input;
|
696 |
-
}
|
697 |
-
|
698 |
-
# _decode_Q STRING
|
699 |
-
# Private: used by _decodeHeader() to decode "Q" encoding, which is
|
700 |
-
# almost, but not exactly, quoted-printable. :-P
|
701 |
-
# source: http://www.phpkode.com/source/p/atmail/atmailopen/libs/Atmail/MIME_Words.php
|
702 |
-
|
703 |
-
function _decode_Q($str) {
|
704 |
-
//$str = str_replace('_', "\x20", $str); # RFC-1522, Q rule 2
|
705 |
-
//$str = preg_replace('/=([\da-fA-F]{2})/e', "pack('C', hexdec('$1'))", $str); # RFC-1522, Q rule 1
|
706 |
-
$str = quoted_printable_decode(str_replace("_", " ", $str));
|
707 |
-
return $str;
|
708 |
-
}
|
709 |
-
|
710 |
-
/**
|
711 |
-
* Given a body string and an encoding type,
|
712 |
-
* this function will decode and return it.
|
713 |
-
*
|
714 |
-
* @param string Input body to decode
|
715 |
-
* @param string Encoding type to use.
|
716 |
-
* @return string Decoded body
|
717 |
-
* @access private
|
718 |
-
*/
|
719 |
-
function _decodeBody($input, $encoding = '7bit') {
|
720 |
-
switch (strtolower($encoding)) {
|
721 |
-
case '7bit':
|
722 |
-
return $input;
|
723 |
-
|
724 |
-
case 'quoted-printable':
|
725 |
-
return $this->_quotedPrintableDecode($input);
|
726 |
-
|
727 |
-
case 'base64':
|
728 |
-
return base64_decode($input);
|
729 |
-
|
730 |
-
default:
|
731 |
-
return $input;
|
732 |
-
}
|
733 |
-
}
|
734 |
-
|
735 |
-
/**
|
736 |
-
* Given a quoted-printable string, this
|
737 |
-
* function will decode and return it.
|
738 |
-
*
|
739 |
-
* @param string Input body to decode
|
740 |
-
* @return string Decoded body
|
741 |
-
* @access private
|
742 |
-
*/
|
743 |
-
function _quotedPrintableDecode($input) {
|
744 |
-
// Remove soft line breaks
|
745 |
-
$input = preg_replace("/=\r?\n/", '', $input);
|
746 |
-
|
747 |
-
// Replace encoded characters
|
748 |
-
$input = preg_replace('/=([a-f0-9]{2})/ie', "chr(hexdec('\\1'))", $input);
|
749 |
-
|
750 |
-
return $input;
|
751 |
-
}
|
752 |
-
|
753 |
-
/**
|
754 |
-
* Checks the input for uuencoded files and returns
|
755 |
-
* an array of them. Can be called statically, eg:
|
756 |
-
*
|
757 |
-
* $files =& Mail_mimeDecode::uudecode($some_text);
|
758 |
-
*
|
759 |
-
* It will check for the begin 666 ... end syntax
|
760 |
-
* however and won't just blindly decode whatever you
|
761 |
-
* pass it.
|
762 |
-
*
|
763 |
-
* @param string Input body to look for attahcments in
|
764 |
-
* @return array Decoded bodies, filenames and permissions
|
765 |
-
* @access public
|
766 |
-
* @author Unknown
|
767 |
-
*/
|
768 |
-
function &uudecode($input) {
|
769 |
-
// Find all uuencoded sections
|
770 |
-
preg_match_all("/begin ([0-7]{3}) (.+)\r?\n(.+)\r?\nend/Us", $input, $matches);
|
771 |
-
|
772 |
-
for ($j = 0; $j < count($matches[3]); $j++) {
|
773 |
-
|
774 |
-
$str = $matches[3][$j];
|
775 |
-
$filename = $matches[2][$j];
|
776 |
-
$fileperm = $matches[1][$j];
|
777 |
-
|
778 |
-
$file = '';
|
779 |
-
$str = preg_split("/\r?\n/", trim($str));
|
780 |
-
$strlen = count($str);
|
781 |
-
|
782 |
-
for ($i = 0; $i < $strlen; $i++) {
|
783 |
-
$pos = 1;
|
784 |
-
$d = 0;
|
785 |
-
$len = (int) (((ord(substr($str[$i], 0, 1)) - 32) - ' ') & 077);
|
786 |
-
|
787 |
-
while (($d + 3 <= $len) AND ( $pos + 4 <= strlen($str[$i]))) {
|
788 |
-
$c0 = (ord(substr($str[$i], $pos, 1)) ^ 0x20);
|
789 |
-
$c1 = (ord(substr($str[$i], $pos + 1, 1)) ^ 0x20);
|
790 |
-
$c2 = (ord(substr($str[$i], $pos + 2, 1)) ^ 0x20);
|
791 |
-
$c3 = (ord(substr($str[$i], $pos + 3, 1)) ^ 0x20);
|
792 |
-
$file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
|
793 |
-
|
794 |
-
$file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
|
795 |
-
|
796 |
-
$file .= chr(((($c2 - ' ') & 077) << 6) | (($c3 - ' ') & 077));
|
797 |
-
|
798 |
-
$pos += 4;
|
799 |
-
$d += 3;
|
800 |
-
}
|
801 |
-
|
802 |
-
if (($d + 2 <= $len) && ($pos + 3 <= strlen($str[$i]))) {
|
803 |
-
$c0 = (ord(substr($str[$i], $pos, 1)) ^ 0x20);
|
804 |
-
$c1 = (ord(substr($str[$i], $pos + 1, 1)) ^ 0x20);
|
805 |
-
$c2 = (ord(substr($str[$i], $pos + 2, 1)) ^ 0x20);
|
806 |
-
$file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
|
807 |
-
|
808 |
-
$file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
|
809 |
-
|
810 |
-
$pos += 3;
|
811 |
-
$d += 2;
|
812 |
-
}
|
813 |
-
|
814 |
-
if (($d + 1 <= $len) && ($pos + 2 <= strlen($str[$i]))) {
|
815 |
-
$c0 = (ord(substr($str[$i], $pos, 1)) ^ 0x20);
|
816 |
-
$c1 = (ord(substr($str[$i], $pos + 1, 1)) ^ 0x20);
|
817 |
-
$file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
|
818 |
-
}
|
819 |
-
}
|
820 |
-
$files[] = array('filename' => $filename, 'fileperm' => $fileperm, 'filedata' => $file);
|
821 |
-
}
|
822 |
-
|
823 |
-
return $files;
|
824 |
-
}
|
825 |
-
|
826 |
-
/**
|
827 |
-
* getSendArray() returns the arguments required for Mail::send()
|
828 |
-
* used to build the arguments for a mail::send() call
|
829 |
-
*
|
830 |
-
* Usage:
|
831 |
-
* $mailtext = Full email (for example generated by a template)
|
832 |
-
* $decoder = new Mail_mimeDecode($mailtext);
|
833 |
-
* $parts = $decoder->getSendArray();
|
834 |
-
* if (!PEAR::isError($parts) {
|
835 |
-
* list($recipents,$headers,$body) = $parts;
|
836 |
-
* $mail = Mail::factory('smtp');
|
837 |
-
* $mail->send($recipents,$headers,$body);
|
838 |
-
* } else {
|
839 |
-
* echo $parts->message;
|
840 |
-
* }
|
841 |
-
* @return mixed array of recipeint, headers,body or Pear_Error
|
842 |
-
* @access public
|
843 |
-
* @author Alan Knowles <alan@akbkhome.com>
|
844 |
-
*/
|
845 |
-
function getSendArray() {
|
846 |
-
// prevent warning if this is not set
|
847 |
-
$this->_decode_headers = FALSE;
|
848 |
-
$headerlist = $this->_parseHeaders($this->_header);
|
849 |
-
$to = "";
|
850 |
-
if (!$headerlist) {
|
851 |
-
return $this->raiseError("Message did not contain headers");
|
852 |
-
}
|
853 |
-
foreach ($headerlist as $item) {
|
854 |
-
$header[$item['name']] = $item['value'];
|
855 |
-
switch (strtolower($item['name'])) {
|
856 |
-
case "to":
|
857 |
-
case "cc":
|
858 |
-
case "bcc":
|
859 |
-
$to .= "," . $item['value'];
|
860 |
-
default:
|
861 |
-
break;
|
862 |
-
}
|
863 |
-
}
|
864 |
-
if ($to == "") {
|
865 |
-
return $this->raiseError("Message did not contain any recipents");
|
866 |
-
}
|
867 |
-
$to = substr($to, 1);
|
868 |
-
return array($to, $header, $this->_body);
|
869 |
-
}
|
870 |
-
|
871 |
-
/**
|
872 |
-
* Returns a xml copy of the output of
|
873 |
-
* Mail_mimeDecode::decode. Pass the output in as the
|
874 |
-
* argument. This function can be called statically. Eg:
|
875 |
-
*
|
876 |
-
* $output = $obj->decode();
|
877 |
-
* $xml = Mail_mimeDecode::getXML($output);
|
878 |
-
*
|
879 |
-
* The DTD used for this should have been in the package. Or
|
880 |
-
* alternatively you can get it from cvs, or here:
|
881 |
-
* http://www.phpguru.org/xmail/xmail.dtd.
|
882 |
-
*
|
883 |
-
* @param object Input to convert to xml. This should be the
|
884 |
-
* output of the Mail_mimeDecode::decode function
|
885 |
-
* @return string XML version of input
|
886 |
-
* @access public
|
887 |
-
*/
|
888 |
-
function getXML($input) {
|
889 |
-
$crlf = "\r\n";
|
890 |
-
$output = '<?xml version=\'1.0\'?>' . $crlf .
|
891 |
-
'<!DOCTYPE email SYSTEM "http://www.phpguru.org/xmail/xmail.dtd">' . $crlf .
|
892 |
-
'<email>' . $crlf .
|
893 |
-
Mail_mimeDecode::_getXML($input) .
|
894 |
-
'</email>';
|
895 |
-
|
896 |
-
return $output;
|
897 |
-
}
|
898 |
-
|
899 |
-
/**
|
900 |
-
* Function that does the actual conversion to xml. Does a single
|
901 |
-
* mimepart at a time.
|
902 |
-
*
|
903 |
-
* @param object Input to convert to xml. This is a mimepart object.
|
904 |
-
* It may or may not contain subparts.
|
905 |
-
* @param integer Number of tabs to indent
|
906 |
-
* @return string XML version of input
|
907 |
-
* @access private
|
908 |
-
*/
|
909 |
-
function _getXML($input, $indent = 1) {
|
910 |
-
$htab = "\t";
|
911 |
-
$crlf = "\r\n";
|
912 |
-
$output = '';
|
913 |
-
$headers = @(array) $input->headers;
|
914 |
-
|
915 |
-
foreach ($headers as $hdr_name => $hdr_value) {
|
916 |
-
|
917 |
-
// Multiple headers with this name
|
918 |
-
if (is_array($headers[$hdr_name])) {
|
919 |
-
for ($i = 0; $i < count($hdr_value); $i++) {
|
920 |
-
$output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value[$i], $indent);
|
921 |
-
}
|
922 |
-
|
923 |
-
// Only one header of this sort
|
924 |
-
} else {
|
925 |
-
$output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value, $indent);
|
926 |
-
}
|
927 |
-
}
|
928 |
-
|
929 |
-
if (!empty($input->parts)) {
|
930 |
-
for ($i = 0; $i < count($input->parts); $i++) {
|
931 |
-
$output .= $crlf . str_repeat($htab, $indent) . '<mimepart>' . $crlf .
|
932 |
-
Mail_mimeDecode::_getXML($input->parts[$i], $indent + 1) .
|
933 |
-
str_repeat($htab, $indent) . '</mimepart>' . $crlf;
|
934 |
-
}
|
935 |
-
} elseif (isset($input->body)) {
|
936 |
-
$output .= $crlf . str_repeat($htab, $indent) . '<body><![CDATA[' .
|
937 |
-
$input->body . ']]></body>' . $crlf;
|
938 |
-
}
|
939 |
-
|
940 |
-
return $output;
|
941 |
-
}
|
942 |
-
|
943 |
-
/**
|
944 |
-
* Helper function to _getXML(). Returns xml of a header.
|
945 |
-
*
|
946 |
-
* @param string Name of header
|
947 |
-
* @param string Value of header
|
948 |
-
* @param integer Number of tabs to indent
|
949 |
-
* @return string XML version of input
|
950 |
-
* @access private
|
951 |
-
*/
|
952 |
-
function _getXML_helper($hdr_name, $hdr_value, $indent) {
|
953 |
-
$htab = "\t";
|
954 |
-
$crlf = "\r\n";
|
955 |
-
$return = '';
|
956 |
-
|
957 |
-
$new_hdr_value = ($hdr_name != 'received') ? Mail_mimeDecode::_parseHeaderValue($hdr_value) : array('value' => $hdr_value);
|
958 |
-
$new_hdr_name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $hdr_name)));
|
959 |
-
|
960 |
-
// Sort out any parameters
|
961 |
-
if (!empty($new_hdr_value['other'])) {
|
962 |
-
foreach ($new_hdr_value['other'] as $paramname => $paramvalue) {
|
963 |
-
$params[] = str_repeat($htab, $indent) . $htab . '<parameter>' . $crlf .
|
964 |
-
str_repeat($htab, $indent) . $htab . $htab . '<paramname>' . htmlspecialchars($paramname) . '</paramname>' . $crlf .
|
965 |
-
str_repeat($htab, $indent) . $htab . $htab . '<paramvalue>' . htmlspecialchars($paramvalue) . '</paramvalue>' . $crlf .
|
966 |
-
str_repeat($htab, $indent) . $htab . '</parameter>' . $crlf;
|
967 |
-
}
|
968 |
-
|
969 |
-
$params = implode('', $params);
|
970 |
-
} else {
|
971 |
-
$params = '';
|
972 |
-
}
|
973 |
-
|
974 |
-
$return = str_repeat($htab, $indent) . '<header>' . $crlf .
|
975 |
-
str_repeat($htab, $indent) . $htab . '<headername>' . htmlspecialchars($new_hdr_name) . '</headername>' . $crlf .
|
976 |
-
str_repeat($htab, $indent) . $htab . '<headervalue>' . htmlspecialchars($new_hdr_value['value']) . '</headervalue>' . $crlf .
|
977 |
-
$params .
|
978 |
-
str_repeat($htab, $indent) . '</header>' . $crlf;
|
979 |
-
|
980 |
-
return $return;
|
981 |
-
}
|
982 |
-
|
983 |
-
}
|
984 |
-
|
985 |
-
// End of class
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
postie-functions.php
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
<?php
|
2 |
/*
|
3 |
-
$Id: postie-functions.php
|
4 |
*/
|
5 |
|
6 |
class PostiePostModifiers {
|
@@ -169,7 +169,7 @@ function tag_Date(&$content, $message_date, $isHtml) {
|
|
169 |
|
170 |
$format = "Y-m-d";
|
171 |
if ($t != '00:00:00') {
|
172 |
-
$format.= " H:i:s";
|
173 |
}
|
174 |
$message_date = date($format, $newdate);
|
175 |
$content = str_replace($matches[0], '', $content);
|
@@ -185,49 +185,131 @@ function tag_Date(&$content, $message_date, $isHtml) {
|
|
185 |
return $message_date;
|
186 |
}
|
187 |
|
188 |
-
function
|
|
|
|
|
189 |
|
190 |
-
|
191 |
-
|
|
|
|
|
|
|
|
|
192 |
|
193 |
-
|
194 |
|
195 |
-
$
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
200 |
|
201 |
-
|
202 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
203 |
if ($fulldebugdump) {
|
204 |
DebugDump($mimeDecodedEmail);
|
205 |
}
|
206 |
}
|
207 |
|
208 |
-
filter_PreferedText($mimeDecodedEmail, $config['prefer_text_type']);
|
209 |
if ($fulldebugdump) {
|
210 |
DebugDump($mimeDecodedEmail);
|
211 |
}
|
212 |
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
DebugDump($attachments);
|
217 |
-
}
|
218 |
-
if (IsDebugMode()) {
|
219 |
-
$dname = POSTIE_ROOT . DIRECTORY_SEPARATOR . "test_emails" . DIRECTORY_SEPARATOR;
|
220 |
-
if (is_dir($dname)) {
|
221 |
-
$fname = $dname . sanitize_file_name($mimeDecodedEmail->headers["message-id"]);
|
222 |
-
$file = fopen($fname . ".content.txt ", "w");
|
223 |
-
fwrite($file, $content);
|
224 |
-
fclose($file);
|
225 |
-
}
|
226 |
-
}
|
227 |
|
228 |
-
$subject =
|
229 |
|
230 |
-
filter_RemoveSignature($content, $config);
|
231 |
if ($fulldebug) {
|
232 |
DebugEcho("post sig: $content");
|
233 |
}
|
@@ -243,17 +325,9 @@ function CreatePost($poster, $mimeDecodedEmail, $post_id, &$is_reply, $config, $
|
|
243 |
}
|
244 |
|
245 |
$message_date = NULL;
|
246 |
-
if (array_key_exists("date", $mimeDecodedEmail
|
247 |
-
DebugEcho("date header: {$mimeDecodedEmail
|
248 |
-
$
|
249 |
-
$cs = "";
|
250 |
-
if (property_exists($mimeDecodedEmail, 'content-transfer-encoding') && array_key_exists('content-transfer-encoding', $mimeDecodedEmail->headers)) {
|
251 |
-
$cte = $mimeDecodedEmail->headers["content-transfer-encoding"];
|
252 |
-
}
|
253 |
-
if (property_exists($mimeDecodedEmail, 'ctype_parameters') && array_key_exists('charset', $mimeDecodedEmail->ctype_parameters)) {
|
254 |
-
$cs = $mimeDecodedEmail->ctype_parameters["charset"];
|
255 |
-
}
|
256 |
-
$message_date = HandleMessageEncoding($cte, $cs, $mimeDecodedEmail->headers["date"], $config['message_encoding'], $config['message_dequote']);
|
257 |
DebugEcho("decoded date: $message_date");
|
258 |
} else {
|
259 |
DebugEcho("date header missing");
|
@@ -265,11 +339,6 @@ function CreatePost($poster, $mimeDecodedEmail, $post_id, &$is_reply, $config, $
|
|
265 |
DebugEcho("post date: $content");
|
266 |
}
|
267 |
|
268 |
-
filter_Ubb2HTML($content);
|
269 |
-
if ($fulldebug) {
|
270 |
-
DebugEcho("post ubb: $content");
|
271 |
-
}
|
272 |
-
|
273 |
//do post type before category to keep the subject line correct
|
274 |
$post_type = tag_PostType($subject, $postmodifiers, $config);
|
275 |
if ($fulldebug) {
|
@@ -277,9 +346,11 @@ function CreatePost($poster, $mimeDecodedEmail, $post_id, &$is_reply, $config, $
|
|
277 |
}
|
278 |
|
279 |
$default_categoryid = $config['default_post_category'];
|
280 |
-
|
|
|
281 |
$default_categoryid = apply_filters('postie_category_default', $default_categoryid);
|
282 |
-
DebugEcho("post postie_category_default $default_categoryid");
|
|
|
283 |
$post_categories = tag_Categories($subject, $default_categoryid, $config, $post_id);
|
284 |
if ($fulldebug) {
|
285 |
DebugEcho("post category: $content");
|
@@ -295,13 +366,13 @@ function CreatePost($poster, $mimeDecodedEmail, $post_id, &$is_reply, $config, $
|
|
295 |
DebugEcho("post comment: $content");
|
296 |
}
|
297 |
|
298 |
-
$post_status = tag_Status($content, $post_status);
|
299 |
if ($fulldebug) {
|
300 |
DebugEcho("post status: $content");
|
301 |
}
|
302 |
|
303 |
//handle CID before linkify
|
304 |
-
filter_ReplaceImageCIDs($content, $
|
305 |
if ($fulldebug) {
|
306 |
DebugEcho("post cid: $content");
|
307 |
}
|
@@ -313,18 +384,8 @@ function CreatePost($poster, $mimeDecodedEmail, $post_id, &$is_reply, $config, $
|
|
313 |
}
|
314 |
}
|
315 |
|
316 |
-
filter_VodafoneHandler($content, $attachments);
|
317 |
-
if ($fulldebug) {
|
318 |
-
DebugEcho("post vodafone: $content");
|
319 |
-
}
|
320 |
-
|
321 |
-
$customImages = tag_CustomImageField($content, $attachments, $config);
|
322 |
-
if ($fulldebug) {
|
323 |
-
DebugEcho("post custom: $content");
|
324 |
-
}
|
325 |
-
|
326 |
if ($config['reply_as_comment'] == true) {
|
327 |
-
$id =
|
328 |
if (empty($id)) {
|
329 |
DebugEcho("Not a reply");
|
330 |
$id = $post_id;
|
@@ -342,7 +403,7 @@ function CreatePost($poster, $mimeDecodedEmail, $post_id, &$is_reply, $config, $
|
|
342 |
preg_match("/^-+.*?(from|subject|to|date).*?/i", $line) == 0 &&
|
343 |
preg_match("/^on.*?wrote:$/i", $line) == 0 &&
|
344 |
preg_match("/^-+\s*forwarded\s*message\s*-+/i", $line) == 0) {
|
345 |
-
$newContents.="$line\n";
|
346 |
}
|
347 |
}
|
348 |
if ((strlen($newContents) <> strlen($content)) && ('html' == $config['prefer_text_type'])) {
|
@@ -363,35 +424,43 @@ function CreatePost($poster, $mimeDecodedEmail, $post_id, &$is_reply, $config, $
|
|
363 |
$post_status = 'future';
|
364 |
}
|
365 |
|
366 |
-
filter_Newlines($content, $config);
|
367 |
if ($fulldebug) {
|
368 |
DebugEcho("post newline: $content");
|
369 |
}
|
370 |
|
371 |
-
filter_Start($content, $config);
|
372 |
if ($fulldebug) {
|
373 |
DebugEcho("post start: $content");
|
374 |
}
|
375 |
|
376 |
-
filter_End($content, $config);
|
377 |
if ($fulldebug) {
|
378 |
DebugEcho("post end: $content");
|
379 |
}
|
380 |
|
381 |
-
|
382 |
-
filter_ReplaceImagePlaceHolders($content, $attachments["html"], $config, $id, $config['image_placeholder'], true);
|
383 |
if ($fulldebug) {
|
384 |
DebugEcho("post body ReplaceImagePlaceHolders: $content");
|
385 |
}
|
386 |
|
387 |
if ($post_excerpt) {
|
388 |
-
filter_ReplaceImagePlaceHolders($post_excerpt, $
|
389 |
DebugEcho("excerpt: $post_excerpt");
|
390 |
if ($fulldebug) {
|
391 |
DebugEcho("post excerpt ReplaceImagePlaceHolders: $content");
|
392 |
}
|
393 |
}
|
394 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
395 |
|
396 |
if (trim($subject) == "") {
|
397 |
$subject = $config['default_title'];
|
@@ -416,7 +485,6 @@ function CreatePost($poster, $mimeDecodedEmail, $post_id, &$is_reply, $config, $
|
|
416 |
'post_name' => sanitize_title($subject),
|
417 |
'post_excerpt' => $post_excerpt,
|
418 |
'ID' => $id,
|
419 |
-
'customImages' => $customImages,
|
420 |
'post_status' => $post_status
|
421 |
);
|
422 |
return $details;
|
@@ -427,7 +495,7 @@ function CreatePost($poster, $mimeDecodedEmail, $post_id, &$is_reply, $config, $
|
|
427 |
*/
|
428 |
function PostEmail($poster, $mimeDecodedEmail, $config) {
|
429 |
postie_disable_revisions();
|
430 |
-
extract($config);
|
431 |
|
432 |
/* in order to do attachments correctly, we need to associate the
|
433 |
attachments with a post. So we add the post here, then update it */
|
@@ -439,10 +507,12 @@ function PostEmail($poster, $mimeDecodedEmail, $config) {
|
|
439 |
$is_reply = false;
|
440 |
$postmodifiers = new PostiePostModifiers();
|
441 |
|
442 |
-
$
|
|
|
|
|
443 |
|
444 |
$details = apply_filters('postie_post', $details);
|
445 |
-
$details = apply_filters('postie_post_before', $details, $mimeDecodedEmail
|
446 |
|
447 |
DebugEcho(("Post postie_post filter"));
|
448 |
DebugDump($details);
|
@@ -457,16 +527,16 @@ function PostEmail($poster, $mimeDecodedEmail, $config) {
|
|
457 |
EchoError("postie_post filter cleared the post, not saving.");
|
458 |
}
|
459 |
} else {
|
460 |
-
|
461 |
|
462 |
-
$postid =
|
463 |
|
464 |
-
if ($confirmation_email != '') {
|
465 |
-
if ($confirmation_email == 'sender') {
|
466 |
$recipients = array($details['email_author']);
|
467 |
-
} elseif ($confirmation_email == 'admin') {
|
468 |
$recipients = array(get_option("admin_email"));
|
469 |
-
} elseif ($confirmation_email == 'both') {
|
470 |
$recipients = array($details['email_author'], get_option("admin_email"));
|
471 |
}
|
472 |
if (null != $postid) {
|
@@ -513,7 +583,7 @@ function tag_PostType(&$subject, $postmodifiers, $config) {
|
|
513 |
DebugEcho("post type: found type '$post_type'");
|
514 |
$post_type = $custom_post_type;
|
515 |
$subject = trim($separated_subject[1]);
|
516 |
-
} elseif (in_array($custom_post_type, array_keys(
|
517 |
DebugEcho("post type: found format '$custom_post_type'");
|
518 |
$postmodifiers->PostFormat = $custom_post_type;
|
519 |
$subject = trim($separated_subject[1]);
|
@@ -545,14 +615,7 @@ function LoadDOM($text) {
|
|
545 |
|
546 |
function getPostAuthorDetails(&$subject, &$content, &$mimeDecodedEmail) {
|
547 |
|
548 |
-
$
|
549 |
-
if (array_key_exists("date", $mimeDecodedEmail->headers) && !empty($mimeDecodedEmail->headers["date"])) {
|
550 |
-
$theDate = $mimeDecodedEmail->headers['date'];
|
551 |
-
}
|
552 |
-
$theEmail = '';
|
553 |
-
if (array_key_exists("from", $mimeDecodedEmail->headers) && !empty($mimeDecodedEmail->headers["from"])) {
|
554 |
-
$theEmail = RemoveExtraCharactersInEmailAddress($mimeDecodedEmail->headers["from"]);
|
555 |
-
}
|
556 |
|
557 |
$regAuthor = get_user_by('email', $theEmail);
|
558 |
if ($regAuthor) {
|
@@ -560,49 +623,18 @@ function getPostAuthorDetails(&$subject, &$content, &$mimeDecodedEmail) {
|
|
560 |
$theUrl = $regAuthor->user_url;
|
561 |
$theID = $regAuthor->ID;
|
562 |
} else {
|
563 |
-
$theAuthor =
|
564 |
$theUrl = '';
|
565 |
$theID = '';
|
566 |
}
|
567 |
|
568 |
-
// see if subject starts with Fwd:
|
569 |
-
$matches = array();
|
570 |
-
if (preg_match("/(^Fwd:) (.*)/", $subject, $matches)) {
|
571 |
-
DebugEcho("Fwd: detected");
|
572 |
-
$subject = trim($matches[2]);
|
573 |
-
if (preg_match("/\nfrom:(.*?)\n/i", $content, $matches)) {
|
574 |
-
$thFeAuthor = GetNameFromEmail($matches[1]);
|
575 |
-
$mimeDecodedEmail->headers['from'] = $theAuthor;
|
576 |
-
}
|
577 |
-
//TODO doesn't always work with HTML
|
578 |
-
if (preg_match("/\ndate:(.*?)\n/i", $content, $matches)) {
|
579 |
-
$theDate = $matches[1];
|
580 |
-
DebugEcho("date in Fwd: $theDate");
|
581 |
-
if (($timestamp = strtotime($theDate)) === false) {
|
582 |
-
DebugEcho("bad date found: $theDate");
|
583 |
-
} else {
|
584 |
-
$mimeDecodedEmail->headers['date'] = $theDate;
|
585 |
-
}
|
586 |
-
}
|
587 |
-
|
588 |
-
// now get rid of forwarding info in the content
|
589 |
-
$lines = preg_split("/\r\n/", $content);
|
590 |
-
$newContents = '';
|
591 |
-
foreach ($lines as $line) {
|
592 |
-
if (preg_match("/^(from|subject|to|date):.*?/i", $line, $matches) == 0 && preg_match("/^-+\s*forwarded\s*message\s*-+/i", $line, $matches) == 0) {
|
593 |
-
$newContents.=preg_replace("/\r/", "", $line) . "\n";
|
594 |
-
}
|
595 |
-
}
|
596 |
-
$content = $newContents;
|
597 |
-
}
|
598 |
$theDetails = array(
|
599 |
-
'content' => "<div class='postmetadata alt'>On $theDate, $theAuthor" . " posted:</div>",
|
600 |
-
'emaildate' => $theDate,
|
601 |
'author' => $theAuthor,
|
602 |
'comment_author_url' => $theUrl,
|
603 |
'user_ID' => $theID,
|
604 |
'email' => $theEmail
|
605 |
);
|
|
|
606 |
return $theDetails;
|
607 |
}
|
608 |
|
@@ -614,7 +646,7 @@ function getPostAuthorDetails(&$subject, &$content, &$mimeDecodedEmail) {
|
|
614 |
* generated
|
615 |
*/
|
616 |
|
617 |
-
function
|
618 |
global $wpdb;
|
619 |
|
620 |
$id = NULL;
|
@@ -656,21 +688,10 @@ function postie_ShowReadMe() {
|
|
656 |
include(POSTIE_ROOT . DIRECTORY_SEPARATOR . "postie_read_me.php");
|
657 |
}
|
658 |
|
659 |
-
/**
|
660 |
-
* This sets up the configuration menu
|
661 |
-
*/
|
662 |
-
function PostieMenu() {
|
663 |
-
if (function_exists('add_options_page')) {
|
664 |
-
if (current_user_can('manage_options')) {
|
665 |
-
add_options_page("Postie", "Postie", 'manage_options', POSTIE_ROOT . "/postie.php", "ConfigurePostie");
|
666 |
-
}
|
667 |
-
}
|
668 |
-
}
|
669 |
-
|
670 |
/**
|
671 |
* This handles actually showing the form. Called by WordPress
|
672 |
*/
|
673 |
-
function
|
674 |
include(POSTIE_ROOT . DIRECTORY_SEPARATOR . "config_form.php");
|
675 |
}
|
676 |
|
@@ -678,7 +699,7 @@ function ConfigurePostie() {
|
|
678 |
* This function handles determining the protocol and fetching the mail
|
679 |
* @return array
|
680 |
*/
|
681 |
-
function
|
682 |
$emails = array();
|
683 |
if (!$server || !$port || !$email) {
|
684 |
EchoError("Missing Configuration For Mail Server");
|
@@ -688,136 +709,74 @@ function FetchMail($server = NULL, $port = NULL, $email = NULL, $password = NULL
|
|
688 |
DebugEcho("MAKE SURE POP IS TURNED ON IN SETTING AT Gmail");
|
689 |
}
|
690 |
switch (strtolower($protocol)) {
|
691 |
-
case 'smtp': //direct
|
692 |
-
$fd = fopen("php://stdin", "r");
|
693 |
-
$input = "";
|
694 |
-
while (!feof($fd)) {
|
695 |
-
$input .= fread($fd, 1024);
|
696 |
-
}
|
697 |
-
fclose($fd);
|
698 |
-
$emails[0] = $input;
|
699 |
-
break;
|
700 |
case 'imap':
|
701 |
case 'imap-ssl':
|
702 |
-
|
703 |
-
if (!HasIMAPSupport()) {
|
704 |
-
EchoError("Sorry - you do not have IMAP php module installed - it is required for this mail setting.");
|
705 |
-
} else {
|
706 |
-
$emails = IMAPMessageFetch($server, $port, $email, $password, $protocol, $offset, $test, $deleteMessages, $maxemails, $email_tls, $ignoreEmailState);
|
707 |
-
}
|
708 |
break;
|
709 |
case 'pop3':
|
|
|
710 |
default:
|
711 |
-
$emails =
|
712 |
}
|
713 |
|
714 |
return $emails;
|
715 |
}
|
716 |
|
717 |
-
|
718 |
-
|
719 |
-
|
720 |
-
|
721 |
-
require_once("postieIMAP.php");
|
722 |
$emails = array();
|
723 |
-
$mail_server = &PostieIMAP::Factory($protocol);
|
724 |
-
if ($tls) {
|
725 |
-
$mail_server->TLSOn();
|
726 |
-
}
|
727 |
-
DebugEcho("Connecting to $server:$port ($protocol)" . ($tls ? " with TLS" : ""));
|
728 |
-
if ($mail_server->connect(trim($server), $port, $email, $password)) {
|
729 |
-
$msg_count = $mail_server->getNumberOfMessages();
|
730 |
-
} else {
|
731 |
-
EchoError("Mail Connection Time Out");
|
732 |
-
EchoError("Common Reasons: Server Down, Network Issue, Port/Protocol MisMatch ");
|
733 |
-
EchoError("The Server said:" . $mail_server->error());
|
734 |
-
$msg_count = 0;
|
735 |
-
}
|
736 |
|
737 |
-
|
738 |
-
|
739 |
-
|
740 |
-
|
741 |
-
$
|
742 |
}
|
743 |
-
if ($
|
744 |
-
|
745 |
-
|
|
|
746 |
}
|
747 |
-
}
|
748 |
-
if ($deleteMessages) {
|
749 |
-
$mail_server->expungeMessages();
|
750 |
-
}
|
751 |
-
//clean up
|
752 |
-
$mail_server->disconnect();
|
753 |
-
return $emails;
|
754 |
-
}
|
755 |
|
756 |
-
|
757 |
-
|
758 |
-
|
759 |
-
function POP3MessageFetch($server = NULL, $port = NULL, $email = NULL, $password = NULL, $protocol = NULL, $offset = NULL, $test = NULL, $deleteMessages = true, $maxemails = 0) {
|
760 |
-
require_once(ABSPATH . WPINC . DIRECTORY_SEPARATOR . 'class-pop3.php');
|
761 |
|
762 |
-
|
763 |
-
|
764 |
-
if (defined('POSTIE_DEBUG')) {
|
765 |
-
$pop3->DEBUG = POSTIE_DEBUG;
|
766 |
-
}
|
767 |
|
768 |
-
|
769 |
|
770 |
-
|
771 |
-
|
772 |
-
|
773 |
-
|
774 |
-
|
775 |
-
|
776 |
-
|
777 |
-
|
778 |
-
|
779 |
-
|
780 |
-
|
781 |
-
|
782 |
-
DebugEcho("message count: $msg_count");
|
783 |
-
|
784 |
-
// loop through messages
|
785 |
-
//$msgs = $pop3->pop_list();
|
786 |
-
//DebugEcho("POP3MessageFetch: messages");
|
787 |
-
//DebugDump($msgs);
|
788 |
-
|
789 |
-
for ($i = 1; $i <= $msg_count; $i++) {
|
790 |
-
$m = $pop3->get($i);
|
791 |
-
if ($m !== false) {
|
792 |
-
if (is_array($m)) {
|
793 |
-
$emails[$i] = implode('', $m);
|
794 |
-
if ($deleteMessages) {
|
795 |
-
if (!$pop3->delete($i)) {
|
796 |
-
EchoError("POP3MessageFetch: cannot delete message $i: " . $pop3->ERROR);
|
797 |
-
$pop3->reset();
|
798 |
-
exit;
|
799 |
-
}
|
800 |
-
}
|
801 |
-
} else {
|
802 |
-
DebugEcho("POP3MessageFetch: message $i not an array");
|
803 |
}
|
804 |
-
} else {
|
805 |
-
EchoError("POP3MessageFetch: message $i $pop3->ERROR");
|
806 |
-
}
|
807 |
-
if ($maxemails != 0 && $i >= $maxemails) {
|
808 |
-
DebugEcho("Max emails ($maxemails)");
|
809 |
-
break;
|
810 |
}
|
|
|
|
|
|
|
|
|
|
|
811 |
}
|
812 |
-
|
813 |
-
$pop3->quit();
|
814 |
return $emails;
|
815 |
}
|
816 |
|
817 |
/**
|
818 |
* This function handles putting the actual entry into the database
|
819 |
*/
|
820 |
-
function
|
821 |
$post_ID = 0;
|
822 |
if (!$isReply) {
|
823 |
$post_ID = wp_insert_post($details, true);
|
@@ -828,8 +787,6 @@ function PostToDB($details, $isReply, $customImageField, $postmodifiers) {
|
|
828 |
wp_delete_post($details['ID']);
|
829 |
$post_ID = null;
|
830 |
}
|
831 |
-
//evidently post_category was depricated at some point.
|
832 |
-
//wp_set_post_terms($post_ID, $details['post_category']);
|
833 |
} else {
|
834 |
$comment = array(
|
835 |
'comment_author' => $details['comment_author'],
|
@@ -852,24 +809,10 @@ function PostToDB($details, $isReply, $customImageField, $postmodifiers) {
|
|
852 |
}
|
853 |
|
854 |
if ($post_ID) {
|
855 |
-
if ($customImageField) {
|
856 |
-
DebugEcho("Saving custom image fields");
|
857 |
-
//DebugDump($details['customImages']);
|
858 |
-
|
859 |
-
if (count($details['customImages']) > 0) {
|
860 |
-
$imageField = 1;
|
861 |
-
foreach ($details['customImages'] as $image) {
|
862 |
-
add_post_meta($post_ID, 'image', $image);
|
863 |
-
DebugEcho("Saving custom image 'image$imageField'");
|
864 |
-
$imageField++;
|
865 |
-
}
|
866 |
-
}
|
867 |
-
}
|
868 |
-
|
869 |
$postmodifiers->apply($post_ID);
|
870 |
-
|
871 |
do_action('postie_post_after', $details);
|
872 |
}
|
|
|
873 |
return $post_ID;
|
874 |
}
|
875 |
|
@@ -891,385 +834,247 @@ function isBannedFileName($filename, $bannedFiles) {
|
|
891 |
return false;
|
892 |
}
|
893 |
|
894 |
-
function
|
895 |
-
|
896 |
-
//global $charset, $encoding;
|
897 |
-
DebugEcho('GetContent: ---- start');
|
898 |
$meta_return = '';
|
899 |
-
if (
|
900 |
-
|
901 |
-
|
|
|
|
|
|
|
|
|
|
|
902 |
}
|
|
|
|
|
903 |
|
904 |
-
|
|
|
905 |
|
906 |
-
|
907 |
-
|
908 |
-
if (isBannedFileName($part->ctype_parameters['name'], $config['banned_files_list'])) {
|
909 |
-
DebugEcho("GetContent: found banned filename");
|
910 |
-
return NULL;
|
911 |
-
}
|
912 |
}
|
913 |
-
|
914 |
-
|
915 |
-
|
916 |
-
|
917 |
-
|
918 |
-
DebugEcho("GetContent: decoding application/octet-stream");
|
919 |
-
$mimeDecodedEmail = DecodeMIMEMail($part->body);
|
920 |
-
filter_PreferedText($mimeDecodedEmail, $config['prefer_text_type']);
|
921 |
-
foreach ($mimeDecodedEmail->parts as $section) {
|
922 |
-
$meta_return .= GetContent($section, $attachments, $post_id, $poster, $config);
|
923 |
-
}
|
924 |
-
}
|
925 |
}
|
926 |
|
927 |
-
|
928 |
-
|
929 |
-
|
930 |
-
|
931 |
-
|
932 |
-
|
933 |
-
$meta_return .= GetContent($section, $attachments, $post_id, $poster, $config);
|
934 |
-
}
|
935 |
-
} else {
|
936 |
-
$filename = "";
|
937 |
-
if (property_exists($part, 'ctype_parameters') && is_array($part->ctype_parameters) && array_key_exists('name', $part->ctype_parameters)) {
|
938 |
-
$filename = $part->ctype_parameters['name'];
|
939 |
-
} elseif (property_exists($part, 'd_parameters') && is_array($part->d_parameters) && array_key_exists('filename', $part->d_parameters)) {
|
940 |
-
$filename = $part->d_parameters['filename'];
|
941 |
-
}
|
942 |
-
DebugEcho("GetContent: pre sanitize file name '$filename'");
|
943 |
-
//DebugDump($part);
|
944 |
-
$filename = sanitize_file_name($filename);
|
945 |
-
$fileext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
946 |
|
947 |
-
DebugEcho("GetContent: file name '$filename'");
|
948 |
-
DebugEcho("GetContent: extension '$fileext'");
|
949 |
|
950 |
-
|
951 |
-
|
952 |
|
953 |
-
|
954 |
-
|
955 |
-
|
956 |
-
|
957 |
-
$mimetype_secondary = strtolower($part->ctype_secondary);
|
958 |
-
}
|
959 |
|
960 |
-
|
961 |
-
|
962 |
-
|
963 |
-
DebugEcho("GetContent: secondary lookup found " . $typeinfo['type']);
|
964 |
-
$mimeparts = explode('/', strtolower($typeinfo['type']));
|
965 |
-
$mimetype_primary = $mimeparts[0];
|
966 |
-
$mimetype_secondary = $mimeparts[1];
|
967 |
-
} else {
|
968 |
-
DebugEcho("GetContent: secondary lookup failed, checking configured extensions");
|
969 |
-
if (in_array($fileext, $config['audiotypes'])) {
|
970 |
-
DebugEcho("GetContent: found audio extension");
|
971 |
-
$mimetype_primary = 'audio';
|
972 |
-
$mimetype_secondary = $fileext;
|
973 |
-
} elseif (in_array($fileext, array_merge($config['video1types'], $config['video2types']))) {
|
974 |
-
DebugEcho("GetContent: found video extension");
|
975 |
-
$mimetype_primary = 'video';
|
976 |
-
$mimetype_secondary = $fileext;
|
977 |
-
} else {
|
978 |
-
DebugEcho("GetContent: found no extension");
|
979 |
}
|
|
|
|
|
980 |
}
|
981 |
|
982 |
-
|
983 |
-
|
984 |
-
switch ($mimetype_primary) {
|
985 |
-
case 'multipart':
|
986 |
-
DebugEcho("GetContent: multipart: " . count($part->parts));
|
987 |
-
//DebugDump($part);
|
988 |
-
filter_PreferedText($part, $config['prefer_text_type']);
|
989 |
-
foreach ($part->parts as $section) {
|
990 |
-
//DebugDump($section->headers);
|
991 |
-
$meta_return .= GetContent($section, $attachments, $post_id, $poster, $config);
|
992 |
-
}
|
993 |
-
break;
|
994 |
|
995 |
-
|
996 |
-
|
997 |
-
|
|
|
|
|
|
|
998 |
|
999 |
-
|
1000 |
-
|
1001 |
-
$charset = $part->ctype_parameters['charset'];
|
1002 |
-
DebugEcho("GetContent: text charset: $charset");
|
1003 |
-
}
|
1004 |
|
1005 |
-
|
1006 |
-
|
1007 |
-
$encoding = $part->headers['content-transfer-encoding'];
|
1008 |
-
DebugEcho("GetContent: text encoding: $encoding");
|
1009 |
-
}
|
1010 |
|
1011 |
-
|
1012 |
-
|
1013 |
-
|
1014 |
-
|
1015 |
-
|
1016 |
-
}
|
1017 |
-
//DebugDump($part);
|
1018 |
-
}
|
1019 |
-
if (array_key_exists('disposition', $part) && $part->disposition == 'attachment') {
|
1020 |
-
DebugEcho("GetContent: text Attachement: $filename");
|
1021 |
-
if (!preg_match('/ATT\d\d\d\d\d.txt/i', $filename)) {
|
1022 |
-
$file_id = postie_media_handle_upload($part, $post_id, $poster, $config['generate_thumbnails']);
|
1023 |
-
if (!is_wp_error($file_id)) {
|
1024 |
-
$file = wp_get_attachment_url($file_id);
|
1025 |
-
$icon = chooseAttachmentIcon($file, $mimetype_primary, $mimetype_secondary, $config['icon_set'], $config['icon_size']);
|
1026 |
-
$attachments["html"][$filename] = "<a href='$file'>" . $icon . $filename . '</a>' . "\n";
|
1027 |
-
DebugEcho("GetContent: text attachment: adding '$filename'");
|
1028 |
-
} else {
|
1029 |
-
EchoError($file_id->get_error_message());
|
1030 |
-
}
|
1031 |
-
} else {
|
1032 |
-
DebugEcho("GetContent: text attachment: skipping '$filename'");
|
1033 |
-
}
|
1034 |
-
} else {
|
1035 |
-
|
1036 |
-
//go through each sub-section
|
1037 |
-
if ($mimetype_secondary == 'enriched') {
|
1038 |
-
//convert enriched text to HTML
|
1039 |
-
DebugEcho("GetContent: enriched");
|
1040 |
-
$meta_return .= filter_Etf2HTML($part->body) . "\n";
|
1041 |
-
} elseif ($mimetype_secondary == 'html') {
|
1042 |
-
//strip excess HTML
|
1043 |
-
DebugEcho("GetContent: html");
|
1044 |
-
$meta_return .= filter_CleanHtml($part->body) . "\n";
|
1045 |
-
} elseif ($mimetype_secondary == 'plain') {
|
1046 |
-
DebugEcho("GetContent: plain text");
|
1047 |
-
//DebugDump($part);
|
1048 |
-
|
1049 |
-
DebugEcho("GetContent: body text");
|
1050 |
-
if ($config['allow_html_in_body']) {
|
1051 |
-
DebugEcho("GetContent: html allowed");
|
1052 |
-
$meta_return .= $part->body;
|
1053 |
-
//$meta_return = "<div>$meta_return</div>\n";
|
1054 |
-
} else {
|
1055 |
-
DebugEcho("GetContent: html not allowed (htmlentities)");
|
1056 |
-
$meta_return .= htmlentities($part->body);
|
1057 |
-
}
|
1058 |
-
$meta_return = filter_StripPGP($meta_return);
|
1059 |
-
//DebugEcho("meta return: $meta_return");
|
1060 |
-
} else {
|
1061 |
-
DebugEcho("GetContent: text Attachement wo disposition: $filename");
|
1062 |
-
$file_id = postie_media_handle_upload($part, $post_id, $poster);
|
1063 |
-
if (!is_wp_error($file_id)) {
|
1064 |
-
$file = wp_get_attachment_url($file_id);
|
1065 |
-
$icon = chooseAttachmentIcon($file, $mimetype_primary, $mimetype_secondary, $config['icon_set'], $config['icon_size']);
|
1066 |
-
$attachments["html"][$filename] = "<a href='$file'>" . $icon . $filename . '</a>' . "\n";
|
1067 |
-
} else {
|
1068 |
-
EchoError($file_id->get_error_message());
|
1069 |
-
}
|
1070 |
-
}
|
1071 |
-
}
|
1072 |
break;
|
1073 |
|
1074 |
case 'image':
|
1075 |
-
DebugEcho("
|
1076 |
-
$
|
1077 |
-
if (!is_wp_error($file_id)) {
|
1078 |
-
$addimage = true;
|
1079 |
-
//set the first image we come across as the featured image
|
1080 |
-
if ($config['featured_image'] && !has_post_thumbnail($post_id)) {
|
1081 |
-
DebugEcho("GetContent: featured image: $file_id");
|
1082 |
-
set_post_thumbnail($post_id, $file_id);
|
1083 |
-
|
1084 |
-
//optionally skip adding the featured imagea to the post
|
1085 |
-
$addimage = $config['include_featured_image'] || $config['prefer_text_type'] == 'html';
|
1086 |
-
}
|
1087 |
-
|
1088 |
-
if ($addimage) {
|
1089 |
-
DebugEcho("GetContent: adding image: $file_id");
|
1090 |
-
$cid = "";
|
1091 |
-
if (array_key_exists('content-id', $part->headers)) {
|
1092 |
-
$cid = trim($part->headers["content-id"], "<>");
|
1093 |
-
DebugEcho("GetContent: found cid: $cid");
|
1094 |
-
}
|
1095 |
-
|
1096 |
-
$attachments["html"][$filename] = parseTemplate($file_id, $mimetype_primary, $config['imagetemplate'], $filename);
|
1097 |
-
if (!empty($cid)) {
|
1098 |
-
$file = wp_get_attachment_url($file_id);
|
1099 |
-
$attachments["cids"][$cid] = array($file, count($attachments["html"]) - 1);
|
1100 |
-
DebugEcho("GetContent: CID Attachement: $cid");
|
1101 |
-
}
|
1102 |
-
} else {
|
1103 |
-
DebugEcho("Skipping image $filename as it is the featured image");
|
1104 |
-
}
|
1105 |
-
} else {
|
1106 |
-
EchoError("image error: " . $file_id->get_error_message());
|
1107 |
-
}
|
1108 |
break;
|
1109 |
|
1110 |
case 'audio':
|
1111 |
-
|
1112 |
-
|
1113 |
-
|
1114 |
-
|
1115 |
-
$file = wp_get_attachment_url($file_id);
|
1116 |
-
$cid = "";
|
1117 |
-
if (array_key_exists('content-id', $part->headers)) {
|
1118 |
-
$cid = trim($part->headers["content-id"], "<>");
|
1119 |
-
DebugEcho("GetContent: audio Attachement cid: $cid");
|
1120 |
-
}
|
1121 |
-
if (in_array($fileext, $config['audiotypes'])) {
|
1122 |
-
DebugEcho("GetContent: using audio template: $mimetype_secondary");
|
1123 |
-
$audioTemplate = $config['audiotemplate'];
|
1124 |
-
} else {
|
1125 |
-
DebugEcho("GetContent: using default audio template: $mimetype_secondary");
|
1126 |
-
$icon = chooseAttachmentIcon($file, $mimetype_primary, $mimetype_secondary, $config['icon_set'], $config['icon_size']);
|
1127 |
-
$audioTemplate = '<a href="{FILELINK}">' . $icon . '{FILENAME}</a>';
|
1128 |
-
}
|
1129 |
-
$attachments["html"][$filename] = parseTemplate($file_id, $mimetype_primary, $audioTemplate, $filename);
|
1130 |
} else {
|
1131 |
-
|
|
|
|
|
1132 |
}
|
|
|
1133 |
break;
|
1134 |
|
1135 |
case 'video':
|
1136 |
-
DebugEcho("
|
1137 |
-
|
1138 |
-
|
1139 |
-
$
|
1140 |
-
|
1141 |
-
|
1142 |
-
|
1143 |
-
DebugEcho("GetContent: video Attachement cid: $cid");
|
1144 |
-
}
|
1145 |
-
//DebugDump($part);
|
1146 |
-
if (in_array($fileext, $config['video1types'])) {
|
1147 |
-
DebugEcho("GetContent: using video1 template: $fileext");
|
1148 |
-
$videoTemplate = $config['video1template'];
|
1149 |
-
} elseif (in_array($fileext, $config['video2types'])) {
|
1150 |
-
DebugEcho("GetContent: using video2 template: $fileext");
|
1151 |
-
$videoTemplate = $config['video2template'];
|
1152 |
-
} else {
|
1153 |
-
DebugEcho("GetContent: using default template: $fileext");
|
1154 |
-
$icon = chooseAttachmentIcon($file, $mimetype_primary, $mimetype_secondary, $config['icon_set'], $config['icon_size']);
|
1155 |
-
$videoTemplate = '<a href="{FILELINK}">' . $icon . '{FILENAME}</a>';
|
1156 |
-
}
|
1157 |
-
$attachments["html"][$filename] = parseTemplate($file_id, $mimetype_primary, $videoTemplate, $filename);
|
1158 |
-
//echo "videoTemplate = $videoTemplate\n";
|
1159 |
} else {
|
1160 |
-
|
|
|
|
|
1161 |
}
|
|
|
1162 |
break;
|
1163 |
|
1164 |
-
default:
|
1165 |
-
DebugEcho("
|
1166 |
-
|
1167 |
-
|
1168 |
-
if ($mimetype_secondary == 'pgp-signature') {
|
1169 |
-
DebugEcho("GetContent: found pgp-signature - done");
|
1170 |
-
break;
|
1171 |
-
}
|
1172 |
-
$file_id = postie_media_handle_upload($part, $post_id, $poster, $config['generate_thumbnails']);
|
1173 |
-
if (!is_wp_error($file_id)) {
|
1174 |
-
$file = wp_get_attachment_url($file_id);
|
1175 |
-
DebugEcho("GetContent: uploaded $file_id ($file)");
|
1176 |
-
$icon = chooseAttachmentIcon($file, $mimetype_primary, $mimetype_secondary, $config['icon_set'], $config['icon_size']);
|
1177 |
-
DebugEcho("GetContent: default: $icon $filename");
|
1178 |
-
$attachments["html"][$filename] = parseTemplate($file_id, $mimetype_primary, $config['generaltemplate'], $filename, $icon);
|
1179 |
-
if (array_key_exists('content-id', $part->headers)) {
|
1180 |
-
$cid = trim($part->headers["content-id"], "<>");
|
1181 |
-
if ($cid) {
|
1182 |
-
$attachments["cids"][$cid] = array($file, count($attachments["html"]) - 1);
|
1183 |
-
}
|
1184 |
-
} else {
|
1185 |
-
DebugEcho("GetContent: No content-id");
|
1186 |
-
}
|
1187 |
-
} else {
|
1188 |
-
EchoError($file_id->get_error_message());
|
1189 |
-
}
|
1190 |
-
} else {
|
1191 |
-
EchoError("$filename has an unsupported MIME type $mimetype_primary and was not added.");
|
1192 |
-
DebugEcho("GetContent: Not in supported filetype list: '$mimetype_primary'");
|
1193 |
-
DebugDump($config['supported_file_types']);
|
1194 |
-
}
|
1195 |
break;
|
1196 |
}
|
1197 |
}
|
1198 |
-
DebugEcho("GetContent: meta_return: " . substr($meta_return, 0, 500));
|
1199 |
-
DebugEcho("GetContent: ==== end");
|
1200 |
-
return $meta_return;
|
1201 |
}
|
1202 |
|
1203 |
-
function
|
1204 |
-
// Array of tags with opening and closing
|
1205 |
-
$tagArray['img'] = array('open' => '<img src="', 'close' => '">');
|
1206 |
-
$tagArray['b'] = array('open' => '<b>', 'close' => '</b>');
|
1207 |
-
$tagArray['i'] = array('open' => '<i>', 'close' => '</i>');
|
1208 |
-
$tagArray['u'] = array('open' => '<u>', 'close' => '</u>');
|
1209 |
-
$tagArray['url'] = array('open' => '<a href="', 'close' => '">\\1</a>');
|
1210 |
-
$tagArray['email'] = array('open' => '<a href="mailto:', 'close' => '">\\1</a>');
|
1211 |
-
$tagArray['url=(.*)'] = array('open' => '<a href="', 'close' => '">\\2</a>');
|
1212 |
-
$tagArray['email=(.*)'] = array('open' => '<a href="mailto:', 'close' => '">\\2</a>');
|
1213 |
-
$tagArray['color=(.*)'] = array('open' => '<font color="', 'close' => '">\\2</font>');
|
1214 |
-
$tagArray['size=(.*)'] = array('open' => '<font size="', 'close' => '">\\2</font>');
|
1215 |
-
$tagArray['font=(.*)'] = array('open' => '<font face="', 'close' => '">\\2</font>');
|
1216 |
-
// Array of tags with only one part
|
1217 |
-
$sTagArray['br'] = array('tag' => '<br>');
|
1218 |
-
$sTagArray['hr'] = array('tag' => '<hr>');
|
1219 |
|
1220 |
-
|
1221 |
-
$
|
1222 |
-
|
|
|
1223 |
}
|
1224 |
-
|
1225 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1226 |
}
|
1227 |
-
|
1228 |
-
|
1229 |
|
1230 |
-
|
1231 |
-
|
1232 |
-
|
1233 |
-
|
1234 |
|
1235 |
-
|
1236 |
-
|
1237 |
-
|
1238 |
-
|
1239 |
-
|
1240 |
-
|
1241 |
-
|
1242 |
-
|
1243 |
-
|
1244 |
-
|
1245 |
-
|
1246 |
-
|
1247 |
-
|
1248 |
-
'/<color>/',
|
1249 |
-
'/<\/color>/',
|
1250 |
-
'/<param>.+<\/param>/'
|
1251 |
-
);
|
1252 |
|
1253 |
-
|
1254 |
-
|
1255 |
-
|
1256 |
-
|
1257 |
-
|
1258 |
-
|
1259 |
-
|
1260 |
-
|
1261 |
-
|
1262 |
-
|
1263 |
-
|
1264 |
-
|
1265 |
-
|
1266 |
-
|
1267 |
-
|
1268 |
-
''
|
1269 |
-
|
1270 |
-
|
1271 |
-
|
1272 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1273 |
}
|
1274 |
|
1275 |
// This function cleans up HTML in the email
|
@@ -1304,36 +1109,39 @@ function ValidatePoster(&$mimeDecodedEmail, $config) {
|
|
1304 |
extract($config);
|
1305 |
$poster = NULL;
|
1306 |
$from = "";
|
1307 |
-
if (
|
1308 |
-
|
1309 |
-
$from =
|
|
|
1310 |
DebugEcho("ValidatePoster: post postie_filter_email $from");
|
1311 |
|
1312 |
$toEmail = '';
|
1313 |
-
if (isset($mimeDecodedEmail
|
1314 |
-
|
|
|
1315 |
}
|
1316 |
|
1317 |
$replytoEmail = '';
|
1318 |
-
if (isset($mimeDecodedEmail
|
1319 |
-
|
|
|
1320 |
}
|
1321 |
|
1322 |
$from = apply_filters("postie_filter_email2", $from, $toEmail, $replytoEmail);
|
1323 |
DebugEcho("ValidatePoster: post postie_filter_email2 $from");
|
1324 |
} else {
|
1325 |
DebugEcho("No 'from' header found");
|
1326 |
-
DebugDump($mimeDecodedEmail
|
1327 |
}
|
1328 |
|
1329 |
-
if (
|
1330 |
-
$from = apply_filters("postie_filter_email3", $from, $mimeDecodedEmail
|
1331 |
DebugEcho("ValidatePoster: post postie_filter_email3 $from");
|
1332 |
}
|
1333 |
|
1334 |
$resentFrom = "";
|
1335 |
-
if (
|
1336 |
-
$resentFrom = RemoveExtraCharactersInEmailAddress(trim($mimeDecodedEmail->headers[
|
1337 |
}
|
1338 |
|
1339 |
//See if the email address is one of the special authorized ones
|
@@ -1360,11 +1168,11 @@ function ValidatePoster(&$mimeDecodedEmail, $config) {
|
|
1360 |
$user_ID = "";
|
1361 |
}
|
1362 |
}
|
1363 |
-
if (empty($user_ID) && ($turn_authorization_off || isEmailAddressAuthorized($from, $authorized_addresses) || isEmailAddressAuthorized($resentFrom, $authorized_addresses))) {
|
1364 |
-
DebugEcho("ValidatePoster: looking up default user $admin_username
|
1365 |
-
$user = get_user_by('login', $admin_username);
|
1366 |
if ($user === false) {
|
1367 |
-
EchoError("Your 'Default Poster' setting '$admin_username' is not a valid WordPress user (2)");
|
1368 |
$poster = 1;
|
1369 |
} else {
|
1370 |
$poster = $user->ID;
|
@@ -1377,9 +1185,9 @@ function ValidatePoster(&$mimeDecodedEmail, $config) {
|
|
1377 |
|
1378 |
if (!$poster) {
|
1379 |
EchoError('Invalid sender: ' . htmlentities($from) . "! Not adding email!");
|
1380 |
-
if ($forward_rejected_mail) {
|
1381 |
$admin_email = get_option("admin_email");
|
1382 |
-
if (MailToRecipients($mimeDecodedEmail, array($admin_email), $return_to_sender)) {
|
1383 |
EchoError("A copy of the message has been forwarded to the administrator.");
|
1384 |
} else {
|
1385 |
EchoError("The message was unable to be forwarded to the adminstrator.");
|
@@ -1407,7 +1215,7 @@ function ValidatePoster(&$mimeDecodedEmail, $config) {
|
|
1407 |
* @param string
|
1408 |
* @param string
|
1409 |
*/
|
1410 |
-
function filter_Start(
|
1411 |
$start = $config['message_start'];
|
1412 |
if (!empty($start)) {
|
1413 |
$pos = strpos($content, $start);
|
@@ -1417,6 +1225,7 @@ function filter_Start(&$content, $config) {
|
|
1417 |
DebugEcho("start filter $start");
|
1418 |
$content = substr($content, $pos + strlen($start), strlen($content));
|
1419 |
}
|
|
|
1420 |
}
|
1421 |
|
1422 |
/**
|
@@ -1425,11 +1234,12 @@ function filter_Start(&$content, $config) {
|
|
1425 |
* @param string
|
1426 |
* @param array - a list of patterns to determine if it is a sig block
|
1427 |
*/
|
1428 |
-
function filter_RemoveSignature(
|
|
|
1429 |
if ($config['drop_signature']) {
|
1430 |
if (empty($config['sig_pattern_list'])) {
|
1431 |
DebugEcho("filter_RemoveSignature: no sig_pattern_list");
|
1432 |
-
return;
|
1433 |
}
|
1434 |
//DebugEcho("looking for signature in: $content");
|
1435 |
|
@@ -1452,26 +1262,30 @@ function filter_RemoveSignature(&$content, $config) {
|
|
1452 |
break;
|
1453 |
}
|
1454 |
|
|
|
1455 |
$strcontent .= $line;
|
1456 |
}
|
1457 |
$content = $strcontent;
|
1458 |
}
|
|
|
|
|
1459 |
}
|
|
|
1460 |
}
|
1461 |
|
1462 |
function filter_RemoveSignatureWorker(&$html, $pattern) {
|
1463 |
$found = false;
|
1464 |
$matches = array();
|
1465 |
if (preg_match($pattern, trim($html->plaintext), $matches)) {
|
1466 |
-
DebugEcho("filter_RemoveSignatureWorker: signature found in
|
1467 |
-
DebugDump($matches);
|
1468 |
$found = true;
|
1469 |
$i = stripos($html->innertext, $matches[1]);
|
1470 |
$presig = substr($html->innertext, 0, $i);
|
1471 |
-
DebugEcho("filter_RemoveSignatureWorker sig new text
|
1472 |
$html->innertext = $presig;
|
1473 |
} else {
|
1474 |
-
|
1475 |
//DebugDump($matches);
|
1476 |
}
|
1477 |
|
@@ -1496,7 +1310,7 @@ function filter_RemoveSignatureWorker(&$html, $pattern) {
|
|
1496 |
* @param string
|
1497 |
* @param filter
|
1498 |
*/
|
1499 |
-
function filter_End(
|
1500 |
$end = $config['message_end'];
|
1501 |
if (!empty($end)) {
|
1502 |
$pos = strpos($content, $end);
|
@@ -1506,36 +1320,41 @@ function filter_End(&$content, $config) {
|
|
1506 |
DebugEcho("end filter: $end");
|
1507 |
$content = substr($content, 0, $pos);
|
1508 |
}
|
|
|
1509 |
}
|
1510 |
|
1511 |
//filter content for new lines
|
1512 |
-
function filter_Newlines(
|
1513 |
if ($config['filternewlines']) {
|
1514 |
DebugEcho("filter_Newlines: filternewlines");
|
1515 |
$search = array(
|
|
|
1516 |
"/\r\n/",
|
1517 |
"/\n\n/",
|
1518 |
-
"/\r\n\r\n/",
|
1519 |
"/\r/",
|
1520 |
"/\n/"
|
1521 |
);
|
1522 |
$replace = array(
|
1523 |
-
|
1524 |
-
|
1525 |
'LINEBREAK',
|
1526 |
'LINEBREAK',
|
1527 |
'LINEBREAK'
|
1528 |
);
|
1529 |
|
1530 |
$result = preg_replace($search, $replace, $content);
|
|
|
1531 |
|
1532 |
-
DebugEcho("filter_Newlines: convertnewline: " . $config['convertnewline']);
|
1533 |
if ($config['convertnewline']) {
|
|
|
1534 |
$content = preg_replace('/(LINEBREAK)/', "<br />\n", $result);
|
|
|
1535 |
} else {
|
1536 |
$content = preg_replace('/(LINEBREAK)/', " ", $result);
|
|
|
1537 |
}
|
1538 |
}
|
|
|
1539 |
}
|
1540 |
|
1541 |
//strip pgp stuff
|
@@ -1551,67 +1370,6 @@ function filter_StripPGP($content) {
|
|
1551 |
return preg_replace($search, $replace, $content);
|
1552 |
}
|
1553 |
|
1554 |
-
function HandleMessageEncoding($contenttransferencoding, $charset, $body, $blogEncoding = 'utf-8', $dequote = true) {
|
1555 |
-
$charset = strtolower($charset);
|
1556 |
-
$contenttransferencoding = strtolower($contenttransferencoding);
|
1557 |
-
|
1558 |
-
DebugEcho("before HandleMessageEncoding");
|
1559 |
-
DebugEcho("email charset: $charset");
|
1560 |
-
DebugEcho("email encoding: $contenttransferencoding");
|
1561 |
-
//DebugDump($body);
|
1562 |
-
|
1563 |
-
if ($contenttransferencoding == 'base64') {
|
1564 |
-
DebugEcho("HandleMessageEncoding: base64 detected");
|
1565 |
-
$body = base64_decode($body);
|
1566 |
-
}
|
1567 |
-
if ($dequote && $contenttransferencoding == 'quoted-printable') {
|
1568 |
-
DebugEcho("quoted-printable detected");
|
1569 |
-
$body = quoted_printable_decode($body);
|
1570 |
-
//DebugEcho($body);
|
1571 |
-
}
|
1572 |
-
|
1573 |
-
DebugEcho("after HandleMessageEncoding");
|
1574 |
-
if (strtolower($charset) != strtolower($blogEncoding)) {
|
1575 |
-
if (!empty($charset) && strtolower($charset) != 'default' && strtolower($charset) != $blogEncoding) {
|
1576 |
-
DebugEcho("converting from $charset to $blogEncoding");
|
1577 |
-
//DebugEcho("before: $body");
|
1578 |
-
$body = iconv($charset, $blogEncoding . '//IGNORE//TRANSLIT', $body);
|
1579 |
-
//DebugEcho("after: $body");
|
1580 |
-
} else {
|
1581 |
-
$body = iconv($blogEncoding, $blogEncoding . '//IGNORE//TRANSLIT', $body);
|
1582 |
-
}
|
1583 |
-
}
|
1584 |
-
return $body;
|
1585 |
-
}
|
1586 |
-
|
1587 |
-
/**
|
1588 |
-
* This function handles decoding base64 if needed
|
1589 |
-
*/
|
1590 |
-
function DecodeBase64Part(&$part) {
|
1591 |
-
if (array_key_exists('content-transfer-encoding', $part->headers)) {
|
1592 |
-
if (strtolower($part->headers['content-transfer-encoding']) == 'base64') {
|
1593 |
-
DebugEcho("DecodeBase64Part: base64 detected");
|
1594 |
-
//DebugDump($part);
|
1595 |
-
$decoded = base64_decode($part->body);
|
1596 |
-
if (isset($part->disposition) && $part->disposition == 'attachment') {
|
1597 |
-
$part->body = $decoded;
|
1598 |
-
} else if (property_exists($part, 'ctype_parameters') && is_array($part->ctype_parameters) && array_key_exists('charset', $part->ctype_parameters)) {
|
1599 |
-
$charset = strtolower($part->ctype_parameters['charset']);
|
1600 |
-
if ($charset != 'utf-8') {
|
1601 |
-
DebugEcho("converted from: " . $charset);
|
1602 |
-
$part->body = iconv($charset, 'UTF-8//TRANSLIT', $decoded);
|
1603 |
-
$part->ctype_parameters['charset'] = 'default'; //so we don't double decode
|
1604 |
-
} else {
|
1605 |
-
$part->body = $decoded;
|
1606 |
-
}
|
1607 |
-
} else {
|
1608 |
-
$part->body = $decoded;
|
1609 |
-
}
|
1610 |
-
$part->headers['content-transfer-encoding'] = '';
|
1611 |
-
}
|
1612 |
-
}
|
1613 |
-
}
|
1614 |
-
|
1615 |
/**
|
1616 |
* Checks for the comments tag
|
1617 |
* @return boolean
|
@@ -1646,6 +1404,7 @@ function tag_Status(&$content, $currentstatus) {
|
|
1646 |
}
|
1647 |
|
1648 |
function tag_Delay(&$content, $message_date = NULL, $offset = 0) {
|
|
|
1649 |
$delay = 0;
|
1650 |
$matches = array();
|
1651 |
if (preg_match("/delay:(-?[0-9dhm]+)/i", $content, $matches) && trim($matches[1])) {
|
@@ -1681,11 +1440,13 @@ function tag_Delay(&$content, $message_date = NULL, $offset = 0) {
|
|
1681 |
}
|
1682 |
$dateInSeconds = strtotime($message_date);
|
1683 |
}
|
|
|
1684 |
$dateInSeconds += $delay;
|
|
|
1685 |
|
1686 |
$post_date = gmdate('Y-m-d H:i:s', $dateInSeconds + ($offset * 3600));
|
1687 |
$post_date_gmt = gmdate('Y-m-d H:i:s', $dateInSeconds);
|
1688 |
-
DebugEcho("tag_Delay: post date: $post_date / $post_date_gmt (gmt)");
|
1689 |
|
1690 |
return array($post_date, $post_date_gmt, $delay);
|
1691 |
}
|
@@ -1696,11 +1457,11 @@ function tag_Delay(&$content, $message_date = NULL, $offset = 0) {
|
|
1696 |
function tag_Subject($content, $defaultTitle) {
|
1697 |
DebugEcho("tag_Subject: Looking for subject in email body");
|
1698 |
if (substr($content, 0, 1) != "#") {
|
1699 |
-
DebugEcho("tag_Subject: No subject found, using default [1]");
|
1700 |
return(array($defaultTitle, $content));
|
1701 |
}
|
|
|
1702 |
if (strtolower(substr($content, 1, 3)) != "img") {
|
1703 |
-
|
1704 |
$subjectEndIndex = strpos($content, "#", 1);
|
1705 |
if (!$subjectEndIndex > 0) {
|
1706 |
DebugEcho("tag_Subject: No subject found, using default [2]");
|
@@ -1715,27 +1476,6 @@ function tag_Subject($content, $defaultTitle) {
|
|
1715 |
}
|
1716 |
}
|
1717 |
|
1718 |
-
/**
|
1719 |
-
* This method sorts thru the mime parts of the message. It is looking for files labeled - "applefile" - current
|
1720 |
-
* this part of the file attachment is not supported
|
1721 |
-
* @param object
|
1722 |
-
*/
|
1723 |
-
function filter_AppleFile(&$mimeDecodedEmail) {
|
1724 |
-
$newParts = array();
|
1725 |
-
$found = false;
|
1726 |
-
for ($i = 0; $i < count($mimeDecodedEmail->parts); $i++) {
|
1727 |
-
if ($mimeDecodedEmail->parts[$i]->ctype_secondary == "applefile") {
|
1728 |
-
$found = true;
|
1729 |
-
DebugEcho("Removing 'applefile'");
|
1730 |
-
} else {
|
1731 |
-
$newParts[] = $mimeDecodedEmail->parts[$i];
|
1732 |
-
}
|
1733 |
-
}
|
1734 |
-
if ($found && $newParts) {
|
1735 |
-
$mimeDecodedEmail->parts = $newParts; //This is now the filtered list of just the preferred type.
|
1736 |
-
}
|
1737 |
-
}
|
1738 |
-
|
1739 |
function postie_media_handle_upload($part, $post_id, $poster, $generate_thubnails = true, $mimetype_primary = null, $mimetype_secondary = null) {
|
1740 |
$post_data = array();
|
1741 |
|
@@ -1743,7 +1483,7 @@ function postie_media_handle_upload($part, $post_id, $poster, $generate_thubnail
|
|
1743 |
if ($tmpFile !== false) {
|
1744 |
$fp = fopen($tmpFile, 'w');
|
1745 |
if ($fp) {
|
1746 |
-
fwrite($fp, $part
|
1747 |
fclose($fp);
|
1748 |
} else {
|
1749 |
EchoError("postie_media_handle_upload: Could not write to temp file: '$tmpFile' ");
|
@@ -1752,27 +1492,12 @@ function postie_media_handle_upload($part, $post_id, $poster, $generate_thubnail
|
|
1752 |
EchoError("postie_media_handle_upload: Could not create temp file in " . get_temp_dir());
|
1753 |
}
|
1754 |
|
1755 |
-
//special case to deal with older png implementations
|
1756 |
$namecs = "";
|
1757 |
-
if (property_exists($part, "ctype_secondary")) {
|
1758 |
-
$namecs = strtolower($part->ctype_secondary);
|
1759 |
-
if ($namecs == 'x-png') {
|
1760 |
-
DebugEcho("postie_media_handle_upload: x-png found, renamed to png");
|
1761 |
-
$part->ctype_secondary = 'png';
|
1762 |
-
}
|
1763 |
-
}
|
1764 |
|
1765 |
$name = 'postie-media.' . $namecs;
|
1766 |
-
|
1767 |
-
|
1768 |
-
|
1769 |
-
}
|
1770 |
-
if (array_key_exists('filename', $part->ctype_parameters) && $part->ctype_parameters['filename'] != '') {
|
1771 |
-
$name = $part->ctype_parameters['filename'];
|
1772 |
-
}
|
1773 |
-
}
|
1774 |
-
if (property_exists($part, 'd_parameters') && is_array($part->d_parameters) && array_key_exists('filename', $part->d_parameters) && $part->d_parameters['filename'] != '') {
|
1775 |
-
$name = $part->d_parameters['filename'];
|
1776 |
}
|
1777 |
DebugEcho("pre-sanitize name: $name, size: " . filesize($tmpFile));
|
1778 |
$name = sanitize_file_name($name);
|
@@ -1797,13 +1522,12 @@ function postie_media_handle_upload($part, $post_id, $poster, $generate_thubnail
|
|
1797 |
$time = current_time('mysql');
|
1798 |
$post = get_post($post_id);
|
1799 |
if (substr($post->post_date, 0, 4) > 0) {
|
1800 |
-
DebugEcho("using post date");
|
1801 |
$time = $post->post_date;
|
1802 |
}
|
1803 |
|
1804 |
$file = postie_handle_upload($the_file, $time, $mimetype_primary, $mimetype_secondary);
|
1805 |
|
1806 |
-
|
1807 |
if (isset($file['error'])) {
|
1808 |
DebugDump($file['error']);
|
1809 |
return new WP_Error('upload_error', $file['error']);
|
@@ -1819,15 +1543,15 @@ function postie_media_handle_upload($part, $post_id, $poster, $generate_thubnail
|
|
1819 |
if (file_exists(ABSPATH . '/wp-admin/includes/image.php')) {
|
1820 |
include_once(ABSPATH . '/wp-admin/includes/image.php');
|
1821 |
include_once(ABSPATH . '/wp-admin/includes/media.php');
|
1822 |
-
DebugEcho("reading metadata");
|
1823 |
if ($image_meta = @wp_read_image_metadata($filename)) {
|
1824 |
if (trim($image_meta['title'])) {
|
1825 |
$title = $image_meta['title'];
|
1826 |
-
DebugEcho("Using metadata title: $title");
|
1827 |
}
|
1828 |
if (trim($image_meta['caption'])) {
|
1829 |
$content = $image_meta['caption'];
|
1830 |
-
DebugEcho("Using metadata caption: $content");
|
1831 |
}
|
1832 |
}
|
1833 |
}
|
@@ -1844,21 +1568,21 @@ function postie_media_handle_upload($part, $post_id, $poster, $generate_thubnail
|
|
1844 |
), $post_data);
|
1845 |
|
1846 |
// Save the data
|
1847 |
-
DebugEcho("before wp_insert_attachment");
|
1848 |
$id = wp_insert_attachment($attachment, $filename, $post_id);
|
1849 |
-
DebugEcho("after wp_insert_attachment:
|
1850 |
|
1851 |
if (!is_wp_error($id)) {
|
1852 |
do_action('postie_file_added', $post_id, $id, $file);
|
1853 |
|
1854 |
if ($generate_thubnails) {
|
1855 |
$amd = wp_generate_attachment_metadata($id, $filename);
|
1856 |
-
DebugEcho("wp_generate_attachment_metadata");
|
1857 |
//DebugDump($amd);
|
1858 |
wp_update_attachment_metadata($id, $amd);
|
1859 |
-
DebugEcho("wp_update_attachment_metadata complete");
|
1860 |
} else {
|
1861 |
-
DebugEcho("thumbnail generation disabled");
|
1862 |
}
|
1863 |
} else {
|
1864 |
EchoError("There was an error adding the attachement: " . $id->get_error_message());
|
@@ -1898,12 +1622,6 @@ function postie_handle_upload(&$file, $time = null, $mimetype_primary = null, $m
|
|
1898 |
|
1899 |
$file = apply_filters('wp_handle_upload_prefilter', $file);
|
1900 |
|
1901 |
-
// You may define your own function and pass the name in $overrides['upload_error_handler']
|
1902 |
-
$upload_error_handler = 'wp_handle_upload_error';
|
1903 |
-
|
1904 |
-
// $_POST['action'] must be set and its value must equal $overrides['action'] or this:
|
1905 |
-
$action = 'wp_handle_upload';
|
1906 |
-
|
1907 |
// Courtesy of php.net, the strings that describe the error indicated in $_FILES[{form field}]['error'].
|
1908 |
$upload_error_strings = array(false,
|
1909 |
__("The uploaded file exceeds the <code>upload_max_filesize</code> directive in <code>php.ini</code>.", 'postie'),
|
@@ -1916,29 +1634,29 @@ function postie_handle_upload(&$file, $time = null, $mimetype_primary = null, $m
|
|
1916 |
|
1917 |
// A successful upload will pass this test. It makes no sense to override this one.
|
1918 |
if ($file['error'] > 0) {
|
1919 |
-
return
|
1920 |
}
|
1921 |
// A file with a valid mime type
|
1922 |
if (empty($file['type'])) {
|
1923 |
-
return
|
1924 |
}
|
1925 |
// A non-empty file will pass this test.
|
1926 |
if (!($file['size'] > 0 )) {
|
1927 |
-
return
|
1928 |
}
|
1929 |
// A properly uploaded file will pass this test. There should be no reason to override this one.
|
1930 |
if (!file_exists($file['tmp_name'])) {
|
1931 |
-
return
|
1932 |
}
|
1933 |
|
1934 |
$mimetype = $file['type'];
|
1935 |
-
$ext = $wp_filetype['ext'];
|
1936 |
|
1937 |
if (empty($ext)) {
|
1938 |
-
$ext = ltrim(strrchr($file['name'], '.'), '.');
|
1939 |
}
|
1940 |
if (empty($ext) && !empty($mimetype_secondary)) {
|
1941 |
-
$ext = $mimetype_secondary;
|
1942 |
$file['name'] = $file['name'] . ".$ext";
|
1943 |
}
|
1944 |
|
@@ -1946,13 +1664,13 @@ function postie_handle_upload(&$file, $time = null, $mimetype_primary = null, $m
|
|
1946 |
|
1947 |
if ((empty($mimetype) && empty($ext)) && !current_user_can('unfiltered_upload')) {
|
1948 |
DebugEcho("postie_handle_upload: no type/ext & user restricted");
|
1949 |
-
return
|
1950 |
}
|
1951 |
|
1952 |
// A writable uploads dir will pass this test. Again, there's no point overriding this one.
|
1953 |
if (!( ( $uploads = wp_upload_dir($time) ) && false === $uploads['error'] )) {
|
1954 |
DebugEcho("postie_handle_upload: directory not writable");
|
1955 |
-
return
|
1956 |
}
|
1957 |
// fix filename (encode non-standard characters)
|
1958 |
$file['name'] = filename_fix($file['name']);
|
@@ -1969,7 +1687,7 @@ function postie_handle_upload(&$file, $time = null, $mimetype_primary = null, $m
|
|
1969 |
DebugEcho("new file: $new_file");
|
1970 |
//DebugDump($file);
|
1971 |
//DebugDump($uploads);
|
1972 |
-
return
|
1973 |
} else {
|
1974 |
DebugEcho("upload: rename to $new_file succeeded");
|
1975 |
}
|
@@ -1997,55 +1715,6 @@ function filename_fix($filename) {
|
|
1997 |
return str_replace('%', '', urlencode($filename));
|
1998 |
}
|
1999 |
|
2000 |
-
/**
|
2001 |
-
* This method sorts thru the mime parts of the message. It is looking for a certain type of text attachment. If
|
2002 |
-
* that type is present it filters out all other text types. If it is not - then nothing is done
|
2003 |
-
* @param object
|
2004 |
-
*/
|
2005 |
-
function filter_PreferedText($mimeDecodedEmail, $preferTextType) {
|
2006 |
-
DebugEcho("filter_PreferedText: begin " . count($mimeDecodedEmail->parts));
|
2007 |
-
$newParts = array();
|
2008 |
-
|
2009 |
-
for ($i = 0; $i < count($mimeDecodedEmail->parts); $i++) {
|
2010 |
-
if (!property_exists($mimeDecodedEmail->parts[$i], "ctype_primary")) {
|
2011 |
-
DebugEcho("filter_PreferedText: missing ctype_primary");
|
2012 |
-
//DebugDump($mimeDecodedEmail->parts[$i]);
|
2013 |
-
} else {
|
2014 |
-
DebugEcho("filter_PreferedText: part: $i " . $mimeDecodedEmail->parts[$i]->ctype_primary . "/" . $mimeDecodedEmail->parts[$i]->ctype_secondary);
|
2015 |
-
}
|
2016 |
-
if (array_key_exists('disposition', $mimeDecodedEmail->parts[$i]) && $mimeDecodedEmail->parts[$i]->disposition == 'attachment') {
|
2017 |
-
DebugEcho("filter_PreferedText: found disposition/attachment");
|
2018 |
-
$newParts[] = $mimeDecodedEmail->parts[$i];
|
2019 |
-
} else {
|
2020 |
-
if ($mimeDecodedEmail->parts[$i]->ctype_primary == "text") {
|
2021 |
-
$ctype = $mimeDecodedEmail->parts[$i]->ctype_secondary;
|
2022 |
-
if ($ctype == 'html' || $ctype == 'plain') {
|
2023 |
-
DebugEcho("filter_PreferedText: checking prefered type");
|
2024 |
-
if ($ctype == $preferTextType) {
|
2025 |
-
DebugEcho("filter_PreferedText: keeping: $ctype");
|
2026 |
-
DebugEcho(substr($mimeDecodedEmail->parts[$i]->body, 0, 500));
|
2027 |
-
$newParts[] = $mimeDecodedEmail->parts[$i];
|
2028 |
-
} else {
|
2029 |
-
DebugEcho("filter_PreferedText: removing: $ctype");
|
2030 |
-
}
|
2031 |
-
} else {
|
2032 |
-
DebugEcho("filter_PreferedText: keeping: {$mimeDecodedEmail->parts[$i]->ctype_primary}");
|
2033 |
-
$newParts[] = $mimeDecodedEmail->parts[$i];
|
2034 |
-
}
|
2035 |
-
} else {
|
2036 |
-
DebugEcho("filter_PreferedText: keeping: {$mimeDecodedEmail->parts[$i]->ctype_primary}");
|
2037 |
-
$newParts[] = $mimeDecodedEmail->parts[$i];
|
2038 |
-
}
|
2039 |
-
}
|
2040 |
-
}
|
2041 |
-
if ($newParts) {
|
2042 |
-
//This is now the filtered list of just the preferred type.
|
2043 |
-
DebugEcho(count($newParts) . " parts");
|
2044 |
-
$mimeDecodedEmail->parts = $newParts;
|
2045 |
-
}
|
2046 |
-
DebugEcho("filter_PreferedText: end");
|
2047 |
-
}
|
2048 |
-
|
2049 |
/**
|
2050 |
* This function can be used to send confirmation or rejection emails
|
2051 |
* It accepts an object containing the entire message
|
@@ -2068,7 +1737,7 @@ function MailToRecipients(&$mail_content, $recipients = array(), $returnToSender
|
|
2068 |
|
2069 |
$to = array_pop($recipients);
|
2070 |
|
2071 |
-
$from = trim($mail_content->headers[
|
2072 |
$subject = $mail_content->headers['subject'];
|
2073 |
if ($returnToSender) {
|
2074 |
DebugEcho("MailToRecipients: return to sender $returnToSender");
|
@@ -2091,12 +1760,12 @@ function MailToRecipients(&$mail_content, $recipients = array(), $returnToSender
|
|
2091 |
DebugEcho("MailToRecipients: sending reject mail");
|
2092 |
$alert_subject = $blogname . ": Unauthorized Post Attempt from $from";
|
2093 |
if (is_array($mail_content->ctype_parameters) && array_key_exists('boundary', $mail_content->ctype_parameters) && $mail_content->ctype_parameters['boundary']) {
|
2094 |
-
$boundary = $mail_content->ctype_parameters[
|
2095 |
} else {
|
2096 |
$boundary = uniqid("B_");
|
2097 |
}
|
2098 |
// Set sender details
|
2099 |
-
$headers.="Content-Type:multipart/alternative; boundary=\"$boundary\"\r\n";
|
2100 |
$message = "An unauthorized message has been sent to $blogname.\n";
|
2101 |
$message .= "Sender: $from\n";
|
2102 |
$message .= "Subject: $subject\n";
|
@@ -2117,13 +1786,13 @@ function MailToRecipients(&$mail_content, $recipients = array(), $returnToSender
|
|
2117 |
foreach ($mailparts as $part) {
|
2118 |
$mailtext .= "--$boundary\r\n";
|
2119 |
if (array_key_exists('content-type', $part->headers)) {
|
2120 |
-
$mailtext .= "Content-Type: " . $part->headers[
|
2121 |
}
|
2122 |
if (array_key_exists('content-transfer-encoding', $part->headers)) {
|
2123 |
-
$mailtext .= "Content-Transfer-Encoding: " . $part->headers[
|
2124 |
}
|
2125 |
if (array_key_exists('content-disposition', $part->headers)) {
|
2126 |
-
$mailtext .= "Content-Disposition: " . $part->headers[
|
2127 |
}
|
2128 |
$mailtext .= "\n";
|
2129 |
if (property_exists($part, 'body')) {
|
@@ -2141,34 +1810,6 @@ function MailToRecipients(&$mail_content, $recipients = array(), $returnToSender
|
|
2141 |
return true;
|
2142 |
}
|
2143 |
|
2144 |
-
/**
|
2145 |
-
* This function handles the basic mime decoding
|
2146 |
-
* @param string
|
2147 |
-
* @return array
|
2148 |
-
*/
|
2149 |
-
function DecodeMIMEMail($email) {
|
2150 |
-
$params = array();
|
2151 |
-
$params['include_bodies'] = true;
|
2152 |
-
$params['decode_bodies'] = false;
|
2153 |
-
$params['decode_headers'] = true;
|
2154 |
-
$params['input'] = $email;
|
2155 |
-
$md = new Mail_mimeDecode($email);
|
2156 |
-
$decoded = $md->decode($params);
|
2157 |
-
if (empty($decoded->parts)) {
|
2158 |
-
$decoded->parts = array(); // have an empty array at minimum, so that it is safe for "foreach"
|
2159 |
-
}
|
2160 |
-
return $decoded;
|
2161 |
-
}
|
2162 |
-
|
2163 |
-
/**
|
2164 |
-
* This is used for debugging the mimeDecodedEmail of the mail
|
2165 |
-
*/
|
2166 |
-
function DisplayMIMEPartTypes($mimeDecodedEmail) {
|
2167 |
-
foreach ($mimeDecodedEmail->parts as $part) {
|
2168 |
-
DebugEcho($part->ctype_primary . " / " . $part->ctype_secondary . "/ " . $part->headers['content-transfer-encoding']);
|
2169 |
-
}
|
2170 |
-
}
|
2171 |
-
|
2172 |
/**
|
2173 |
* This compares the current address to the list of authorized addresses
|
2174 |
* @param string - email address
|
@@ -2205,10 +1846,10 @@ function RemoveExtraCharactersInEmailAddress($address) {
|
|
2205 |
}
|
2206 |
|
2207 |
/**
|
2208 |
-
* This function gleans the name from
|
2209 |
* it just returns the username (everything before @)
|
2210 |
*/
|
2211 |
-
function
|
2212 |
$name = "";
|
2213 |
$matches = array();
|
2214 |
if (preg_match('/^([^<>]+)<([^<> ()]+)>$/', $address, $matches)) {
|
@@ -2291,8 +1932,7 @@ function chooseAttachmentIcon($file, $primary, $secondary, $iconSet = 'silver',
|
|
2291 |
}
|
2292 |
|
2293 |
function parseTemplate($fileid, $type, $template, $orig_filename, $icon = "") {
|
2294 |
-
DebugEcho("parseTemplate
|
2295 |
-
$size = 'medium';
|
2296 |
/* we check template for thumb, thumbnail, large, full and use that as
|
2297 |
size. If not found, we default to medium */
|
2298 |
if ($type == 'image') {
|
@@ -2302,22 +1942,21 @@ function parseTemplate($fileid, $type, $template, $orig_filename, $icon = "") {
|
|
2302 |
$heights = array();
|
2303 |
$img_src = array();
|
2304 |
|
2305 |
-
|
2306 |
|
2307 |
for ($i = 0; $i < count($sizes); $i++) {
|
2308 |
list( $img_src[$i], $widths[$i], $heights[$i] ) = image_downsize($fileid, $sizes[$i]);
|
2309 |
$hwstrings[$i] = image_hwstring($widths[$i], $heights[$i]);
|
2310 |
}
|
2311 |
-
DebugEcho('Sources');
|
2312 |
DebugDump($img_src);
|
2313 |
-
DebugEcho('Heights');
|
2314 |
DebugDump($heights);
|
2315 |
-
DebugEcho('Widths');
|
2316 |
DebugDump($widths);
|
2317 |
}
|
2318 |
|
2319 |
$attachment = get_post($fileid);
|
2320 |
-
$the_parent = get_post($attachment->post_parent);
|
2321 |
$uploadDir = wp_upload_dir();
|
2322 |
$fileName = basename($attachment->guid);
|
2323 |
$fileType = pathinfo($fileName, PATHINFO_EXTENSION);
|
@@ -2350,8 +1989,9 @@ function parseTemplate($fileid, $type, $template, $orig_filename, $icon = "") {
|
|
2350 |
$template = str_replace('{RELFILENAME}', $relFileName, $template);
|
2351 |
$template = str_replace('{ICON}', $icon, $template);
|
2352 |
$template = str_replace('{FILEID}', $fileid, $template);
|
|
|
2353 |
|
2354 |
-
DebugEcho("parseTemplate
|
2355 |
return $template . '<br />';
|
2356 |
}
|
2357 |
|
@@ -2361,62 +2001,49 @@ function parseTemplate($fileid, $type, $template, $orig_filename, $icon = "") {
|
|
2361 |
* @param string - text of post
|
2362 |
* @param array - array of HTML for images for post
|
2363 |
*/
|
2364 |
-
function filter_ReplaceImageCIDs(
|
2365 |
-
|
2366 |
-
|
2367 |
-
|
2368 |
-
|
2369 |
-
|
2370 |
-
|
2371 |
-
|
2372 |
-
|
2373 |
-
|
2374 |
-
$content =
|
2375 |
-
$
|
2376 |
} else {
|
2377 |
-
DebugEcho("
|
2378 |
}
|
2379 |
}
|
2380 |
-
|
2381 |
-
|
2382 |
-
|
2383 |
-
|
2384 |
-
|
2385 |
-
DebugEcho('dumping $attachments["html"]');
|
2386 |
-
DebugDump($attachments["html"]);
|
2387 |
-
DebugEcho('dumping $used[]');
|
2388 |
-
DebugDump($used);
|
2389 |
-
for ($i = 0; $i < count($attachments["html"]); $i++) {
|
2390 |
-
DebugEcho("looking for $i in used");
|
2391 |
-
if (!in_array($i, $used)) {
|
2392 |
-
DebugEcho("not found, adding {$att[$i]}");
|
2393 |
-
$html[] = $att[$i];
|
2394 |
-
} else {
|
2395 |
-
DebugEcho("found, skipping");
|
2396 |
-
}
|
2397 |
-
}
|
2398 |
|
2399 |
-
|
2400 |
-
|
2401 |
-
|
2402 |
-
|
2403 |
-
|
2404 |
-
|
2405 |
-
|
2406 |
-
|
2407 |
-
|
2408 |
-
$attachments["html"] = $html;
|
2409 |
-
//DebugDump($attachments);
|
2410 |
} else {
|
2411 |
-
DebugEcho(
|
2412 |
}
|
2413 |
}
|
|
|
2414 |
}
|
2415 |
|
2416 |
/**
|
2417 |
* This function handles replacing image place holder #img1# with the HTML for that image
|
2418 |
*/
|
2419 |
-
function filter_ReplaceImagePlaceHolders(
|
|
|
2420 |
if (!$config['custom_image_field']) {
|
2421 |
$startIndex = $config['start_image_count_at_zero'] ? 0 : 1;
|
2422 |
|
@@ -2425,137 +2052,106 @@ function filter_ReplaceImagePlaceHolders(&$content, $attachments, $config, $post
|
|
2425 |
'post_type' => 'attachment',
|
2426 |
'numberposts' => -1,
|
2427 |
'post_mime_type' => 'image',));
|
2428 |
-
DebugEcho("images in post: " . count($images));
|
2429 |
DebugDump($images);
|
2430 |
|
2431 |
-
if ((count($images) > 0) && $config['auto_gallery']) {
|
2432 |
-
$linktype = strtolower($config['auto_gallery_link']);
|
2433 |
-
DebugEcho("Auto gallery: link type $linktype");
|
2434 |
-
DebugFiltersFor('postie_gallery');
|
2435 |
-
if ($linktype == 'default') {
|
2436 |
-
$imageTemplate = apply_filters('postie_gallery', '[gallery]', $post_id);
|
2437 |
-
} else {
|
2438 |
-
$imageTemplate = apply_filters('postie_gallery', "[gallery link='$linktype']", $post_id);
|
2439 |
-
}
|
2440 |
-
DebugEcho("Auto gallery: template '$imageTemplate'");
|
2441 |
-
if ($config['images_append']) {
|
2442 |
-
$content .= "\n$imageTemplate";
|
2443 |
-
DebugEcho("Auto gallery: append");
|
2444 |
-
} else {
|
2445 |
-
$content = "$imageTemplate\n" . $content;
|
2446 |
-
DebugEcho("Auto gallery: prepend");
|
2447 |
-
}
|
2448 |
-
DebugFiltersFor('post_gallery'); //list gallery handler
|
2449 |
-
|
2450 |
-
return;
|
2451 |
-
} else {
|
2452 |
-
DebugEcho("Auto gallery: none");
|
2453 |
-
}
|
2454 |
-
|
2455 |
$pics = "";
|
2456 |
$i = 0;
|
2457 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2458 |
// looks for ' #img1# ' etc... and replaces with image
|
2459 |
-
$
|
2460 |
-
|
2461 |
-
DebugEcho("
|
2462 |
-
if (stristr($content, $
|
2463 |
-
|
2464 |
-
|
2465 |
-
|
2466 |
-
|
2467 |
-
|
2468 |
-
$
|
2469 |
-
if (
|
2470 |
-
|
2471 |
-
|
2472 |
-
|
|
|
|
|
|
|
2473 |
|
2474 |
-
|
2475 |
-
|
2476 |
-
|
2477 |
-
|
2478 |
|
2479 |
-
|
2480 |
-
|
2481 |
-
|
2482 |
-
|
2483 |
-
|
2484 |
-
|
2485 |
-
|
2486 |
-
|
2487 |
|
2488 |
-
|
2489 |
|
2490 |
-
|
2491 |
-
|
2492 |
-
} else {
|
2493 |
-
DebugEcho("No $img_placeholder_temp found");
|
2494 |
-
$imageTemplate = str_replace('{CAPTION}', '', $imageTemplate);
|
2495 |
-
/* if using the gallery shortcode, don't add pictures at all */
|
2496 |
-
if (!preg_match("/\[gallery[^\[]*\]/", $content, $matches)) {
|
2497 |
-
DebugEcho("imageTemplate: $imageTemplate");
|
2498 |
-
$pics .= $imageTemplate;
|
2499 |
-
} else {
|
2500 |
-
DebugEcho("gallery detected, not inserting images");
|
2501 |
}
|
2502 |
}
|
2503 |
$i++;
|
2504 |
}
|
2505 |
-
if ($autoadd_images) {
|
2506 |
-
if ($config['images_append']) {
|
2507 |
-
DebugEcho("auto adding images to end");
|
2508 |
-
$content .= $pics;
|
2509 |
-
} else {
|
2510 |
-
DebugEcho("auto adding images to beginning");
|
2511 |
-
$content = $pics . $content;
|
2512 |
-
}
|
2513 |
-
} else {
|
2514 |
-
DebugEcho("Not auto adding images");
|
2515 |
-
}
|
2516 |
} else {
|
2517 |
-
DebugEcho("Custom image field, not adding images");
|
2518 |
}
|
|
|
|
|
2519 |
}
|
2520 |
|
2521 |
/**
|
2522 |
* This function handles finding and setting the correct subject
|
2523 |
* @return array - (subject,content)
|
2524 |
*/
|
2525 |
-
function
|
2526 |
//assign the default title/subject
|
2527 |
-
if (
|
2528 |
-
DebugEcho("No subject in email");
|
2529 |
-
|
2530 |
-
|
2531 |
-
list($subject, $content) = tag_Subject($content, $default_title);
|
2532 |
} else {
|
2533 |
-
DebugEcho("Using default subject");
|
2534 |
$subject = $config['default_title'];
|
2535 |
}
|
2536 |
-
$mimeDecodedEmail
|
2537 |
} else {
|
2538 |
-
$subject = $mimeDecodedEmail
|
2539 |
-
DebugEcho(("Predecoded subject: $subject"));
|
2540 |
|
2541 |
if ($config['allow_subject_in_mail']) {
|
2542 |
list($subject, $content) = tag_Subject($content, $subject);
|
2543 |
}
|
2544 |
}
|
2545 |
if (!$config['allow_html_in_subject']) {
|
2546 |
-
DebugEcho("subject before htmlentities: $subject");
|
2547 |
$subject = htmlentities($subject, ENT_COMPAT, $config['message_encoding']);
|
2548 |
-
DebugEcho("subject after htmlentities: $subject");
|
2549 |
}
|
2550 |
|
2551 |
//This is for ISO-2022-JP - Can anyone confirm that this is still neeeded?
|
2552 |
// escape sequence is 'ESC $ B' == 1b 24 42 hex.
|
2553 |
if (strpos($subject, "\x1b\x24\x42") !== false) {
|
2554 |
// found iso-2022-jp escape sequence in subject... convert!
|
2555 |
-
DebugEcho("extra parsing for ISO-2022-JP");
|
2556 |
$subject = iconv("ISO-2022-JP", "UTF-8//TRANSLIT", $subject);
|
2557 |
}
|
2558 |
-
DebugEcho("
|
2559 |
return $subject;
|
2560 |
}
|
2561 |
|
@@ -2594,7 +2190,7 @@ function tag_Excerpt(&$content, $config) {
|
|
2594 |
DebugEcho("excerpt found: $post_excerpt");
|
2595 |
if ($config['filternewlines']) {
|
2596 |
DebugEcho("filtering newlines from excerpt");
|
2597 |
-
filter_Newlines($post_excerpt, $config);
|
2598 |
}
|
2599 |
}
|
2600 |
return $post_excerpt;
|
@@ -2728,20 +2324,20 @@ function lookup_category($trial_category, $category_match) {
|
|
2728 |
/**
|
2729 |
* This function just outputs a simple html report about what is being posted in
|
2730 |
*/
|
2731 |
-
function
|
2732 |
//DebugDump($details);
|
2733 |
// Report
|
2734 |
-
DebugEcho('Post Author: ' . $details[
|
2735 |
-
DebugEcho('Date: ' . $details[
|
2736 |
-
foreach ($details[
|
2737 |
DebugEcho('Category: ' . $category);
|
2738 |
}
|
2739 |
-
DebugEcho('Ping Status: ' . $details[
|
2740 |
-
DebugEcho('Comment Status: ' . $details[
|
2741 |
-
DebugEcho('Subject: ' . $details[
|
2742 |
-
DebugEcho('Postname: ' . $details[
|
2743 |
-
DebugEcho('Post Id: ' . $details[
|
2744 |
-
DebugEcho('Post Type: ' . $details[
|
2745 |
//DebugEcho('Posted content: '.$details["post_content"]);
|
2746 |
}
|
2747 |
|
@@ -2757,15 +2353,15 @@ function BuildSelect($label, $id, $current_value, $options, $recommendation = NU
|
|
2757 |
$html = "<tr>
|
2758 |
<th scope='row'><label for='$id'>$label</label>";
|
2759 |
|
2760 |
-
$html.="</th><td><select name='$id' id='$id'>";
|
2761 |
foreach ($options as $value) {
|
2762 |
-
$html.="<option value='$value' " . ($value == $current_value ? "selected='selected'" : "") . ">" . __($value, 'postie') . '</option>';
|
2763 |
}
|
2764 |
-
$html.='</select>';
|
2765 |
if (!empty($recommendation)) {
|
2766 |
-
$html.='<p class = "description">' . $recommendation . '</p>';
|
2767 |
}
|
2768 |
-
$html.="</td>\n</tr>";
|
2769 |
|
2770 |
return $html;
|
2771 |
}
|
@@ -2787,15 +2383,15 @@ function BuildBooleanSelect($label, $id, $current_value, $recommendation = NULL,
|
|
2787 |
$options = Array('Yes', 'No');
|
2788 |
}
|
2789 |
|
2790 |
-
$html.="</th>
|
2791 |
<td><select name='$id' id='$id'>
|
2792 |
<option value='1'>" . __($options[0], 'postie') . "</option>
|
2793 |
<option value='0' " . (!$current_value ? "selected='selected'" : "") . ">" . __($options[1], 'postie') . '</option>
|
2794 |
</select>';
|
2795 |
if (!empty($recommendation)) {
|
2796 |
-
$html.='<p class = "description">' . $recommendation . '</p>';
|
2797 |
}
|
2798 |
-
$html.="</td>\n</tr>";
|
2799 |
|
2800 |
return $html;
|
2801 |
}
|
@@ -2810,9 +2406,9 @@ function BuildBooleanSelect($label, $id, $current_value, $recommendation = NULL,
|
|
2810 |
function BuildTextArea($label, $id, $current_value, $recommendation = NULL) {
|
2811 |
$html = "<tr><th scope='row'><label for='$id'>$label</label>";
|
2812 |
|
2813 |
-
$html.="</th>";
|
2814 |
|
2815 |
-
$html .="<td><br /><textarea cols=40 rows=3 name='$id' id='$id'>";
|
2816 |
$current_value = preg_split("/[\r\n]+/", esc_attr(trim($current_value)));
|
2817 |
if (is_array($current_value)) {
|
2818 |
foreach ($current_value as $item) {
|
@@ -2821,9 +2417,9 @@ function BuildTextArea($label, $id, $current_value, $recommendation = NULL) {
|
|
2821 |
}
|
2822 |
$html .= "</textarea>";
|
2823 |
if ($recommendation) {
|
2824 |
-
$html.="<p class='description'>" . $recommendation . "</p>";
|
2825 |
}
|
2826 |
-
$html .="</td></tr>";
|
2827 |
return $html;
|
2828 |
}
|
2829 |
|
@@ -2833,7 +2429,7 @@ function BuildTextArea($label, $id, $current_value, $recommendation = NULL) {
|
|
2833 |
function config_ResetToDefault() {
|
2834 |
$newconfig = config_GetDefaults();
|
2835 |
$config = get_option('postie-settings');
|
2836 |
-
$save_keys = array('mail_password', 'mail_server', 'mail_server_port', 'mail_userid', 'input_protocol');
|
2837 |
foreach ($save_keys as $key) {
|
2838 |
$newconfig[$key] = $config[$key];
|
2839 |
}
|
@@ -2847,7 +2443,7 @@ function config_ResetToDefault() {
|
|
2847 |
* @return boolean
|
2848 |
*/
|
2849 |
function config_Update($data) {
|
2850 |
-
UpdatePostiePermissions($data[
|
2851 |
// We also update the cron settings
|
2852 |
postie_cron($data['interval']);
|
2853 |
}
|
@@ -2892,6 +2488,7 @@ function config_GetDefaults() {
|
|
2892 |
'imagetemplate' => $wordpress_default,
|
2893 |
'imagetemplates' => $imageTemplates,
|
2894 |
'input_protocol' => "pop3",
|
|
|
2895 |
'interval' => 'twiceperhour',
|
2896 |
'mail_server' => NULL,
|
2897 |
'mail_server_port' => 110,
|
@@ -2908,7 +2505,7 @@ function config_GetDefaults() {
|
|
2908 |
'role_access' => array(),
|
2909 |
'selected_audiotemplate' => 'simple_link',
|
2910 |
'selected_imagetemplate' => 'wordpress_default',
|
2911 |
-
'selected_video1template' => '
|
2912 |
'selected_video2template' => 'simple_link',
|
2913 |
'shortcode' => false,
|
2914 |
'sig_pattern_list' => array('--\s?[\r\n]?', '--\s', '--', '---'),
|
@@ -2942,7 +2539,8 @@ function config_GetDefaults() {
|
|
2942 |
'postie_log_debug' => false,
|
2943 |
'category_colon' => true,
|
2944 |
'category_dash' => true,
|
2945 |
-
'category_bracket' => true
|
|
|
2946 |
);
|
2947 |
}
|
2948 |
|
@@ -2969,9 +2567,9 @@ function config_Read() {
|
|
2969 |
function config_ReadOld() {
|
2970 |
$config = array();
|
2971 |
global $wpdb;
|
2972 |
-
$wpdb->query("SHOW TABLES LIKE '" . $GLOBALS[
|
2973 |
if ($wpdb->num_rows > 0) {
|
2974 |
-
$data = $wpdb->get_results("SELECT label,value FROM " . $GLOBALS[
|
2975 |
if (is_array($data)) {
|
2976 |
foreach ($data as $row) {
|
2977 |
if (in_array($row->label, config_GetListOfArrayConfig())) {
|
@@ -2992,14 +2590,14 @@ function config_ReadOld() {
|
|
2992 |
*/
|
2993 |
function config_UpgradeOld() {
|
2994 |
$config = config_ReadOld();
|
2995 |
-
if (!isset($config[
|
2996 |
-
$config[
|
2997 |
}
|
2998 |
-
if (!isset($config[
|
2999 |
-
$config[
|
3000 |
}
|
3001 |
-
if (!isset($config[
|
3002 |
-
$config[
|
3003 |
}
|
3004 |
if (!isset($config["INPUT_PROTOCOL"])) {
|
3005 |
$config["INPUT_PROTOCOL"] = "pop3";
|
@@ -3206,7 +2804,7 @@ function config_GetOld() {
|
|
3206 |
}
|
3207 |
|
3208 |
/**
|
3209 |
-
* end of functions
|
3210 |
* =======================================================
|
3211 |
*/
|
3212 |
|
@@ -3220,19 +2818,6 @@ function config_ArrayedSettings() {
|
|
3220 |
"\n" => array('smtp', 'authorized_addresses', 'supported_file_types', 'banned_files_list', 'sig_pattern_list'));
|
3221 |
}
|
3222 |
|
3223 |
-
/**
|
3224 |
-
* Detects if they can do IMAP
|
3225 |
-
* @return boolean
|
3226 |
-
*/
|
3227 |
-
function HasIMAPSupport($display = true) {
|
3228 |
-
$function_list = array("imap_open",
|
3229 |
-
"imap_delete",
|
3230 |
-
"imap_expunge",
|
3231 |
-
"imap_body",
|
3232 |
-
"imap_fetchheader");
|
3233 |
-
return(HasFunctions($function_list, $display));
|
3234 |
-
}
|
3235 |
-
|
3236 |
function HasMbStringInstalled() {
|
3237 |
$function_list = array("mb_detect_encoding");
|
3238 |
return(HasFunctions($function_list));
|
@@ -3351,73 +2936,27 @@ function IsDebugMode() {
|
|
3351 |
return (defined('POSTIE_DEBUG') && POSTIE_DEBUG == true);
|
3352 |
}
|
3353 |
|
3354 |
-
function
|
3355 |
if (IsDebugMode()) {
|
3356 |
//DebugDump($email);
|
3357 |
//DebugDump($mimeDecodedEmail);
|
3358 |
|
3359 |
-
$dname = POSTIE_ROOT . DIRECTORY_SEPARATOR .
|
3360 |
if (is_dir($dname)) {
|
3361 |
-
$
|
3362 |
-
|
3363 |
-
|
3364 |
-
fclose($file);
|
3365 |
-
|
3366 |
-
$file = fopen($fname . "-mime.txt ", "w");
|
3367 |
-
fwrite($file, print_r($mimeDecodedEmail, true));
|
3368 |
-
fclose($file);
|
3369 |
-
|
3370 |
-
$file = fopen($fname . ".php ", "w");
|
3371 |
-
fwrite($file, serialize($email));
|
3372 |
-
fclose($file);
|
3373 |
-
}
|
3374 |
-
}
|
3375 |
-
}
|
3376 |
-
|
3377 |
-
function tag_CustomImageField(&$content, &$attachments, $config) {
|
3378 |
-
$customImages = array();
|
3379 |
-
if ($config['custom_image_field']) {
|
3380 |
-
DebugEcho("Looking for custom images");
|
3381 |
-
DebugDump($attachments["html"]);
|
3382 |
-
|
3383 |
-
foreach ($attachments["html"] as $key => $value) {
|
3384 |
-
//DebugEcho("checking " . htmlentities($value));
|
3385 |
-
$matches = array();
|
3386 |
-
if (preg_match("/src\s*=\s*['\"]([^'\"]*)['\"]/i", $value, $matches)) {
|
3387 |
-
DebugEcho("found custom image: " . $matches[1]);
|
3388 |
-
array_push($customImages, $matches[1]);
|
3389 |
}
|
3390 |
-
|
3391 |
-
}
|
3392 |
-
return $customImages;
|
3393 |
-
}
|
3394 |
|
3395 |
-
|
3396 |
-
|
3397 |
-
|
3398 |
-
function filter_VodafoneHandler(&$content, &$attachments) {
|
3399 |
-
if (preg_match('/You have been sent a message from Vodafone mobile/', $content)) {
|
3400 |
-
DebugEcho("Vodafone message");
|
3401 |
-
$index = strpos($content, "TEXT:");
|
3402 |
-
if (strpos !== false) {
|
3403 |
-
$alt_content = substr($content, $index, strlen($content));
|
3404 |
-
$matches = array();
|
3405 |
-
if (preg_match("/<font face=\"verdana,helvetica,arial\" class=\"standard\" color=\"#999999\"><b>(.*)<\/b>/", $alt_content, $matches)) {
|
3406 |
-
//The content is now just the text of the message
|
3407 |
-
$content = $matches[1];
|
3408 |
-
//Now to clean up the attachments
|
3409 |
-
$vodafone_images = array("live.gif", "smiley.gif", "border_left_txt.gif", "border_top.gif", "border_bot.gif", "border_right.gif", "banner1.gif", "i_text.gif", "i_picture.gif",);
|
3410 |
-
while (list($key, $value) = each($attachments['cids'])) {
|
3411 |
-
if (!in_array($key, $vodafone_images)) {
|
3412 |
-
$content .= "<br/>" . $attachments['html'][$attachments['cids'][$key][1]];
|
3413 |
-
}
|
3414 |
-
}
|
3415 |
-
}
|
3416 |
}
|
3417 |
}
|
3418 |
}
|
3419 |
|
3420 |
-
function
|
3421 |
global $wp_filter;
|
3422 |
if (empty($hook) || !isset($wp_filter[$hook])) {
|
3423 |
DebugEcho("No registered filters for $hook");
|
@@ -3429,7 +2968,7 @@ function DebugFiltersFor($hook = '') {
|
|
3429 |
|
3430 |
function postie_test_config() {
|
3431 |
|
3432 |
-
|
3433 |
|
3434 |
if (!current_user_can('manage_options')) {
|
3435 |
DebugEcho("non-admin tried to set options");
|
@@ -3441,6 +2980,9 @@ function postie_test_config() {
|
|
3441 |
if (true == $config['postie_log_error'] || (defined('POSTIE_DEBUG') && true == POSTIE_DEBUG)) {
|
3442 |
add_action('postie_log_error', 'postie_log_error');
|
3443 |
}
|
|
|
|
|
|
|
3444 |
if (true == $config['postie_log_debug'] || (defined('POSTIE_DEBUG') && true == POSTIE_DEBUG)) {
|
3445 |
add_action('postie_log_debug', 'postie_log_debug');
|
3446 |
}
|
@@ -3481,64 +3023,74 @@ function postie_test_config() {
|
|
3481 |
DebugEcho("checking", true);
|
3482 |
}
|
3483 |
|
3484 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
3485 |
case 'imap':
|
3486 |
case 'imap-ssl':
|
3487 |
-
|
3488 |
-
|
3489 |
-
|
3490 |
-
} else {
|
3491 |
-
require_once("postieIMAP.php");
|
3492 |
-
$mail_server = &PostieIMAP::Factory($config["input_protocol"]);
|
3493 |
-
if ($config['email_tls']) {
|
3494 |
-
$mail_server->TLSOn();
|
3495 |
-
}
|
3496 |
-
if (!$mail_server->connect($config["mail_server"], $config["mail_server_port"], $config["mail_userid"], $config["mail_password"])) {
|
3497 |
-
EchoError("Unable to connect. The server said:");
|
3498 |
-
EchoError($mail_server->error());
|
3499 |
} else {
|
3500 |
-
|
3501 |
-
DebugEcho("# of waiting messages: " . $mail_server->getNumberOfMessages(), true);
|
3502 |
-
$mail_server->disconnect();
|
3503 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3504 |
}
|
3505 |
break;
|
|
|
3506 |
case 'pop3':
|
3507 |
-
|
3508 |
-
|
3509 |
-
|
3510 |
-
|
3511 |
-
$pop3->DEBUG = POSTIE_DEBUG;
|
3512 |
-
}
|
3513 |
-
if (!$pop3->connect($config["mail_server"], $config["mail_server_port"])) {
|
3514 |
-
EchoError("Unable to connect. The server said:" . $pop3->ERROR);
|
3515 |
-
} else {
|
3516 |
-
DebugEcho("Sucessful " . strtoupper($config['input_protocol']) . " connection on port {$config["mail_server_port"]}", true);
|
3517 |
-
$msgs = $pop3->login($config["mail_userid"], $config["mail_password"]);
|
3518 |
-
if ($msgs === false) {
|
3519 |
-
//workaround for bug reported here Apr 12, 2013
|
3520 |
-
//https://sourceforge.net/tracker/?func=detail&atid=100311&aid=3610701&group_id=311
|
3521 |
-
//originally repoted here:
|
3522 |
-
//https://core.trac.wordpress.org/ticket/10587
|
3523 |
-
if (empty($pop3->ERROR)) {
|
3524 |
-
DebugEcho("No waiting messages", true);
|
3525 |
-
} else {
|
3526 |
-
EchoError("Unable to login. The server said:" . $pop3->ERROR);
|
3527 |
-
}
|
3528 |
} else {
|
3529 |
-
|
3530 |
}
|
3531 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3532 |
}
|
3533 |
break;
|
|
|
|
|
|
|
3534 |
}
|
3535 |
?>
|
3536 |
</div>
|
3537 |
<?php
|
3538 |
}
|
3539 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3540 |
function postie_get_mail() {
|
3541 |
-
require_once (plugin_dir_path(__FILE__) . 'mimedecode.php');
|
3542 |
if (!function_exists('file_get_html')) {
|
3543 |
require_once (plugin_dir_path(__FILE__) . 'simple_html_dom.php');
|
3544 |
}
|
@@ -3547,6 +3099,9 @@ function postie_get_mail() {
|
|
3547 |
if (true == $config['postie_log_error'] || (defined('POSTIE_DEBUG') && true == POSTIE_DEBUG)) {
|
3548 |
add_action('postie_log_error', 'postie_log_error');
|
3549 |
}
|
|
|
|
|
|
|
3550 |
if (true == $config['postie_log_debug'] || (defined('POSTIE_DEBUG') && true == POSTIE_DEBUG)) {
|
3551 |
add_action('postie_log_debug', 'postie_log_debug');
|
3552 |
}
|
@@ -3567,25 +3122,14 @@ function postie_get_mail() {
|
|
3567 |
echo "Postie: filter 'postie_post' is depricated in favor of 'postie_post_before'";
|
3568 |
}
|
3569 |
|
3570 |
-
$test_email = null;
|
3571 |
if (!array_key_exists('maxemails', $config)) {
|
3572 |
$config['maxemails'] = 0;
|
3573 |
}
|
3574 |
|
3575 |
-
$conninfo =
|
3576 |
-
$conninfo['mail_server'] = $config['mail_server'];
|
3577 |
-
$conninfo['mail_port'] = $config['mail_server_port'];
|
3578 |
-
$conninfo['mail_user'] = $config['mail_userid'];
|
3579 |
-
$conninfo['mail_password'] = $config['mail_password'];
|
3580 |
-
$conninfo['mail_protocol'] = $config['input_protocol'];
|
3581 |
-
$conninfo['mail_tls'] = $config['email_tls'];
|
3582 |
-
$conninfo['email_delete_after_processing'] = $config['delete_mail_after_processing'];
|
3583 |
-
$conninfo['email_max'] = $config['maxemails'];
|
3584 |
-
$conninfo['email_ignore_state'] = $config['ignore_mail_state'];
|
3585 |
|
3586 |
-
$
|
3587 |
|
3588 |
-
$emails = FetchMail($conninfo['mail_server'], $conninfo['mail_port'], $conninfo['mail_user'], $conninfo['mail_password'], $conninfo['mail_protocol'], $config['time_offset'], $test_email, $conninfo['email_delete_after_processing'], $conninfo['email_max'], $conninfo['mail_tls'], $conninfo['email_ignore_state']);
|
3589 |
$message = 'Done.';
|
3590 |
|
3591 |
DebugEcho(sprintf(__("There are %d messages to process", 'postie'), count($emails)));
|
@@ -3616,14 +3160,22 @@ function postie_get_mail() {
|
|
3616 |
continue;
|
3617 |
}
|
3618 |
|
3619 |
-
$
|
3620 |
-
|
3621 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3622 |
|
3623 |
//Check poster to see if a valid person
|
3624 |
-
$poster = ValidatePoster($
|
3625 |
if (!empty($poster)) {
|
3626 |
-
PostEmail($poster, $
|
3627 |
DebugEcho("$message_number: processed");
|
3628 |
} else {
|
3629 |
EchoError("Ignoring email - not authorized.");
|
1 |
<?php
|
2 |
/*
|
3 |
+
$Id: postie-functions.php 1516992 2016-10-17 21:44:29Z WayneAllen $
|
4 |
*/
|
5 |
|
6 |
class PostiePostModifiers {
|
169 |
|
170 |
$format = "Y-m-d";
|
171 |
if ($t != '00:00:00') {
|
172 |
+
$format .= " H:i:s";
|
173 |
}
|
174 |
$message_date = date($format, $newdate);
|
175 |
$content = str_replace($matches[0], '', $content);
|
185 |
return $message_date;
|
186 |
}
|
187 |
|
188 |
+
function tag_CustomImageField($post_ID, $email, $config) {
|
189 |
+
if ($config['custom_image_field']) {
|
190 |
+
DebugEcho("Saving custom image post_meta");
|
191 |
|
192 |
+
foreach (array_merge($email['attachment'], $email['inline'], $email['related']) as $attachment) {
|
193 |
+
add_post_meta($post_ID, 'image', $attachment['filename']);
|
194 |
+
DebugEcho("Saving custom attachment '{$attachment['filename']}'");
|
195 |
+
}
|
196 |
+
}
|
197 |
+
}
|
198 |
|
199 |
+
function filter_AttachmentTemplates($content, $mimeDecodedEmail, $post_id, $config) {
|
200 |
|
201 |
+
$addimages = !($config['custom_image_field'] || $config['auto_gallery'] || preg_match("/\[gallery[^\[]*\]/", $content, $matches));
|
202 |
+
$fiid = -1;
|
203 |
+
|
204 |
+
DebugEcho("filter_AttachmentTemplates: looking for attachments to add to post");
|
205 |
+
$html = '';
|
206 |
+
if ($config['include_featured_image']) {
|
207 |
+
$fiid = get_post_thumbnail_id($post_id);
|
208 |
+
}
|
209 |
+
|
210 |
+
foreach ($mimeDecodedEmail['attachment'] as $attachment) {
|
211 |
+
if ($fiid != $attachment['wp_id'] && false == $attachment['exclude']) {
|
212 |
+
if (!$addimages && $attachment['primary'] == 'image') {
|
213 |
+
DebugEcho("filter_AttachmentTemplates: skip image " . $attachment['filename']);
|
214 |
+
} else {
|
215 |
+
$html .= $attachment['template'];
|
216 |
+
}
|
217 |
+
} else {
|
218 |
+
DebugEcho("filter_AttachmentTemplates: skip attachment " . $attachment['filename']);
|
219 |
+
}
|
220 |
+
}
|
221 |
+
|
222 |
+
if ($config['prefer_text_type'] == 'plain') {
|
223 |
+
foreach (array_merge($mimeDecodedEmail['inline'], $mimeDecodedEmail['related']) as $attachment) {
|
224 |
+
if ($fiid != $attachment['wp_id'] && false == $attachment['exclude']) {
|
225 |
+
if (!$addimages && $attachment['primary'] == 'image') {
|
226 |
+
DebugEcho("filter_AttachmentTemplates: skip image (alt) " . $attachment['filename']);
|
227 |
+
} else {
|
228 |
+
$html .= $attachment['template'];
|
229 |
+
}
|
230 |
+
} else {
|
231 |
+
DebugEcho("filter_AttachmentTemplates: skip attachment (alt) " . $attachment['filename']);
|
232 |
+
}
|
233 |
+
}
|
234 |
+
}
|
235 |
+
|
236 |
+
if ($config['images_append']) {
|
237 |
+
$content .= $html;
|
238 |
+
} else {
|
239 |
+
$content = $html . $content;
|
240 |
+
}
|
241 |
+
|
242 |
+
|
243 |
+
//strip featured image from html
|
244 |
+
if ($fiid > 0 && $config['prefer_text_type'] == 'html' && !$config['include_featured_image']) {
|
245 |
+
DebugEcho("filter_AttachmentTemplates: remove featured image from post");
|
246 |
+
$html = str_get_html($content);
|
247 |
+
if ($html) {
|
248 |
+
$elements = $html->find('img[src=' . wp_get_attachment_url($fiid) . ']');
|
249 |
+
foreach ($elements as $e) {
|
250 |
+
DebugEcho('filter_AttachmentTemplates: outertext:' . $e->outertext);
|
251 |
+
$e->outertext = '';
|
252 |
+
}
|
253 |
+
$content = $html->save();
|
254 |
+
}
|
255 |
+
}
|
256 |
+
|
257 |
+
$imagecount = 0;
|
258 |
+
foreach ($mimeDecodedEmail['attachment'] as $attachment) {
|
259 |
+
DebugEcho("filter_AttachmentTemplates: attachment mime primary: {$attachment['primary']}");
|
260 |
+
if ($attachment['primary'] == 'image' && $attachment['exclude'] == false) {
|
261 |
+
$imagecount++;
|
262 |
+
}
|
263 |
+
}
|
264 |
+
DebugEcho("filter_AttachmentTemplates: image count $imagecount");
|
265 |
+
|
266 |
+
if (($imagecount > 0) && $config['auto_gallery']) {
|
267 |
+
$linktype = strtolower($config['auto_gallery_link']);
|
268 |
+
DebugEcho("filter_AttachmentTemplates: Auto gallery: link type $linktype");
|
269 |
+
postie_show_filters_for('postie_gallery');
|
270 |
+
if ($linktype == 'default') {
|
271 |
+
$imageTemplate = apply_filters('postie_gallery', '[gallery]', $post_id);
|
272 |
+
} else {
|
273 |
+
$imageTemplate = apply_filters('postie_gallery', "[gallery link='$linktype']", $post_id);
|
274 |
+
}
|
275 |
+
DebugEcho("filter_AttachmentTemplates: Auto gallery: template '$imageTemplate'");
|
276 |
+
if ($config['images_append']) {
|
277 |
+
$content .= "\n$imageTemplate";
|
278 |
+
DebugEcho("filter_AttachmentTemplates: Auto gallery: append");
|
279 |
+
} else {
|
280 |
+
$content = "$imageTemplate\n" . $content;
|
281 |
+
DebugEcho("filter_AttachmentTemplates: Auto gallery: prepend");
|
282 |
+
}
|
283 |
+
} else {
|
284 |
+
DebugEcho("filter_AttachmentTemplates: Auto gallery: none");
|
285 |
+
}
|
286 |
|
287 |
+
return $content;
|
288 |
+
}
|
289 |
+
|
290 |
+
function postie_create_post($poster, $mimeDecodedEmail, $post_id, &$is_reply, $config, $postmodifiers) {
|
291 |
+
|
292 |
+
$fulldebug = IsDebugMode();
|
293 |
+
$fulldebugdump = false;
|
294 |
+
|
295 |
+
if (array_key_exists('message-id', $mimeDecodedEmail['headers'])) {
|
296 |
+
DebugEcho("Message Id is :" . htmlentities($mimeDecodedEmail['headers']['message-id']));
|
297 |
if ($fulldebugdump) {
|
298 |
DebugDump($mimeDecodedEmail);
|
299 |
}
|
300 |
}
|
301 |
|
|
|
302 |
if ($fulldebugdump) {
|
303 |
DebugDump($mimeDecodedEmail);
|
304 |
}
|
305 |
|
306 |
+
postie_save_attachments($mimeDecodedEmail, $post_id, $poster, $config);
|
307 |
+
|
308 |
+
$content = postie_get_content($mimeDecodedEmail, $config);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
309 |
|
310 |
+
$subject = postie_get_subject($mimeDecodedEmail, $content, $config);
|
311 |
|
312 |
+
$content = filter_RemoveSignature($content, $config);
|
313 |
if ($fulldebug) {
|
314 |
DebugEcho("post sig: $content");
|
315 |
}
|
325 |
}
|
326 |
|
327 |
$message_date = NULL;
|
328 |
+
if (array_key_exists("date", $mimeDecodedEmail['headers']) && !empty($mimeDecodedEmail['headers']['date'])) {
|
329 |
+
DebugEcho("date header: {$mimeDecodedEmail['headers']['date']}");
|
330 |
+
$message_date = $mimeDecodedEmail['headers']['date'];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
331 |
DebugEcho("decoded date: $message_date");
|
332 |
} else {
|
333 |
DebugEcho("date header missing");
|
339 |
DebugEcho("post date: $content");
|
340 |
}
|
341 |
|
|
|
|
|
|
|
|
|
|
|
342 |
//do post type before category to keep the subject line correct
|
343 |
$post_type = tag_PostType($subject, $postmodifiers, $config);
|
344 |
if ($fulldebug) {
|
346 |
}
|
347 |
|
348 |
$default_categoryid = $config['default_post_category'];
|
349 |
+
|
350 |
+
DebugEcho("pre postie_category_default: '$default_categoryid'");
|
351 |
$default_categoryid = apply_filters('postie_category_default', $default_categoryid);
|
352 |
+
DebugEcho("post postie_category_default: '$default_categoryid'");
|
353 |
+
|
354 |
$post_categories = tag_Categories($subject, $default_categoryid, $config, $post_id);
|
355 |
if ($fulldebug) {
|
356 |
DebugEcho("post category: $content");
|
366 |
DebugEcho("post comment: $content");
|
367 |
}
|
368 |
|
369 |
+
$post_status = tag_Status($content, $config['post_status']);
|
370 |
if ($fulldebug) {
|
371 |
DebugEcho("post status: $content");
|
372 |
}
|
373 |
|
374 |
//handle CID before linkify
|
375 |
+
$content = filter_ReplaceImageCIDs($content, $mimeDecodedEmail);
|
376 |
if ($fulldebug) {
|
377 |
DebugEcho("post cid: $content");
|
378 |
}
|
384 |
}
|
385 |
}
|
386 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
387 |
if ($config['reply_as_comment'] == true) {
|
388 |
+
$id = postie_get_parent_post($subject);
|
389 |
if (empty($id)) {
|
390 |
DebugEcho("Not a reply");
|
391 |
$id = $post_id;
|
403 |
preg_match("/^-+.*?(from|subject|to|date).*?/i", $line) == 0 &&
|
404 |
preg_match("/^on.*?wrote:$/i", $line) == 0 &&
|
405 |
preg_match("/^-+\s*forwarded\s*message\s*-+/i", $line) == 0) {
|
406 |
+
$newContents .= "$line\n";
|
407 |
}
|
408 |
}
|
409 |
if ((strlen($newContents) <> strlen($content)) && ('html' == $config['prefer_text_type'])) {
|
424 |
$post_status = 'future';
|
425 |
}
|
426 |
|
427 |
+
$content = filter_Newlines($content, $config);
|
428 |
if ($fulldebug) {
|
429 |
DebugEcho("post newline: $content");
|
430 |
}
|
431 |
|
432 |
+
$content = filter_Start($content, $config);
|
433 |
if ($fulldebug) {
|
434 |
DebugEcho("post start: $content");
|
435 |
}
|
436 |
|
437 |
+
$content = filter_End($content, $config);
|
438 |
if ($fulldebug) {
|
439 |
DebugEcho("post end: $content");
|
440 |
}
|
441 |
|
442 |
+
$content = filter_ReplaceImagePlaceHolders($content, $mimeDecodedEmail, $config, $id, $config['image_placeholder']);
|
|
|
443 |
if ($fulldebug) {
|
444 |
DebugEcho("post body ReplaceImagePlaceHolders: $content");
|
445 |
}
|
446 |
|
447 |
if ($post_excerpt) {
|
448 |
+
$post_excerpt = filter_ReplaceImagePlaceHolders($post_excerpt, $mimeDecodedEmail, $config, $id, '#eimg%#');
|
449 |
DebugEcho("excerpt: $post_excerpt");
|
450 |
if ($fulldebug) {
|
451 |
DebugEcho("post excerpt ReplaceImagePlaceHolders: $content");
|
452 |
}
|
453 |
}
|
454 |
+
|
455 |
+
//handle inline images after linkify
|
456 |
+
if ('plain' == $config['prefer_text_type']) {
|
457 |
+
$content = filter_ReplaceInlineImage($content, $mimeDecodedEmail, $config);
|
458 |
+
if ($fulldebug) {
|
459 |
+
DebugEcho("post filter_ReplaceInlineImage: $content");
|
460 |
+
}
|
461 |
+
}
|
462 |
+
|
463 |
+
$content = filter_AttachmentTemplates($content, $mimeDecodedEmail, $post_id, $config);
|
464 |
|
465 |
if (trim($subject) == "") {
|
466 |
$subject = $config['default_title'];
|
485 |
'post_name' => sanitize_title($subject),
|
486 |
'post_excerpt' => $post_excerpt,
|
487 |
'ID' => $id,
|
|
|
488 |
'post_status' => $post_status
|
489 |
);
|
490 |
return $details;
|
495 |
*/
|
496 |
function PostEmail($poster, $mimeDecodedEmail, $config) {
|
497 |
postie_disable_revisions();
|
498 |
+
//extract($config);
|
499 |
|
500 |
/* in order to do attachments correctly, we need to associate the
|
501 |
attachments with a post. So we add the post here, then update it */
|
507 |
$is_reply = false;
|
508 |
$postmodifiers = new PostiePostModifiers();
|
509 |
|
510 |
+
$mimeDecodedEmail = apply_filters('postie_post_pre', $mimeDecodedEmail);
|
511 |
+
|
512 |
+
$details = postie_create_post($poster, $mimeDecodedEmail, $post_id, $is_reply, $config, $postmodifiers);
|
513 |
|
514 |
$details = apply_filters('postie_post', $details);
|
515 |
+
$details = apply_filters('postie_post_before', $details, $mimeDecodedEmail['headers']);
|
516 |
|
517 |
DebugEcho(("Post postie_post filter"));
|
518 |
DebugDump($details);
|
527 |
EchoError("postie_post filter cleared the post, not saving.");
|
528 |
}
|
529 |
} else {
|
530 |
+
postie_show_post_details($details);
|
531 |
|
532 |
+
$postid = postie_save_post($details, $is_reply, $config['custom_image_field'], $postmodifiers);
|
533 |
|
534 |
+
if ($config['confirmation_email'] != '') {
|
535 |
+
if ($config['confirmation_email'] == 'sender') {
|
536 |
$recipients = array($details['email_author']);
|
537 |
+
} elseif ($config['confirmation_email'] == 'admin') {
|
538 |
$recipients = array(get_option("admin_email"));
|
539 |
+
} elseif ($config['confirmation_email'] == 'both') {
|
540 |
$recipients = array($details['email_author'], get_option("admin_email"));
|
541 |
}
|
542 |
if (null != $postid) {
|
583 |
DebugEcho("post type: found type '$post_type'");
|
584 |
$post_type = $custom_post_type;
|
585 |
$subject = trim($separated_subject[1]);
|
586 |
+
} elseif (in_array($custom_post_type, array_keys(get_post_format_strings()))) {
|
587 |
DebugEcho("post type: found format '$custom_post_type'");
|
588 |
$postmodifiers->PostFormat = $custom_post_type;
|
589 |
$subject = trim($separated_subject[1]);
|
615 |
|
616 |
function getPostAuthorDetails(&$subject, &$content, &$mimeDecodedEmail) {
|
617 |
|
618 |
+
$theEmail = $mimeDecodedEmail['headers']['from']['mailbox'] . '@' . $mimeDecodedEmail['headers']['from']['host'];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
619 |
|
620 |
$regAuthor = get_user_by('email', $theEmail);
|
621 |
if ($regAuthor) {
|
623 |
$theUrl = $regAuthor->user_url;
|
624 |
$theID = $regAuthor->ID;
|
625 |
} else {
|
626 |
+
$theAuthor = postie_get_name_from_email($theEmail);
|
627 |
$theUrl = '';
|
628 |
$theID = '';
|
629 |
}
|
630 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
631 |
$theDetails = array(
|
|
|
|
|
632 |
'author' => $theAuthor,
|
633 |
'comment_author_url' => $theUrl,
|
634 |
'user_ID' => $theID,
|
635 |
'email' => $theEmail
|
636 |
);
|
637 |
+
|
638 |
return $theDetails;
|
639 |
}
|
640 |
|
646 |
* generated
|
647 |
*/
|
648 |
|
649 |
+
function postie_get_parent_post(&$subject) {
|
650 |
global $wpdb;
|
651 |
|
652 |
$id = NULL;
|
688 |
include(POSTIE_ROOT . DIRECTORY_SEPARATOR . "postie_read_me.php");
|
689 |
}
|
690 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
691 |
/**
|
692 |
* This handles actually showing the form. Called by WordPress
|
693 |
*/
|
694 |
+
function postie_configure() {
|
695 |
include(POSTIE_ROOT . DIRECTORY_SEPARATOR . "config_form.php");
|
696 |
}
|
697 |
|
699 |
* This function handles determining the protocol and fetching the mail
|
700 |
* @return array
|
701 |
*/
|
702 |
+
function postie_fetch_mail($server, $port, $email, $password, $protocol, $deleteMessages, $maxemails, $connectiontype) {
|
703 |
$emails = array();
|
704 |
if (!$server || !$port || !$email) {
|
705 |
EchoError("Missing Configuration For Mail Server");
|
709 |
DebugEcho("MAKE SURE POP IS TURNED ON IN SETTING AT Gmail");
|
710 |
}
|
711 |
switch (strtolower($protocol)) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
712 |
case 'imap':
|
713 |
case 'imap-ssl':
|
714 |
+
$emails = postie_getemails('imap', $server, $port, $email, $password, $protocol, $deleteMessages, $maxemails, $connectiontype);
|
|
|
|
|
|
|
|
|
|
|
715 |
break;
|
716 |
case 'pop3':
|
717 |
+
case 'pop3-ssl':
|
718 |
default:
|
719 |
+
$emails = postie_getemails('pop3', $server, $port, $email, $password, $protocol, $deleteMessages, $maxemails, $connectiontype);
|
720 |
}
|
721 |
|
722 |
return $emails;
|
723 |
}
|
724 |
|
725 |
+
function postie_getemails($type, $server, $port, $email, $password, $protocol, $deleteMessages, $maxemails, $connectiontype) {
|
726 |
+
|
727 |
+
DebugEcho("postie_getemails: Connecting to $server:$port ($protocol)");
|
728 |
+
|
|
|
729 |
$emails = array();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
730 |
|
731 |
+
try {
|
732 |
+
if ($connectiontype == 'curl') {
|
733 |
+
$conn = new pCurlConnection($type, trim($server), $email, $password, $port, ($protocol == 'imap-ssl' || $protocol == 'pop3-ssl'));
|
734 |
+
} else {
|
735 |
+
$conn = new pSocketConnection($type, trim($server), $email, $password, $port, ($protocol == 'imap-ssl' || $protocol == 'pop3-ssl'));
|
736 |
}
|
737 |
+
if ($type == 'imap') {
|
738 |
+
$srv = new pImapMailServer($conn);
|
739 |
+
} else {
|
740 |
+
$srv = new pPop3MailServer($conn);
|
741 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
742 |
|
743 |
+
DebugEcho("postie_getemails: listing messages");
|
744 |
+
$mailbox = new fMailbox($type, $conn, $srv);
|
745 |
+
$messages = $mailbox->listMessages($maxemails);
|
|
|
|
|
746 |
|
747 |
+
foreach ($messages as $uid => $messages) {
|
748 |
+
DebugEcho("postie_getemails: fetch $uid");
|
|
|
|
|
|
|
749 |
|
750 |
+
$email = $mailbox->fetchMessage($uid);
|
751 |
|
752 |
+
if (!empty($email['html'])) {
|
753 |
+
$email['html'] = filter_CleanHtml($email['html']);
|
754 |
+
}
|
755 |
+
|
756 |
+
if (IsDebugMode()) {
|
757 |
+
$raw = $mailbox->fetchMessageSource($uid);
|
758 |
+
postie_save_email_debug($raw, $email);
|
759 |
+
}
|
760 |
+
$emails[] = $email;
|
761 |
+
if ($deleteMessages) {
|
762 |
+
DebugEcho("postie_getemails: deleting $uid");
|
763 |
+
$mailbox->deleteMessages($uid);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
764 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
765 |
}
|
766 |
+
|
767 |
+
DebugEcho("postie_getemails: closing connection");
|
768 |
+
$mailbox->close();
|
769 |
+
} catch (Exception $e) {
|
770 |
+
EchoError($e->getMessage());
|
771 |
}
|
772 |
+
|
|
|
773 |
return $emails;
|
774 |
}
|
775 |
|
776 |
/**
|
777 |
* This function handles putting the actual entry into the database
|
778 |
*/
|
779 |
+
function postie_save_post($details, $isReply, $customImageField, $postmodifiers) {
|
780 |
$post_ID = 0;
|
781 |
if (!$isReply) {
|
782 |
$post_ID = wp_insert_post($details, true);
|
787 |
wp_delete_post($details['ID']);
|
788 |
$post_ID = null;
|
789 |
}
|
|
|
|
|
790 |
} else {
|
791 |
$comment = array(
|
792 |
'comment_author' => $details['comment_author'],
|
809 |
}
|
810 |
|
811 |
if ($post_ID) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
812 |
$postmodifiers->apply($post_ID);
|
|
|
813 |
do_action('postie_post_after', $details);
|
814 |
}
|
815 |
+
|
816 |
return $post_ID;
|
817 |
}
|
818 |
|
834 |
return false;
|
835 |
}
|
836 |
|
837 |
+
function postie_get_content(&$email, $config) {
|
838 |
+
DebugEcho('postie_save_attachments: ---- start');
|
|
|
|
|
839 |
$meta_return = '';
|
840 |
+
if ($config['prefer_text_type'] == 'html') {
|
841 |
+
if (isset($email['html'])) {
|
842 |
+
$meta_return = $email['html'];
|
843 |
+
}
|
844 |
+
} else {
|
845 |
+
if (isset($email['text'])) {
|
846 |
+
$meta_return = $email['text'];
|
847 |
+
}
|
848 |
}
|
849 |
+
return $meta_return;
|
850 |
+
}
|
851 |
|
852 |
+
function postie_save_attachments(&$email, $post_id, $poster, $config) {
|
853 |
+
DebugEcho('postie_save_attachments: ---- start');
|
854 |
|
855 |
+
if (!isset($email['attachment'])) {
|
856 |
+
$email['attachment'] = array();
|
|
|
|
|
|
|
|
|
857 |
}
|
858 |
+
if (!isset($email['inline'])) {
|
859 |
+
$email['inline'] = array();
|
860 |
+
}
|
861 |
+
if (!isset($email['related'])) {
|
862 |
+
$email['related'] = array();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
863 |
}
|
864 |
|
865 |
+
DebugEcho("postie_save_attachments: [attachment]");
|
866 |
+
postie_save_attachments_worker($email['attachment'], $post_id, $poster, $config);
|
867 |
+
DebugEcho("postie_save_attachments: [inline]");
|
868 |
+
postie_save_attachments_worker($email['inline'], $post_id, $poster, $config);
|
869 |
+
DebugEcho("postie_save_attachments: [related]");
|
870 |
+
postie_save_attachments_worker($email['related'], $post_id, $poster, $config);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
871 |
|
|
|
|
|
872 |
|
873 |
+
DebugEcho("postie_save_attachments: ==== end");
|
874 |
+
}
|
875 |
|
876 |
+
function postie_save_attachments_worker(&$attachments, $post_id, $poster, $config) {
|
877 |
+
foreach ($attachments as &$attachment) {
|
878 |
+
if (array_key_exists('filename', $attachment)) {
|
879 |
+
DebugEcho('postie_save_attachments_worker: ' . $attachment['filename']);
|
|
|
|
|
880 |
|
881 |
+
if (isBannedFileName($attachment['filename'], $config['banned_files_list'])) {
|
882 |
+
DebugEcho("postie_save_attachments_worker: skipping banned filename " . $attachment['filename']);
|
883 |
+
break;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
884 |
}
|
885 |
+
} else {
|
886 |
+
DebugEcho('postie_save_attachments_worker: un-named attachment');
|
887 |
}
|
888 |
|
889 |
+
postie_save_attachment($attachment, $post_id, $poster, $config);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
890 |
|
891 |
+
$filename = $attachment['filename'];
|
892 |
+
$fileext = $attachment['ext'];
|
893 |
+
$mparts = explode('/', $attachment['mimetype']);
|
894 |
+
$mimetype_primary = $mparts[0];
|
895 |
+
$mimetype_secondary = $mparts[1];
|
896 |
+
DebugEcho("postie_save_attachments_worker: mime primary: $mimetype_primary");
|
897 |
|
898 |
+
$attachment['primary'] = $mimetype_primary;
|
899 |
+
$attachment['exclude'] = false;
|
|
|
|
|
|
|
900 |
|
901 |
+
$file_id = $attachment['wp_id'];
|
902 |
+
$file = wp_get_attachment_url($file_id);
|
|
|
|
|
|
|
903 |
|
904 |
+
switch ($mimetype_primary) {
|
905 |
+
case 'text':
|
906 |
+
DebugEcho("postie_save_attachments_worker: text attachment");
|
907 |
+
$icon = chooseAttachmentIcon($file, $mimetype_primary, $mimetype_secondary, $config['icon_set'], $config['icon_size']);
|
908 |
+
$attachment['template'] = "<a href='$file'>" . $icon . $filename . '</a>' . "\n";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
909 |
break;
|
910 |
|
911 |
case 'image':
|
912 |
+
DebugEcho("postie_save_attachments_worker: image attachment");
|
913 |
+
$attachment['template'] = parseTemplate($file_id, $mimetype_primary, $config['imagetemplate'], $filename) . "\n";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
914 |
break;
|
915 |
|
916 |
case 'audio':
|
917 |
+
DebugEcho("postie_save_attachments_worker: audio attachment");
|
918 |
+
if (in_array($fileext, $config['audiotypes'])) {
|
919 |
+
DebugEcho("postie_save_attachments_worker: using audio template: $mimetype_secondary");
|
920 |
+
$audioTemplate = $config['audiotemplate'];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
921 |
} else {
|
922 |
+
DebugEcho("postie_save_attachments_worker: using default audio template: $mimetype_secondary");
|
923 |
+
$icon = chooseAttachmentIcon($file, $mimetype_primary, $mimetype_secondary, $config['icon_set'], $config['icon_size']);
|
924 |
+
$audioTemplate = '<a href="{FILELINK}">' . $icon . '{FILENAME}</a>';
|
925 |
}
|
926 |
+
$attachment['template'] = parseTemplate($file_id, $mimetype_primary, $audioTemplate, $filename);
|
927 |
break;
|
928 |
|
929 |
case 'video':
|
930 |
+
DebugEcho("postie_save_attachments_worker: video attachment");
|
931 |
+
if (in_array($fileext, $config['video1types'])) {
|
932 |
+
DebugEcho("postie_save_attachments_worker: using video1 template: $fileext");
|
933 |
+
$videoTemplate = $config['video1template'];
|
934 |
+
} elseif (in_array($fileext, $config['video2types'])) {
|
935 |
+
DebugEcho("postie_save_attachments_worker: using video2 template: $fileext");
|
936 |
+
$videoTemplate = $config['video2template'];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
937 |
} else {
|
938 |
+
DebugEcho("postie_save_attachments_worker: using default template: $fileext");
|
939 |
+
$icon = chooseAttachmentIcon($file, $mimetype_primary, $mimetype_secondary, $config['icon_set'], $config['icon_size']);
|
940 |
+
$videoTemplate = '<a href="{FILELINK}">' . $icon . '{FILENAME}</a>';
|
941 |
}
|
942 |
+
$attachment['template'] = parseTemplate($file_id, $mimetype_primary, $videoTemplate, $filename);
|
943 |
break;
|
944 |
|
945 |
+
default :
|
946 |
+
DebugEcho("postie_save_attachments_worker: generic attachment ($mimetype_primary)");
|
947 |
+
$icon = chooseAttachmentIcon($file, $mimetype_primary, $mimetype_secondary, $config['icon_set'], $config['icon_size']);
|
948 |
+
$attachment['template'] = parseTemplate($file_id, $mimetype_primary, $config['generaltemplate'], $filename, $icon) . "\n";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
949 |
break;
|
950 |
}
|
951 |
}
|
|
|
|
|
|
|
952 |
}
|
953 |
|
954 |
+
function postie_save_attachment(&$attachment, $post_id, $poster, $config) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
955 |
|
956 |
+
if (isset($attachment['filename']) && !empty($attachment['filename'])) {
|
957 |
+
$filename = $attachment['filename'];
|
958 |
+
} else {
|
959 |
+
$filename = uniqid();
|
960 |
}
|
961 |
+
|
962 |
+
DebugEcho("postie_save_attachment: pre sanitize file name '$filename'");
|
963 |
+
//DebugDump($part);
|
964 |
+
$filename = sanitize_file_name($filename);
|
965 |
+
$attachment['filename'] = $filename;
|
966 |
+
|
967 |
+
$fileext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
968 |
+
$attachment['ext'] = $fileext;
|
969 |
+
|
970 |
+
DebugEcho("postie_save_attachment: file name '$filename'");
|
971 |
+
DebugEcho("postie_save_attachment: extension '$fileext'");
|
972 |
+
|
973 |
+
$mparts = explode('/', $attachment['mimetype']);
|
974 |
+
$mimetype_primary = $mparts[0];
|
975 |
+
$mimetype_secondary = $mparts[1];
|
976 |
+
|
977 |
+
$typeinfo = wp_check_filetype($filename);
|
978 |
+
//DebugDump($typeinfo);
|
979 |
+
if (!empty($typeinfo['type'])) {
|
980 |
+
DebugEcho("postie_save_attachment: secondary lookup found " . $typeinfo['type']);
|
981 |
+
$mimeparts = explode('/', strtolower($typeinfo['type']));
|
982 |
+
$mimetype_primary = $mimeparts[0];
|
983 |
+
$mimetype_secondary = $mimeparts[1];
|
984 |
+
} else {
|
985 |
+
DebugEcho("postie_save_attachment: secondary lookup failed, checking configured extensions");
|
986 |
+
if (in_array($fileext, $config['audiotypes'])) {
|
987 |
+
DebugEcho("postie_save_attachment: found audio extension");
|
988 |
+
$mimetype_primary = 'audio';
|
989 |
+
$mimetype_secondary = $fileext;
|
990 |
+
} elseif (in_array($fileext, array_merge($config['video1types'], $config['video2types']))) {
|
991 |
+
DebugEcho("postie_save_attachment: found video extension");
|
992 |
+
$mimetype_primary = 'video';
|
993 |
+
$mimetype_secondary = $fileext;
|
994 |
+
} else {
|
995 |
+
DebugEcho("postie_save_attachment: found no extension");
|
996 |
+
}
|
997 |
}
|
998 |
+
$attachment['mimetype'] = "$mimetype_primary/$mimetype_secondary";
|
999 |
+
DebugEcho("postie_save_attachment: mimetype $mimetype_primary/$mimetype_secondary");
|
1000 |
|
1001 |
+
switch ($mimetype_primary) {
|
1002 |
+
case 'text':
|
1003 |
+
DebugEcho("postie_save_attachment: ctype_primary: text");
|
1004 |
+
//DebugDump($part);
|
1005 |
|
1006 |
+
DebugEcho("postie_save_attachment: text Attachement: $filename");
|
1007 |
+
if (!preg_match('/ATT\d\d\d\d\d.txt/i', $filename)) {
|
1008 |
+
$file_id = postie_media_handle_upload($attachment, $post_id, $poster, $config['generate_thumbnails']);
|
1009 |
+
if (!is_wp_error($file_id)) {
|
1010 |
+
$attachment['wp_id'] = $file_id;
|
1011 |
+
DebugEcho("postie_save_attachment: text attachment: adding '$filename'");
|
1012 |
+
} else {
|
1013 |
+
EchoError($file_id->get_error_message());
|
1014 |
+
}
|
1015 |
+
} else {
|
1016 |
+
DebugEcho("postie_save_attachment: text attachment: skipping '$filename'");
|
1017 |
+
}
|
1018 |
+
break;
|
|
|
|
|
|
|
|
|
1019 |
|
1020 |
+
case 'image':
|
1021 |
+
DebugEcho("postie_save_attachment: image Attachement: $filename");
|
1022 |
+
$file_id = postie_media_handle_upload($attachment, $post_id, $poster, $config['generate_thumbnails'], $mimetype_primary, $mimetype_secondary);
|
1023 |
+
if (!is_wp_error($file_id)) {
|
1024 |
+
$attachment['wp_id'] = $file_id;
|
1025 |
+
//set the first image we come across as the featured image
|
1026 |
+
if ($config['featured_image'] && !has_post_thumbnail($post_id)) {
|
1027 |
+
DebugEcho("postie_save_attachment: featured image: $file_id");
|
1028 |
+
set_post_thumbnail($post_id, $file_id);
|
1029 |
+
}
|
1030 |
+
} else {
|
1031 |
+
EchoError("postie_save_attachment image error: " . $file_id->get_error_message());
|
1032 |
+
}
|
1033 |
+
break;
|
1034 |
+
|
1035 |
+
case 'audio':
|
1036 |
+
DebugEcho("postie_save_attachment: audio Attachement: $filename");
|
1037 |
+
$file_id = postie_media_handle_upload($attachment, $post_id, $poster, $config['generate_thumbnails']);
|
1038 |
+
if (!is_wp_error($file_id)) {
|
1039 |
+
$attachment['wp_id'] = $file_id;
|
1040 |
+
} else {
|
1041 |
+
EchoError("postie_save_attachment audio error: " . $file_id->get_error_message());
|
1042 |
+
}
|
1043 |
+
break;
|
1044 |
+
|
1045 |
+
case 'video':
|
1046 |
+
DebugEcho("postie_save_attachment: video Attachement: $filename");
|
1047 |
+
$file_id = postie_media_handle_upload($attachment, $post_id, $poster, $config['generate_thumbnails']);
|
1048 |
+
if (!is_wp_error($file_id)) {
|
1049 |
+
$attachment['wp_id'] = $file_id;
|
1050 |
+
} else {
|
1051 |
+
EchoError($file_id->get_error_message());
|
1052 |
+
}
|
1053 |
+
break;
|
1054 |
+
|
1055 |
+
default:
|
1056 |
+
DebugEcho("postie_save_attachment: found file type: " . $mimetype_primary);
|
1057 |
+
if (in_array($mimetype_primary, $config['supported_file_types'])) {
|
1058 |
+
//pgp signature - then forget it
|
1059 |
+
if ($mimetype_secondary == 'pgp-signature') {
|
1060 |
+
DebugEcho("postie_save_attachment: found pgp-signature - done");
|
1061 |
+
break;
|
1062 |
+
}
|
1063 |
+
$file_id = postie_media_handle_upload($attachment, $post_id, $poster, $config['generate_thumbnails']);
|
1064 |
+
if (!is_wp_error($file_id)) {
|
1065 |
+
$attachment['wp_id'] = $file_id;
|
1066 |
+
$file = wp_get_attachment_url($file_id);
|
1067 |
+
DebugEcho("postie_save_attachment: uploaded $file_id ($file)");
|
1068 |
+
} else {
|
1069 |
+
EchoError($file_id->get_error_message());
|
1070 |
+
}
|
1071 |
+
} else {
|
1072 |
+
EchoError("$filename has an unsupported MIME type $mimetype_primary and was not added.");
|
1073 |
+
DebugEcho("postie_save_attachment: Not in supported filetype list: '$mimetype_primary'");
|
1074 |
+
DebugDump($config['supported_file_types']);
|
1075 |
+
}
|
1076 |
+
break;
|
1077 |
+
}
|
1078 |
}
|
1079 |
|
1080 |
// This function cleans up HTML in the email
|
1109 |
extract($config);
|
1110 |
$poster = NULL;
|
1111 |
$from = "";
|
1112 |
+
if (array_key_exists('headers', $mimeDecodedEmail) && array_key_exists('from', $mimeDecodedEmail['headers'])) {
|
1113 |
+
//$from = RemoveExtraCharactersInEmailAddress(trim($mimeDecodedEmail->headers["from"]));
|
1114 |
+
$from = $mimeDecodedEmail['headers']['from']['mailbox'] . '@' . $mimeDecodedEmail['headers']['from']['host'];
|
1115 |
+
$from = apply_filters('postie_filter_email', $from);
|
1116 |
DebugEcho("ValidatePoster: post postie_filter_email $from");
|
1117 |
|
1118 |
$toEmail = '';
|
1119 |
+
if (isset($mimeDecodedEmail['headers']['to'])) {
|
1120 |
+
//$toEmail = RemoveExtraCharactersInEmailAddress(trim($mimeDecodedEmail->headers["to"]));
|
1121 |
+
$toEmail = $mimeDecodedEmail['headers']['to'][0]['mailbox'] . '@' . $mimeDecodedEmail['headers']['to'][0]['host'];
|
1122 |
}
|
1123 |
|
1124 |
$replytoEmail = '';
|
1125 |
+
if (isset($mimeDecodedEmail['headers']['reply-to'])) {
|
1126 |
+
//$replytoEmail = RemoveExtraCharactersInEmailAddress(trim($mimeDecodedEmail->headers["reply-to"]));
|
1127 |
+
$replytoEmail = $mimeDecodedEmail['headers']['reply-to']['mailbox'] . '@' . $mimeDecodedEmail['headers']['reply-to']['host'];
|
1128 |
}
|
1129 |
|
1130 |
$from = apply_filters("postie_filter_email2", $from, $toEmail, $replytoEmail);
|
1131 |
DebugEcho("ValidatePoster: post postie_filter_email2 $from");
|
1132 |
} else {
|
1133 |
DebugEcho("No 'from' header found");
|
1134 |
+
DebugDump($mimeDecodedEmail['headers']);
|
1135 |
}
|
1136 |
|
1137 |
+
if (array_key_exists("headers", $mimeDecodedEmail)) {
|
1138 |
+
$from = apply_filters("postie_filter_email3", $from, $mimeDecodedEmail['headers']);
|
1139 |
DebugEcho("ValidatePoster: post postie_filter_email3 $from");
|
1140 |
}
|
1141 |
|
1142 |
$resentFrom = "";
|
1143 |
+
if (array_key_exists('headers', $mimeDecodedEmail) && array_key_exists('resent-from', $mimeDecodedEmail['headers'])) {
|
1144 |
+
$resentFrom = RemoveExtraCharactersInEmailAddress(trim($mimeDecodedEmail->headers['resent-from']));
|
1145 |
}
|
1146 |
|
1147 |
//See if the email address is one of the special authorized ones
|
1168 |
$user_ID = "";
|
1169 |
}
|
1170 |
}
|
1171 |
+
if (empty($user_ID) && ($config['turn_authorization_off'] || isEmailAddressAuthorized($from, $config['authorized_addresses']) || isEmailAddressAuthorized($resentFrom, $config['authorized_addresses']))) {
|
1172 |
+
DebugEcho("ValidatePoster: looking up default user " . $config['admin_username']);
|
1173 |
+
$user = get_user_by('login', $config['admin_username']);
|
1174 |
if ($user === false) {
|
1175 |
+
EchoError("Your 'Default Poster' setting '" . $config['admin_username'] . "' is not a valid WordPress user (2)");
|
1176 |
$poster = 1;
|
1177 |
} else {
|
1178 |
$poster = $user->ID;
|
1185 |
|
1186 |
if (!$poster) {
|
1187 |
EchoError('Invalid sender: ' . htmlentities($from) . "! Not adding email!");
|
1188 |
+
if ($config['forward_rejected_mail']) {
|
1189 |
$admin_email = get_option("admin_email");
|
1190 |
+
if (MailToRecipients($mimeDecodedEmail, array($admin_email), $config['return_to_sender'])) {
|
1191 |
EchoError("A copy of the message has been forwarded to the administrator.");
|
1192 |
} else {
|
1193 |
EchoError("The message was unable to be forwarded to the adminstrator.");
|
1215 |
* @param string
|
1216 |
* @param string
|
1217 |
*/
|
1218 |
+
function filter_Start($content, $config) {
|
1219 |
$start = $config['message_start'];
|
1220 |
if (!empty($start)) {
|
1221 |
$pos = strpos($content, $start);
|
1225 |
DebugEcho("start filter $start");
|
1226 |
$content = substr($content, $pos + strlen($start), strlen($content));
|
1227 |
}
|
1228 |
+
return $content;
|
1229 |
}
|
1230 |
|
1231 |
/**
|
1234 |
* @param string
|
1235 |
* @param array - a list of patterns to determine if it is a sig block
|
1236 |
*/
|
1237 |
+
function filter_RemoveSignature($content, $config) {
|
1238 |
+
DebugEcho("filter_RemoveSignature: start");
|
1239 |
if ($config['drop_signature']) {
|
1240 |
if (empty($config['sig_pattern_list'])) {
|
1241 |
DebugEcho("filter_RemoveSignature: no sig_pattern_list");
|
1242 |
+
return $content;
|
1243 |
}
|
1244 |
//DebugEcho("looking for signature in: $content");
|
1245 |
|
1262 |
break;
|
1263 |
}
|
1264 |
|
1265 |
+
DebugEcho("filter_RemoveSignature: keeping '$line'");
|
1266 |
$strcontent .= $line;
|
1267 |
}
|
1268 |
$content = $strcontent;
|
1269 |
}
|
1270 |
+
} else {
|
1271 |
+
DebugEcho("filter_RemoveSignature: configured to skip");
|
1272 |
}
|
1273 |
+
return $content;
|
1274 |
}
|
1275 |
|
1276 |
function filter_RemoveSignatureWorker(&$html, $pattern) {
|
1277 |
$found = false;
|
1278 |
$matches = array();
|
1279 |
if (preg_match($pattern, trim($html->plaintext), $matches)) {
|
1280 |
+
DebugEcho("filter_RemoveSignatureWorker: signature '$matches[1]' found in:\n" . trim($html->plaintext));
|
1281 |
+
//DebugDump($matches);
|
1282 |
$found = true;
|
1283 |
$i = stripos($html->innertext, $matches[1]);
|
1284 |
$presig = substr($html->innertext, 0, $i);
|
1285 |
+
DebugEcho("filter_RemoveSignatureWorker sig new text:\n$presig");
|
1286 |
$html->innertext = $presig;
|
1287 |
} else {
|
1288 |
+
DebugEcho("filter_RemoveSignatureWorker: no matches {preg_last_error()} '$pattern' $html->plaintext");
|
1289 |
//DebugDump($matches);
|
1290 |
}
|
1291 |
|
1310 |
* @param string
|
1311 |
* @param filter
|
1312 |
*/
|
1313 |
+
function filter_End($content, $config) {
|
1314 |
$end = $config['message_end'];
|
1315 |
if (!empty($end)) {
|
1316 |
$pos = strpos($content, $end);
|
1320 |
DebugEcho("end filter: $end");
|
1321 |
$content = substr($content, 0, $pos);
|
1322 |
}
|
1323 |
+
return $content;
|
1324 |
}
|
1325 |
|
1326 |
//filter content for new lines
|
1327 |
+
function filter_Newlines($content, $config) {
|
1328 |
if ($config['filternewlines']) {
|
1329 |
DebugEcho("filter_Newlines: filternewlines");
|
1330 |
$search = array(
|
1331 |
+
"/\r\n\r\n/",
|
1332 |
"/\r\n/",
|
1333 |
"/\n\n/",
|
|
|
1334 |
"/\r/",
|
1335 |
"/\n/"
|
1336 |
);
|
1337 |
$replace = array(
|
1338 |
+
'PARABREAK',
|
1339 |
+
'LINEBREAK',
|
1340 |
'LINEBREAK',
|
1341 |
'LINEBREAK',
|
1342 |
'LINEBREAK'
|
1343 |
);
|
1344 |
|
1345 |
$result = preg_replace($search, $replace, $content);
|
1346 |
+
DebugDump($result);
|
1347 |
|
|
|
1348 |
if ($config['convertnewline']) {
|
1349 |
+
DebugEcho("filter_Newlines: converting newlines to <br>");
|
1350 |
$content = preg_replace('/(LINEBREAK)/', "<br />\n", $result);
|
1351 |
+
$content = preg_replace('/(PARABREAK)/', "<br />\n<br />\n", $content);
|
1352 |
} else {
|
1353 |
$content = preg_replace('/(LINEBREAK)/', " ", $result);
|
1354 |
+
$content = preg_replace('/(PARABREAK)/', "\r\n", $content);
|
1355 |
}
|
1356 |
}
|
1357 |
+
return $content;
|
1358 |
}
|
1359 |
|
1360 |
//strip pgp stuff
|
1370 |
return preg_replace($search, $replace, $content);
|
1371 |
}
|
1372 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1373 |
/**
|
1374 |
* Checks for the comments tag
|
1375 |
* @return boolean
|
1404 |
}
|
1405 |
|
1406 |
function tag_Delay(&$content, $message_date = NULL, $offset = 0) {
|
1407 |
+
DebugEcho("tag_Delay: $content");
|
1408 |
$delay = 0;
|
1409 |
$matches = array();
|
1410 |
if (preg_match("/delay:(-?[0-9dhm]+)/i", $content, $matches) && trim($matches[1])) {
|
1440 |
}
|
1441 |
$dateInSeconds = strtotime($message_date);
|
1442 |
}
|
1443 |
+
DebugEcho("tag_Delay: pre: $dateInSeconds");
|
1444 |
$dateInSeconds += $delay;
|
1445 |
+
DebugEcho("tag_Delay: post: $dateInSeconds");
|
1446 |
|
1447 |
$post_date = gmdate('Y-m-d H:i:s', $dateInSeconds + ($offset * 3600));
|
1448 |
$post_date_gmt = gmdate('Y-m-d H:i:s', $dateInSeconds);
|
1449 |
+
DebugEcho("tag_Delay: post date: $post_date / $post_date_gmt (gmt) / $delay");
|
1450 |
|
1451 |
return array($post_date, $post_date_gmt, $delay);
|
1452 |
}
|
1457 |
function tag_Subject($content, $defaultTitle) {
|
1458 |
DebugEcho("tag_Subject: Looking for subject in email body");
|
1459 |
if (substr($content, 0, 1) != "#") {
|
1460 |
+
DebugEcho("tag_Subject: No inline subject found, using supplied default [1] '$defaultTitle'");
|
1461 |
return(array($defaultTitle, $content));
|
1462 |
}
|
1463 |
+
//make sure the first line isn't #img
|
1464 |
if (strtolower(substr($content, 1, 3)) != "img") {
|
|
|
1465 |
$subjectEndIndex = strpos($content, "#", 1);
|
1466 |
if (!$subjectEndIndex > 0) {
|
1467 |
DebugEcho("tag_Subject: No subject found, using default [2]");
|
1476 |
}
|
1477 |
}
|
1478 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1479 |
function postie_media_handle_upload($part, $post_id, $poster, $generate_thubnails = true, $mimetype_primary = null, $mimetype_secondary = null) {
|
1480 |
$post_data = array();
|
1481 |
|
1483 |
if ($tmpFile !== false) {
|
1484 |
$fp = fopen($tmpFile, 'w');
|
1485 |
if ($fp) {
|
1486 |
+
fwrite($fp, $part['data']);
|
1487 |
fclose($fp);
|
1488 |
} else {
|
1489 |
EchoError("postie_media_handle_upload: Could not write to temp file: '$tmpFile' ");
|
1492 |
EchoError("postie_media_handle_upload: Could not create temp file in " . get_temp_dir());
|
1493 |
}
|
1494 |
|
|
|
1495 |
$namecs = "";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1496 |
|
1497 |
$name = 'postie-media.' . $namecs;
|
1498 |
+
|
1499 |
+
if (isset($part['filename']) && !empty($part['filename'])) {
|
1500 |
+
$name = $part['filename'];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1501 |
}
|
1502 |
DebugEcho("pre-sanitize name: $name, size: " . filesize($tmpFile));
|
1503 |
$name = sanitize_file_name($name);
|
1522 |
$time = current_time('mysql');
|
1523 |
$post = get_post($post_id);
|
1524 |
if (substr($post->post_date, 0, 4) > 0) {
|
1525 |
+
DebugEcho("postie_media_handle_upload: using post date");
|
1526 |
$time = $post->post_date;
|
1527 |
}
|
1528 |
|
1529 |
$file = postie_handle_upload($the_file, $time, $mimetype_primary, $mimetype_secondary);
|
1530 |
|
|
|
1531 |
if (isset($file['error'])) {
|
1532 |
DebugDump($file['error']);
|
1533 |
return new WP_Error('upload_error', $file['error']);
|
1543 |
if (file_exists(ABSPATH . '/wp-admin/includes/image.php')) {
|
1544 |
include_once(ABSPATH . '/wp-admin/includes/image.php');
|
1545 |
include_once(ABSPATH . '/wp-admin/includes/media.php');
|
1546 |
+
DebugEcho("postie_media_handle_upload: reading metadata");
|
1547 |
if ($image_meta = @wp_read_image_metadata($filename)) {
|
1548 |
if (trim($image_meta['title'])) {
|
1549 |
$title = $image_meta['title'];
|
1550 |
+
DebugEcho("postie_media_handle_upload: Using metadata title: $title");
|
1551 |
}
|
1552 |
if (trim($image_meta['caption'])) {
|
1553 |
$content = $image_meta['caption'];
|
1554 |
+
DebugEcho("postie_media_handle_upload: Using metadata caption: $content");
|
1555 |
}
|
1556 |
}
|
1557 |
}
|
1568 |
), $post_data);
|
1569 |
|
1570 |
// Save the data
|
1571 |
+
DebugEcho("postie_media_handle_upload: before wp_insert_attachment");
|
1572 |
$id = wp_insert_attachment($attachment, $filename, $post_id);
|
1573 |
+
DebugEcho("postie_media_handle_upload: after wp_insert_attachment: attachment id: $id");
|
1574 |
|
1575 |
if (!is_wp_error($id)) {
|
1576 |
do_action('postie_file_added', $post_id, $id, $file);
|
1577 |
|
1578 |
if ($generate_thubnails) {
|
1579 |
$amd = wp_generate_attachment_metadata($id, $filename);
|
1580 |
+
DebugEcho("postie_media_handle_upload: wp_generate_attachment_metadata");
|
1581 |
//DebugDump($amd);
|
1582 |
wp_update_attachment_metadata($id, $amd);
|
1583 |
+
DebugEcho("postie_media_handle_upload: wp_update_attachment_metadata complete");
|
1584 |
} else {
|
1585 |
+
DebugEcho("postie_media_handle_upload: thumbnail generation disabled");
|
1586 |
}
|
1587 |
} else {
|
1588 |
EchoError("There was an error adding the attachement: " . $id->get_error_message());
|
1622 |
|
1623 |
$file = apply_filters('wp_handle_upload_prefilter', $file);
|
1624 |
|
|
|
|
|
|
|
|
|
|
|
|
|
1625 |
// Courtesy of php.net, the strings that describe the error indicated in $_FILES[{form field}]['error'].
|
1626 |
$upload_error_strings = array(false,
|
1627 |
__("The uploaded file exceeds the <code>upload_max_filesize</code> directive in <code>php.ini</code>.", 'postie'),
|
1634 |
|
1635 |
// A successful upload will pass this test. It makes no sense to override this one.
|
1636 |
if ($file['error'] > 0) {
|
1637 |
+
return wp_handle_upload_error($file, $upload_error_strings[$file['error']]);
|
1638 |
}
|
1639 |
// A file with a valid mime type
|
1640 |
if (empty($file['type'])) {
|
1641 |
+
return wp_handle_upload_error($file, __('File type is not allowed', 'postie'));
|
1642 |
}
|
1643 |
// A non-empty file will pass this test.
|
1644 |
if (!($file['size'] > 0 )) {
|
1645 |
+
return wp_handle_upload_error($file, __('File is empty. Please upload something more substantial. This error could also be caused by uploads being disabled in your php.ini.', 'postie'));
|
1646 |
}
|
1647 |
// A properly uploaded file will pass this test. There should be no reason to override this one.
|
1648 |
if (!file_exists($file['tmp_name'])) {
|
1649 |
+
return wp_handle_upload_error($file, __('Specified file failed upload test.', 'postie'));
|
1650 |
}
|
1651 |
|
1652 |
$mimetype = $file['type'];
|
1653 |
+
$ext = strtolower($wp_filetype['ext']);
|
1654 |
|
1655 |
if (empty($ext)) {
|
1656 |
+
$ext = strtolower(ltrim(strrchr($file['name'], '.'), '.'));
|
1657 |
}
|
1658 |
if (empty($ext) && !empty($mimetype_secondary)) {
|
1659 |
+
$ext = strtolower($mimetype_secondary);
|
1660 |
$file['name'] = $file['name'] . ".$ext";
|
1661 |
}
|
1662 |
|
1664 |
|
1665 |
if ((empty($mimetype) && empty($ext)) && !current_user_can('unfiltered_upload')) {
|
1666 |
DebugEcho("postie_handle_upload: no type/ext & user restricted");
|
1667 |
+
return wp_handle_upload_error($file, __('File type does not meet security guidelines. Try another.', 'postie'));
|
1668 |
}
|
1669 |
|
1670 |
// A writable uploads dir will pass this test. Again, there's no point overriding this one.
|
1671 |
if (!( ( $uploads = wp_upload_dir($time) ) && false === $uploads['error'] )) {
|
1672 |
DebugEcho("postie_handle_upload: directory not writable");
|
1673 |
+
return wp_handle_upload_error($file, $uploads['error']);
|
1674 |
}
|
1675 |
// fix filename (encode non-standard characters)
|
1676 |
$file['name'] = filename_fix($file['name']);
|
1687 |
DebugEcho("new file: $new_file");
|
1688 |
//DebugDump($file);
|
1689 |
//DebugDump($uploads);
|
1690 |
+
return wp_handle_upload_error($file, sprintf(__('The uploaded file could not be moved to %s.', 'postie'), $uploads['path']));
|
1691 |
} else {
|
1692 |
DebugEcho("upload: rename to $new_file succeeded");
|
1693 |
}
|
1715 |
return str_replace('%', '', urlencode($filename));
|
1716 |
}
|
1717 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1718 |
/**
|
1719 |
* This function can be used to send confirmation or rejection emails
|
1720 |
* It accepts an object containing the entire message
|
1737 |
|
1738 |
$to = array_pop($recipients);
|
1739 |
|
1740 |
+
$from = trim($mail_content->headers['from']);
|
1741 |
$subject = $mail_content->headers['subject'];
|
1742 |
if ($returnToSender) {
|
1743 |
DebugEcho("MailToRecipients: return to sender $returnToSender");
|
1760 |
DebugEcho("MailToRecipients: sending reject mail");
|
1761 |
$alert_subject = $blogname . ": Unauthorized Post Attempt from $from";
|
1762 |
if (is_array($mail_content->ctype_parameters) && array_key_exists('boundary', $mail_content->ctype_parameters) && $mail_content->ctype_parameters['boundary']) {
|
1763 |
+
$boundary = $mail_content->ctype_parameters['boundary'];
|
1764 |
} else {
|
1765 |
$boundary = uniqid("B_");
|
1766 |
}
|
1767 |
// Set sender details
|
1768 |
+
$headers .= "Content-Type:multipart/alternative; boundary=\"$boundary\"\r\n";
|
1769 |
$message = "An unauthorized message has been sent to $blogname.\n";
|
1770 |
$message .= "Sender: $from\n";
|
1771 |
$message .= "Subject: $subject\n";
|
1786 |
foreach ($mailparts as $part) {
|
1787 |
$mailtext .= "--$boundary\r\n";
|
1788 |
if (array_key_exists('content-type', $part->headers)) {
|
1789 |
+
$mailtext .= "Content-Type: " . $part->headers['content-type'] . "\n";
|
1790 |
}
|
1791 |
if (array_key_exists('content-transfer-encoding', $part->headers)) {
|
1792 |
+
$mailtext .= "Content-Transfer-Encoding: " . $part->headers['content-transfer-encoding'] . "\n";
|
1793 |
}
|
1794 |
if (array_key_exists('content-disposition', $part->headers)) {
|
1795 |
+
$mailtext .= "Content-Disposition: " . $part->headers['content-disposition'] . "\n";
|
1796 |
}
|
1797 |
$mailtext .= "\n";
|
1798 |
if (property_exists($part, 'body')) {
|
1810 |
return true;
|
1811 |
}
|
1812 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1813 |
/**
|
1814 |
* This compares the current address to the list of authorized addresses
|
1815 |
* @param string - email address
|
1846 |
}
|
1847 |
|
1848 |
/**
|
1849 |
+
* This function gleans the name from an email address if available. If not
|
1850 |
* it just returns the username (everything before @)
|
1851 |
*/
|
1852 |
+
function postie_get_name_from_email($address) {
|
1853 |
$name = "";
|
1854 |
$matches = array();
|
1855 |
if (preg_match('/^([^<>]+)<([^<> ()]+)>$/', $address, $matches)) {
|
1932 |
}
|
1933 |
|
1934 |
function parseTemplate($fileid, $type, $template, $orig_filename, $icon = "") {
|
1935 |
+
DebugEcho("parseTemplate: before '$template'");
|
|
|
1936 |
/* we check template for thumb, thumbnail, large, full and use that as
|
1937 |
size. If not found, we default to medium */
|
1938 |
if ($type == 'image') {
|
1942 |
$heights = array();
|
1943 |
$img_src = array();
|
1944 |
|
1945 |
+
postie_show_filters_for('image_downsize'); //possible overrides for image_downsize()
|
1946 |
|
1947 |
for ($i = 0; $i < count($sizes); $i++) {
|
1948 |
list( $img_src[$i], $widths[$i], $heights[$i] ) = image_downsize($fileid, $sizes[$i]);
|
1949 |
$hwstrings[$i] = image_hwstring($widths[$i], $heights[$i]);
|
1950 |
}
|
1951 |
+
DebugEcho('parseTemplate: Sources');
|
1952 |
DebugDump($img_src);
|
1953 |
+
DebugEcho('parseTemplate: Heights');
|
1954 |
DebugDump($heights);
|
1955 |
+
DebugEcho('parseTemplate: Widths');
|
1956 |
DebugDump($widths);
|
1957 |
}
|
1958 |
|
1959 |
$attachment = get_post($fileid);
|
|
|
1960 |
$uploadDir = wp_upload_dir();
|
1961 |
$fileName = basename($attachment->guid);
|
1962 |
$fileType = pathinfo($fileName, PATHINFO_EXTENSION);
|
1989 |
$template = str_replace('{RELFILENAME}', $relFileName, $template);
|
1990 |
$template = str_replace('{ICON}', $icon, $template);
|
1991 |
$template = str_replace('{FILEID}', $fileid, $template);
|
1992 |
+
//$template = str_replace('{CAPTION}', '', $template);
|
1993 |
|
1994 |
+
DebugEcho("parseTemplate: after: '$template<br />'");
|
1995 |
return $template . '<br />';
|
1996 |
}
|
1997 |
|
2001 |
* @param string - text of post
|
2002 |
* @param array - array of HTML for images for post
|
2003 |
*/
|
2004 |
+
function filter_ReplaceImageCIDs($content, &$email) {
|
2005 |
+
DebugEcho('filter_ReplaceImageCIDs: start');
|
2006 |
+
$content = preg_replace("/\[(cid:\S+)\]/i", '<img src="$1"/>', $content); //fix "[cid:xxx-xxx-xx]" image references gmail adds to plain text
|
2007 |
+
if (count($email['related'])) {
|
2008 |
+
DebugEcho("filter_ReplaceImageCIDs: # CID attachments: " . count($email['related']));
|
2009 |
+
foreach ($email['related'] as $cid => &$attachment) {
|
2010 |
+
if (false !== strpos($content, $cid)) {
|
2011 |
+
DebugEcho("filter_ReplaceImageCIDs: CID: $cid");
|
2012 |
+
$fileid = $attachment['wp_id'];
|
2013 |
+
$url = wp_get_attachment_url($fileid);
|
2014 |
+
$content = str_replace($cid, $url, $content);
|
2015 |
+
$attachment['exclude'] = true;
|
2016 |
} else {
|
2017 |
+
DebugEcho("filter_ReplaceImageCIDs: CID not found: $cid");
|
2018 |
}
|
2019 |
}
|
2020 |
+
} else {
|
2021 |
+
DebugEcho("filter_ReplaceImageCIDs: no cid attachments");
|
2022 |
+
}
|
2023 |
+
return $content;
|
2024 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025 |
|
2026 |
+
function filter_ReplaceInlineImage($content, &$email, $config) {
|
2027 |
+
DebugEcho('filter_ReplaceInlineImage: start');
|
2028 |
+
foreach ($email['inline'] as &$inlineImage) {
|
2029 |
+
$template = $inlineImage['template'];
|
2030 |
+
$new = '<:inline ' . $inlineImage['filename'] . ' inline:>';
|
2031 |
+
if (false !== strpos($content, $new)) {
|
2032 |
+
DebugEcho('filter_ReplaceInlineImage: ' . $inlineImage['filename']);
|
2033 |
+
$content = str_ireplace($new, $template, $content);
|
2034 |
+
$inlineImage['exclude'] = true;
|
|
|
|
|
2035 |
} else {
|
2036 |
+
DebugEcho('filter_ReplaceInlineImage: not found: ' . $inlineImage['filename']);
|
2037 |
}
|
2038 |
}
|
2039 |
+
return $content;
|
2040 |
}
|
2041 |
|
2042 |
/**
|
2043 |
* This function handles replacing image place holder #img1# with the HTML for that image
|
2044 |
*/
|
2045 |
+
function filter_ReplaceImagePlaceHolders($content, &$email, $config, $post_id, $image_pattern) {
|
2046 |
+
DebugEcho("filter_ReplaceImagePlaceHolders: start");
|
2047 |
if (!$config['custom_image_field']) {
|
2048 |
$startIndex = $config['start_image_count_at_zero'] ? 0 : 1;
|
2049 |
|
2052 |
'post_type' => 'attachment',
|
2053 |
'numberposts' => -1,
|
2054 |
'post_mime_type' => 'image',));
|
2055 |
+
DebugEcho("filter_ReplaceImagePlaceHolders: images in post: " . count($images));
|
2056 |
DebugDump($images);
|
2057 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2058 |
$pics = "";
|
2059 |
$i = 0;
|
2060 |
+
|
2061 |
+
foreach ($email['attachment'] as &$attachment) {
|
2062 |
+
DebugEcho("filter_ReplaceImagePlaceHolders: " . $attachment['filename']);
|
2063 |
+
|
2064 |
+
if (empty($attachment['template'])) {
|
2065 |
+
DebugEcho("filter_ReplaceImagePlaceHolders: no template");
|
2066 |
+
continue;
|
2067 |
+
}
|
2068 |
+
$imageTemplate = $attachment['template'];
|
2069 |
+
|
2070 |
// looks for ' #img1# ' etc... and replaces with image
|
2071 |
+
$img_placeholder_temp0 = rtrim(str_replace("%", intval($startIndex + $i), $image_pattern), '#');
|
2072 |
+
|
2073 |
+
DebugEcho("filter_ReplaceImagePlaceHolders: img_placeholder_temp0: $img_placeholder_temp0");
|
2074 |
+
if (stristr($content, $img_placeholder_temp0)) {
|
2075 |
+
$attachment['exclude'] = true;
|
2076 |
+
while (stristr($content, $img_placeholder_temp0)) {
|
2077 |
+
// look for caption
|
2078 |
+
DebugEcho("filter_ReplaceImagePlaceHolders: Found $img_placeholder_temp0");
|
2079 |
+
$caption = '';
|
2080 |
+
$img_placeholder_temp = $img_placeholder_temp0;
|
2081 |
+
if (preg_match("/$img_placeholder_temp caption=(.*?)#/i", $content, $matches)) {
|
2082 |
+
//DebugDump($matches);
|
2083 |
+
$caption = trim($matches[1]);
|
2084 |
+
if (strlen($caption) > 2 && ($caption[0] == "'" || $caption[0] == '"')) {
|
2085 |
+
$caption = substr($caption, 1, strlen($caption) - 2);
|
2086 |
+
}
|
2087 |
+
DebugEcho("filter_ReplaceImagePlaceHolders: caption: $caption");
|
2088 |
|
2089 |
+
if (count($images) > $i) {
|
2090 |
+
DebugEcho("filter_ReplaceImagePlaceHolders: Adding alt text to image {$images[$i]->ID}");
|
2091 |
+
update_post_meta($images[$i]->ID, '_wp_attachment_image_alt', $caption);
|
2092 |
+
}
|
2093 |
|
2094 |
+
$img_placeholder_temp = substr($matches[0], 0, -1);
|
2095 |
+
DebugEcho("filter_ReplaceImagePlaceHolders: $img_placeholder_temp");
|
2096 |
+
} else {
|
2097 |
+
DebugEcho("filter_ReplaceImagePlaceHolders: No caption found");
|
2098 |
+
}
|
2099 |
+
DebugEcho("filter_ReplaceImagePlaceHolders: parameterize templete: " . $imageTemplate);
|
2100 |
+
$imageTemplate = mb_str_replace('{CAPTION}', htmlspecialchars($caption, ENT_QUOTES), $imageTemplate);
|
2101 |
+
DebugEcho("filter_ReplaceImagePlaceHolders: captioned template: " . $imageTemplate);
|
2102 |
|
2103 |
+
$img_placeholder_temp .= '#';
|
2104 |
|
2105 |
+
$content = str_ireplace($img_placeholder_temp, $imageTemplate, $content);
|
2106 |
+
DebugEcho("filter_ReplaceImagePlaceHolders: post replace: $content");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2107 |
}
|
2108 |
}
|
2109 |
$i++;
|
2110 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2111 |
} else {
|
2112 |
+
DebugEcho("filter_ReplaceImagePlaceHolders: Custom image field, not adding images");
|
2113 |
}
|
2114 |
+
DebugEcho("filter_ReplaceImagePlaceHolders: end");
|
2115 |
+
return $content;
|
2116 |
}
|
2117 |
|
2118 |
/**
|
2119 |
* This function handles finding and setting the correct subject
|
2120 |
* @return array - (subject,content)
|
2121 |
*/
|
2122 |
+
function postie_get_subject(&$mimeDecodedEmail, &$content, $config) {
|
2123 |
//assign the default title/subject
|
2124 |
+
if (empty($mimeDecodedEmail['headers']['subject'])) {
|
2125 |
+
DebugEcho("postie_get_subject: No subject in email");
|
2126 |
+
if ($config['allow_subject_in_mail']) {
|
2127 |
+
list($subject, $content) = tag_Subject($content, $config['default_title']);
|
|
|
2128 |
} else {
|
2129 |
+
DebugEcho("postie_get_subject: Using default subject");
|
2130 |
$subject = $config['default_title'];
|
2131 |
}
|
2132 |
+
$mimeDecodedEmail['headers']['subject'] = $subject;
|
2133 |
} else {
|
2134 |
+
$subject = $mimeDecodedEmail['headers']['subject'];
|
2135 |
+
DebugEcho(("postie_get_subject: Predecoded subject: $subject"));
|
2136 |
|
2137 |
if ($config['allow_subject_in_mail']) {
|
2138 |
list($subject, $content) = tag_Subject($content, $subject);
|
2139 |
}
|
2140 |
}
|
2141 |
if (!$config['allow_html_in_subject']) {
|
2142 |
+
DebugEcho("postie_get_subject: subject before htmlentities: $subject");
|
2143 |
$subject = htmlentities($subject, ENT_COMPAT, $config['message_encoding']);
|
2144 |
+
DebugEcho("postie_get_subject: subject after htmlentities: $subject");
|
2145 |
}
|
2146 |
|
2147 |
//This is for ISO-2022-JP - Can anyone confirm that this is still neeeded?
|
2148 |
// escape sequence is 'ESC $ B' == 1b 24 42 hex.
|
2149 |
if (strpos($subject, "\x1b\x24\x42") !== false) {
|
2150 |
// found iso-2022-jp escape sequence in subject... convert!
|
2151 |
+
DebugEcho("postie_get_subject: extra parsing for ISO-2022-JP");
|
2152 |
$subject = iconv("ISO-2022-JP", "UTF-8//TRANSLIT", $subject);
|
2153 |
}
|
2154 |
+
DebugEcho("postie_get_subject: '$subject'");
|
2155 |
return $subject;
|
2156 |
}
|
2157 |
|
2190 |
DebugEcho("excerpt found: $post_excerpt");
|
2191 |
if ($config['filternewlines']) {
|
2192 |
DebugEcho("filtering newlines from excerpt");
|
2193 |
+
$post_excerpt = filter_Newlines($post_excerpt, $config);
|
2194 |
}
|
2195 |
}
|
2196 |
return $post_excerpt;
|
2324 |
/**
|
2325 |
* This function just outputs a simple html report about what is being posted in
|
2326 |
*/
|
2327 |
+
function postie_show_post_details($details) {
|
2328 |
//DebugDump($details);
|
2329 |
// Report
|
2330 |
+
DebugEcho('Post Author: ' . $details['post_author']);
|
2331 |
+
DebugEcho('Date: ' . $details['post_date']);
|
2332 |
+
foreach ($details['post_category'] as $category) {
|
2333 |
DebugEcho('Category: ' . $category);
|
2334 |
}
|
2335 |
+
DebugEcho('Ping Status: ' . $details['ping_status']);
|
2336 |
+
DebugEcho('Comment Status: ' . $details['comment_status']);
|
2337 |
+
DebugEcho('Subject: ' . $details['post_title']);
|
2338 |
+
DebugEcho('Postname: ' . $details['post_name']);
|
2339 |
+
DebugEcho('Post Id: ' . $details['ID']);
|
2340 |
+
DebugEcho('Post Type: ' . $details['post_type']); /* Added by Raam Dev <raam@raamdev.com> */
|
2341 |
//DebugEcho('Posted content: '.$details["post_content"]);
|
2342 |
}
|
2343 |
|
2353 |
$html = "<tr>
|
2354 |
<th scope='row'><label for='$id'>$label</label>";
|
2355 |
|
2356 |
+
$html .= "</th><td><select name='$id' id='$id'>";
|
2357 |
foreach ($options as $value) {
|
2358 |
+
$html .= "<option value='$value' " . ($value == $current_value ? "selected='selected'" : "") . ">" . __($value, 'postie') . '</option>';
|
2359 |
}
|
2360 |
+
$html .= '</select>';
|
2361 |
if (!empty($recommendation)) {
|
2362 |
+
$html .= '<p class = "description">' . $recommendation . '</p>';
|
2363 |
}
|
2364 |
+
$html .= "</td>\n</tr>";
|
2365 |
|
2366 |
return $html;
|
2367 |
}
|
2383 |
$options = Array('Yes', 'No');
|
2384 |
}
|
2385 |
|
2386 |
+
$html .= "</th>
|
2387 |
<td><select name='$id' id='$id'>
|
2388 |
<option value='1'>" . __($options[0], 'postie') . "</option>
|
2389 |
<option value='0' " . (!$current_value ? "selected='selected'" : "") . ">" . __($options[1], 'postie') . '</option>
|
2390 |
</select>';
|
2391 |
if (!empty($recommendation)) {
|
2392 |
+
$html .= '<p class = "description">' . $recommendation . '</p>';
|
2393 |
}
|
2394 |
+
$html .= "</td>\n</tr>";
|
2395 |
|
2396 |
return $html;
|
2397 |
}
|
2406 |
function BuildTextArea($label, $id, $current_value, $recommendation = NULL) {
|
2407 |
$html = "<tr><th scope='row'><label for='$id'>$label</label>";
|
2408 |
|
2409 |
+
$html .= "</th>";
|
2410 |
|
2411 |
+
$html .= "<td><br /><textarea cols=40 rows=3 name='$id' id='$id'>";
|
2412 |
$current_value = preg_split("/[\r\n]+/", esc_attr(trim($current_value)));
|
2413 |
if (is_array($current_value)) {
|
2414 |
foreach ($current_value as $item) {
|
2417 |
}
|
2418 |
$html .= "</textarea>";
|
2419 |
if ($recommendation) {
|
2420 |
+
$html .= "<p class='description'>" . $recommendation . "</p>";
|
2421 |
}
|
2422 |
+
$html .= "</td></tr>";
|
2423 |
return $html;
|
2424 |
}
|
2425 |
|
2429 |
function config_ResetToDefault() {
|
2430 |
$newconfig = config_GetDefaults();
|
2431 |
$config = get_option('postie-settings');
|
2432 |
+
$save_keys = array('mail_password', 'mail_server', 'mail_server_port', 'mail_userid', 'input_protocol', 'input_connection');
|
2433 |
foreach ($save_keys as $key) {
|
2434 |
$newconfig[$key] = $config[$key];
|
2435 |
}
|
2443 |
* @return boolean
|
2444 |
*/
|
2445 |
function config_Update($data) {
|
2446 |
+
UpdatePostiePermissions($data['role_access']);
|
2447 |
// We also update the cron settings
|
2448 |
postie_cron($data['interval']);
|
2449 |
}
|
2488 |
'imagetemplate' => $wordpress_default,
|
2489 |
'imagetemplates' => $imageTemplates,
|
2490 |
'input_protocol' => "pop3",
|
2491 |
+
'input_connection' => 'sockets',
|
2492 |
'interval' => 'twiceperhour',
|
2493 |
'mail_server' => NULL,
|
2494 |
'mail_server_port' => 110,
|
2505 |
'role_access' => array(),
|
2506 |
'selected_audiotemplate' => 'simple_link',
|
2507 |
'selected_imagetemplate' => 'wordpress_default',
|
2508 |
+
'selected_video1template' => 'vshortcode',
|
2509 |
'selected_video2template' => 'simple_link',
|
2510 |
'shortcode' => false,
|
2511 |
'sig_pattern_list' => array('--\s?[\r\n]?', '--\s', '--', '---'),
|
2539 |
'postie_log_debug' => false,
|
2540 |
'category_colon' => true,
|
2541 |
'category_dash' => true,
|
2542 |
+
'category_bracket' => true,
|
2543 |
+
'prefer_text_convert' => true
|
2544 |
);
|
2545 |
}
|
2546 |
|
2567 |
function config_ReadOld() {
|
2568 |
$config = array();
|
2569 |
global $wpdb;
|
2570 |
+
$wpdb->query("SHOW TABLES LIKE '" . $GLOBALS['table_prefix'] . "postie_config'");
|
2571 |
if ($wpdb->num_rows > 0) {
|
2572 |
+
$data = $wpdb->get_results("SELECT label,value FROM " . $GLOBALS['table_prefix'] . "postie_config;");
|
2573 |
if (is_array($data)) {
|
2574 |
foreach ($data as $row) {
|
2575 |
if (in_array($row->label, config_GetListOfArrayConfig())) {
|
2590 |
*/
|
2591 |
function config_UpgradeOld() {
|
2592 |
$config = config_ReadOld();
|
2593 |
+
if (!isset($config['ADMIN_USERNAME'])) {
|
2594 |
+
$config['ADMIN_USERNAME'] = 'admin';
|
2595 |
}
|
2596 |
+
if (!isset($config['PREFER_TEXT_TYPE'])) {
|
2597 |
+
$config['PREFER_TEXT_TYPE'] = "plain";
|
2598 |
}
|
2599 |
+
if (!isset($config['DEFAULT_TITLE'])) {
|
2600 |
+
$config['DEFAULT_TITLE'] = "Live From The Field";
|
2601 |
}
|
2602 |
if (!isset($config["INPUT_PROTOCOL"])) {
|
2603 |
$config["INPUT_PROTOCOL"] = "pop3";
|
2804 |
}
|
2805 |
|
2806 |
/**
|
2807 |
+
* end of functions used to retrieve the old (pre 1.4) config
|
2808 |
* =======================================================
|
2809 |
*/
|
2810 |
|
2818 |
"\n" => array('smtp', 'authorized_addresses', 'supported_file_types', 'banned_files_list', 'sig_pattern_list'));
|
2819 |
}
|
2820 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2821 |
function HasMbStringInstalled() {
|
2822 |
$function_list = array("mb_detect_encoding");
|
2823 |
return(HasFunctions($function_list));
|
2936 |
return (defined('POSTIE_DEBUG') && POSTIE_DEBUG == true);
|
2937 |
}
|
2938 |
|
2939 |
+
function postie_save_email_debug($raw, $email) {
|
2940 |
if (IsDebugMode()) {
|
2941 |
//DebugDump($email);
|
2942 |
//DebugDump($mimeDecodedEmail);
|
2943 |
|
2944 |
+
$dname = POSTIE_ROOT . DIRECTORY_SEPARATOR . 'test_emails' . DIRECTORY_SEPARATOR;
|
2945 |
if (is_dir($dname)) {
|
2946 |
+
$mid = $email['headers']['message-id'];
|
2947 |
+
if (empty($mid)) {
|
2948 |
+
$mid = uniqid();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2949 |
}
|
2950 |
+
$fname = $dname . sanitize_file_name($mid);
|
|
|
|
|
|
|
2951 |
|
2952 |
+
file_put_contents($fname . '-raw.txt', $raw);
|
2953 |
+
file_put_contents($fname . '.txt', $email['text']);
|
2954 |
+
file_put_contents($fname . '.html', $email['html']);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2955 |
}
|
2956 |
}
|
2957 |
}
|
2958 |
|
2959 |
+
function postie_show_filters_for($hook = '') {
|
2960 |
global $wp_filter;
|
2961 |
if (empty($hook) || !isset($wp_filter[$hook])) {
|
2962 |
DebugEcho("No registered filters for $hook");
|
2968 |
|
2969 |
function postie_test_config() {
|
2970 |
|
2971 |
+
wp_get_current_user();
|
2972 |
|
2973 |
if (!current_user_can('manage_options')) {
|
2974 |
DebugEcho("non-admin tried to set options");
|
2980 |
if (true == $config['postie_log_error'] || (defined('POSTIE_DEBUG') && true == POSTIE_DEBUG)) {
|
2981 |
add_action('postie_log_error', 'postie_log_error');
|
2982 |
}
|
2983 |
+
if (true == $config['postie_log_debug'] && !defined('POSTIE_DEBUG')) {
|
2984 |
+
define('POSTIE_DEBUG', true);
|
2985 |
+
}
|
2986 |
if (true == $config['postie_log_debug'] || (defined('POSTIE_DEBUG') && true == POSTIE_DEBUG)) {
|
2987 |
add_action('postie_log_debug', 'postie_log_debug');
|
2988 |
}
|
3023 |
DebugEcho("checking", true);
|
3024 |
}
|
3025 |
|
3026 |
+
$conninfo = postie_get_conn_info($config);
|
3027 |
+
if (IsDebugMode()) {
|
3028 |
+
fCore::enableDebugging(true);
|
3029 |
+
fCore::registerDebugCallback('DebugEcho');
|
3030 |
+
}
|
3031 |
+
|
3032 |
+
switch (strtolower($config['input_protocol'])) {
|
3033 |
case 'imap':
|
3034 |
case 'imap-ssl':
|
3035 |
+
try {
|
3036 |
+
if ($config['input_connection'] == 'curl') {
|
3037 |
+
$conn = new pCurlConnection('imap', $conninfo['mail_server'], $conninfo['mail_user'], $conninfo['mail_password'], $conninfo['mail_port'], ($conninfo['mail_protocol'] == 'imap-ssl' || $conninfo['mail_protocol'] == 'pop3-ssl'));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3038 |
} else {
|
3039 |
+
$conn = new pSocketConnection('imap', $conninfo['mail_server'], $conninfo['mail_user'], $conninfo['mail_password'], $conninfo['mail_port'], ($conninfo['mail_protocol'] == 'imap-ssl' || $conninfo['mail_protocol'] == 'pop3-ssl'));
|
|
|
|
|
3040 |
}
|
3041 |
+
$srv = new pImapMailServer($conn);
|
3042 |
+
$mailbox = new fMailbox('imap', $conn, $srv);
|
3043 |
+
$m = $mailbox->countMessages();
|
3044 |
+
DebugEcho("Successful " . strtoupper($config['input_protocol']) . " connection on port {$config['mail_server_port']}", true);
|
3045 |
+
DebugEcho("# of waiting messages: $m", true);
|
3046 |
+
} catch (Exception $e) {
|
3047 |
+
EchoError("Unable to connect. The server said: " . $e->getMessage());
|
3048 |
}
|
3049 |
break;
|
3050 |
+
|
3051 |
case 'pop3':
|
3052 |
+
case 'pop3-ssl':
|
3053 |
+
try {
|
3054 |
+
if ($config['input_connection'] == 'curl') {
|
3055 |
+
$conn = new pCurlConnection('pop3', $conninfo['mail_server'], $conninfo['mail_user'], $conninfo['mail_password'], $conninfo['mail_port'], ($conninfo['mail_protocol'] == 'imap-ssl' || $conninfo['mail_protocol'] == 'pop3-ssl'), 30);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3056 |
} else {
|
3057 |
+
$conn = new pSocketConnection('pop3', $conninfo['mail_server'], $conninfo['mail_user'], $conninfo['mail_password'], $conninfo['mail_port'], ($conninfo['mail_protocol'] == 'imap-ssl' || $conninfo['mail_protocol'] == 'pop3-ssl'));
|
3058 |
}
|
3059 |
+
$srv = new pPop3MailServer($conn);
|
3060 |
+
$mailbox = new fMailbox('pop3', $conn, $srv);
|
3061 |
+
$m = $mailbox->countMessages();
|
3062 |
+
DebugEcho("Successful " . strtoupper($config['input_protocol']) . " connection on port {$config['mail_server_port']}", true);
|
3063 |
+
DebugEcho("# of waiting messages: $m", true);
|
3064 |
+
} catch (Exception $e) {
|
3065 |
+
EchoError("Unable to connect. The server said:");
|
3066 |
+
EchoError($e->getMessage());
|
3067 |
}
|
3068 |
break;
|
3069 |
+
default:
|
3070 |
+
EchoError("Unable to connect. Invalid setup");
|
3071 |
+
break;
|
3072 |
}
|
3073 |
?>
|
3074 |
</div>
|
3075 |
<?php
|
3076 |
}
|
3077 |
|
3078 |
+
function postie_get_conn_info($config) {
|
3079 |
+
$conninfo = array();
|
3080 |
+
$conninfo['mail_server'] = $config['mail_server'];
|
3081 |
+
$conninfo['mail_port'] = $config['mail_server_port'];
|
3082 |
+
$conninfo['mail_user'] = $config['mail_userid'];
|
3083 |
+
$conninfo['mail_password'] = $config['mail_password'];
|
3084 |
+
$conninfo['mail_protocol'] = $config['input_protocol'];
|
3085 |
+
$conninfo['mail_tls'] = $config['email_tls'];
|
3086 |
+
$conninfo['email_delete_after_processing'] = $config['delete_mail_after_processing'];
|
3087 |
+
$conninfo['email_max'] = $config['maxemails'];
|
3088 |
+
$conninfo['email_ignore_state'] = $config['ignore_mail_state'];
|
3089 |
+
|
3090 |
+
return apply_filters('postie_preconnect', $conninfo);
|
3091 |
+
}
|
3092 |
+
|
3093 |
function postie_get_mail() {
|
|
|
3094 |
if (!function_exists('file_get_html')) {
|
3095 |
require_once (plugin_dir_path(__FILE__) . 'simple_html_dom.php');
|
3096 |
}
|
3099 |
if (true == $config['postie_log_error'] || (defined('POSTIE_DEBUG') && true == POSTIE_DEBUG)) {
|
3100 |
add_action('postie_log_error', 'postie_log_error');
|
3101 |
}
|
3102 |
+
if (true == $config['postie_log_debug'] && !defined('POSTIE_DEBUG')) {
|
3103 |
+
define('POSTIE_DEBUG', true);
|
3104 |
+
}
|
3105 |
if (true == $config['postie_log_debug'] || (defined('POSTIE_DEBUG') && true == POSTIE_DEBUG)) {
|
3106 |
add_action('postie_log_debug', 'postie_log_debug');
|
3107 |
}
|
3122 |
echo "Postie: filter 'postie_post' is depricated in favor of 'postie_post_before'";
|
3123 |
}
|
3124 |
|
|
|
3125 |
if (!array_key_exists('maxemails', $config)) {
|
3126 |
$config['maxemails'] = 0;
|
3127 |
}
|
3128 |
|
3129 |
+
$conninfo = postie_get_conn_info($config);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3130 |
|
3131 |
+
$emails = postie_fetch_mail($conninfo['mail_server'], $conninfo['mail_port'], $conninfo['mail_user'], $conninfo['mail_password'], $conninfo['mail_protocol'], $conninfo['email_delete_after_processing'], $conninfo['email_max'], $config['input_connection']);
|
3132 |
|
|
|
3133 |
$message = 'Done.';
|
3134 |
|
3135 |
DebugEcho(sprintf(__("There are %d messages to process", 'postie'), count($emails)));
|
3160 |
continue;
|
3161 |
}
|
3162 |
|
3163 |
+
$tmp_config = $config;
|
3164 |
+
if ($config['prefer_text_convert']) {
|
3165 |
+
if ($config['prefer_text_type'] == 'plain' && trim($email['text']) == '') {
|
3166 |
+
DebugEcho("postie_get_mail: switching to html");
|
3167 |
+
$tmp_config['prefer_text_type'] = 'html';
|
3168 |
+
}
|
3169 |
+
if ($config['prefer_text_type'] == 'html' && trim($email['html']) == '') {
|
3170 |
+
DebugEcho("postie_get_mail: switching to plain");
|
3171 |
+
$tmp_config['prefer_text_type'] = 'plain';
|
3172 |
+
}
|
3173 |
+
}
|
3174 |
|
3175 |
//Check poster to see if a valid person
|
3176 |
+
$poster = ValidatePoster($email, $tmp_config);
|
3177 |
if (!empty($poster)) {
|
3178 |
+
PostEmail($poster, $email, $tmp_config);
|
3179 |
DebugEcho("$message_number: processed");
|
3180 |
} else {
|
3181 |
EchoError("Ignoring email - not authorized.");
|
postie.php
CHANGED
@@ -3,8 +3,8 @@
|
|
3 |
/*
|
4 |
Plugin Name: Postie
|
5 |
Plugin URI: http://PostiePlugin.com/
|
6 |
-
Description: Create posts via email.
|
7 |
-
Version: 1.
|
8 |
Author: Wayne Allen
|
9 |
Author URI: http://PostiePlugin.com/
|
10 |
License: GPL2
|
@@ -28,12 +28,28 @@
|
|
28 |
*/
|
29 |
|
30 |
/*
|
31 |
-
$Id: postie.php
|
32 |
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . "lib_autolink.php");
|
34 |
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . "postie-functions.php");
|
35 |
|
36 |
-
define('POSTIE_VERSION', '1.
|
37 |
define("POSTIE_ROOT", dirname(__FILE__));
|
38 |
define("POSTIE_URL", WP_PLUGIN_URL . '/' . basename(dirname(__FILE__)));
|
39 |
|
@@ -208,28 +224,6 @@ function postie_warnings() {
|
|
208 |
add_action('admin_notices', 'postie_enter_info');
|
209 |
}
|
210 |
|
211 |
-
$p = strtolower($config['input_protocol']);
|
212 |
-
if (!function_exists('imap_mime_header_decode') && ($p == 'imap' || $p == 'imap-ssl' || $p == 'pop-ssl')) {
|
213 |
-
|
214 |
-
function postie_imap_warning() {
|
215 |
-
echo "<div id='postie-imap-warning' class='error'><p><strong>";
|
216 |
-
echo __('Warning: the IMAP php extension is not installed. Postie can not use IMAP, IMAP-SSL or POP-SSL without this extension.', 'postie');
|
217 |
-
echo "</strong></p></div>";
|
218 |
-
}
|
219 |
-
|
220 |
-
add_action('admin_notices', 'postie_imap_warning');
|
221 |
-
}
|
222 |
-
if ($p == 'pop3' && $config['email_tls']) {
|
223 |
-
|
224 |
-
function postie_tls_warning() {
|
225 |
-
echo "<div id='postie-lst-warning' class='error'><p><strong>";
|
226 |
-
echo __('Warning: The POP3 connector does not support TLS.', 'postie');
|
227 |
-
echo "</strong></p></div>";
|
228 |
-
}
|
229 |
-
|
230 |
-
add_action('admin_notices', 'postie_tls_warning');
|
231 |
-
}
|
232 |
-
|
233 |
if (isMarkdownInstalled() && $config['prefer_text_type'] == 'html') {
|
234 |
|
235 |
function postie_markdown_warning() {
|
@@ -252,12 +246,25 @@ function postie_warnings() {
|
|
252 |
add_action('admin_notices', 'postie_iconv_warning');
|
253 |
}
|
254 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
255 |
$userdata = WP_User::get_data_by('login', $config['admin_username']);
|
256 |
if (!$userdata) {
|
257 |
|
258 |
function postie_adminuser_warning() {
|
259 |
echo "<div id='postie-mbstring-warning' class='error'><p><strong>";
|
260 |
-
echo __('Warning: the
|
261 |
echo "</strong></p></div>";
|
262 |
}
|
263 |
|
3 |
/*
|
4 |
Plugin Name: Postie
|
5 |
Plugin URI: http://PostiePlugin.com/
|
6 |
+
Description: Create posts via email. Significantly upgrades the Post by Email features of WordPress.
|
7 |
+
Version: 1.8.4
|
8 |
Author: Wayne Allen
|
9 |
Author URI: http://PostiePlugin.com/
|
10 |
License: GPL2
|
28 |
*/
|
29 |
|
30 |
/*
|
31 |
+
$Id: postie.php 1517002 2016-10-17 22:14:58Z WayneAllen $
|
32 |
*/
|
33 |
+
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . "lib/fException.php");
|
34 |
+
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . "lib/fUnexpectedException.php");
|
35 |
+
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . "lib/fExpectedException.php");
|
36 |
+
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . "lib/fConnectivityException.php");
|
37 |
+
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . "lib/fValidationException.php");
|
38 |
+
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . "lib/fProgrammerException.php");
|
39 |
+
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . "lib/fEnvironmentException.php");
|
40 |
+
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . "lib/fCore.php");
|
41 |
+
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . "lib/fMailbox.php");
|
42 |
+
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . "lib/fEmail.php");
|
43 |
+
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . "lib/pConnection.php");
|
44 |
+
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . "lib/pCurlConnection.php");
|
45 |
+
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . "lib/pSocketConnection.php");
|
46 |
+
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . "lib/pMailServer.php");
|
47 |
+
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . "lib/pImapMailServer.php");
|
48 |
+
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . "lib/pPop3MailServer.php");
|
49 |
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . "lib_autolink.php");
|
50 |
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . "postie-functions.php");
|
51 |
|
52 |
+
define('POSTIE_VERSION', '1.8.4');
|
53 |
define("POSTIE_ROOT", dirname(__FILE__));
|
54 |
define("POSTIE_URL", WP_PLUGIN_URL . '/' . basename(dirname(__FILE__)));
|
55 |
|
224 |
add_action('admin_notices', 'postie_enter_info');
|
225 |
}
|
226 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
227 |
if (isMarkdownInstalled() && $config['prefer_text_type'] == 'html') {
|
228 |
|
229 |
function postie_markdown_warning() {
|
246 |
add_action('admin_notices', 'postie_iconv_warning');
|
247 |
}
|
248 |
|
249 |
+
$cv = curl_version();
|
250 |
+
if ($config['input_connection'] == 'curl' && !version_compare($cv['version'], '7.30.0', 'ge')) {
|
251 |
+
|
252 |
+
function postie_curl_warning() {
|
253 |
+
$cv = curl_version();
|
254 |
+
echo "<div id='postie-lst-warning' class='error'><p><strong>";
|
255 |
+
_e("Warning! Postie requires cURL 7.30.0 or newer be installed. {$cv['version']} is installed.", 'postie');
|
256 |
+
echo "</strong></p></div>";
|
257 |
+
}
|
258 |
+
|
259 |
+
add_action('admin_notices', 'postie_curl_warning');
|
260 |
+
}
|
261 |
+
|
262 |
$userdata = WP_User::get_data_by('login', $config['admin_username']);
|
263 |
if (!$userdata) {
|
264 |
|
265 |
function postie_adminuser_warning() {
|
266 |
echo "<div id='postie-mbstring-warning' class='error'><p><strong>";
|
267 |
+
echo __('Warning: the Default Poster is not a valid WordPress login. Postie may reject emails if this is not corrected.', 'postie');
|
268 |
echo "</strong></p></div>";
|
269 |
}
|
270 |
|
readme.txt
CHANGED
@@ -5,8 +5,8 @@ Author URI: http://allens-home.com/
|
|
5 |
Plugin URI: http://PostiePlugin.com/
|
6 |
Tags: e-mail, email, post-by-email
|
7 |
Requires at least: 3.3.0
|
8 |
-
Tested up to: 4.
|
9 |
-
Stable tag: 1.
|
10 |
License: GPLv2 or later
|
11 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
12 |
|
@@ -16,7 +16,7 @@ Postie allows you to create posts via email, including many advanced features no
|
|
16 |
Postie offers many advanced features for creating posts by email,
|
17 |
including the ability to assign categories by name, included pictures and
|
18 |
videos, and automatically strip off signatures. It also has support for both
|
19 |
-
IMAP and POP3, with the option for ssl with both.
|
20 |
[other notes](other_notes) page
|
21 |
|
22 |
More info at http://PostiePlugin.com/
|
@@ -208,6 +208,11 @@ More info at http://PostiePlugin.com/
|
|
208 |
Please visit our FAQ page at <a href="http://postieplugin.com/faq/">http://postieplugin.com/faq/</a>
|
209 |
== Upgrade Notice ==
|
210 |
|
|
|
|
|
|
|
|
|
|
|
211 |
= 1.6.0 =
|
212 |
Remote cron jobs need to update the URL used to kick off a manual email check. The new URL is http://<mysite>/?postie=get-mail
|
213 |
Accessing http://<mysite>/wp-content/plugins/postie/get_mail.php will now receive a 403 error and a message stating what the new URL should be.
|
@@ -235,6 +240,33 @@ All script, style and body tags are stripped from html emails.
|
|
235 |
Attachments are now processed in the order they were attached.
|
236 |
|
237 |
== CHANGELOG ==
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
238 |
= 1.7.32 (2016-04-15) =
|
239 |
* Deal with incorrectly formatted date headers
|
240 |
|
5 |
Plugin URI: http://PostiePlugin.com/
|
6 |
Tags: e-mail, email, post-by-email
|
7 |
Requires at least: 3.3.0
|
8 |
+
Tested up to: 4.6.1
|
9 |
+
Stable tag: 1.8.4
|
10 |
License: GPLv2 or later
|
11 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
12 |
|
16 |
Postie offers many advanced features for creating posts by email,
|
17 |
including the ability to assign categories by name, included pictures and
|
18 |
videos, and automatically strip off signatures. It also has support for both
|
19 |
+
IMAP and POP3, with the option for ssl with both. For usage notes, see the
|
20 |
[other notes](other_notes) page
|
21 |
|
22 |
More info at http://PostiePlugin.com/
|
208 |
Please visit our FAQ page at <a href="http://postieplugin.com/faq/">http://postieplugin.com/faq/</a>
|
209 |
== Upgrade Notice ==
|
210 |
|
211 |
+
= 1.8.0 =
|
212 |
+
The php-imap library has been replaced with logic based on Flourish fMailbox et al, there are some differences in the structure of the mail header array. This affects the
|
213 |
+
postie_filter_email3 and postie_post_before filters.
|
214 |
+
See http://flourishlib.com/docs/fMailbox
|
215 |
+
|
216 |
= 1.6.0 =
|
217 |
Remote cron jobs need to update the URL used to kick off a manual email check. The new URL is http://<mysite>/?postie=get-mail
|
218 |
Accessing http://<mysite>/wp-content/plugins/postie/get_mail.php will now receive a 403 error and a message stating what the new URL should be.
|
240 |
Attachments are now processed in the order they were attached.
|
241 |
|
242 |
== CHANGELOG ==
|
243 |
+
= 1.8.4 (2016-10-17)
|
244 |
+
* General release
|
245 |
+
|
246 |
+
= 1.8.3 (beta 4)
|
247 |
+
* Refactor attachment handling
|
248 |
+
* Gallery shortcode handling is now correct when there are both images and non-images.
|
249 |
+
|
250 |
+
= 1.8.2 (beta 3)
|
251 |
+
* New icon set thanks to Chris Lacey
|
252 |
+
* php-imap replaced by cURL and Socket connection - work sponsored by xsell.net
|
253 |
+
* TLS automatically detected, setting removed
|
254 |
+
* Ignore mail state no longer supported, setting removed
|
255 |
+
* Header array in filter_email3 and postie_post_before filters have changed format
|
256 |
+
* Fixed paragraph detection in plain text when removing newlines
|
257 |
+
* Removed old partially functioning forward detection logic
|
258 |
+
* Transform "[cid:xxx-xx-xx]" references gmail adds to image references.
|
259 |
+
* Add new filter: postie_post_pre that runs after the email is parsed, but before any changes are made.
|
260 |
+
* Fixed improper decoding of encoded headers when multiple 'encoded-words' are present.
|
261 |
+
* Fix to allow multiple #img# reference to the same image.
|
262 |
+
* The default video 1 template is now 'vshortcode'
|
263 |
+
* New option: Text fallback. Falls back to plain if html is blank and vice versa.
|
264 |
+
* Support removing featured image from html
|
265 |
+
|
266 |
+
= 1.8.1 (beta 2)
|
267 |
+
|
268 |
+
= 1.8.0 (beta 1)
|
269 |
+
|
270 |
= 1.7.32 (2016-04-15) =
|
271 |
* Deal with incorrectly formatted date headers
|
272 |
|