Postie - Version 1.8.4

Version Description

(2016-10-17) * General release

Download this release

Release Info

Developer WayneAllen
Plugin Icon 128x128 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 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
- 'generaltemplateselect', 'postie-settings-selected_generaltemplate', 'interesting_document.doc', false);" >
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. This only works if the 'Preferred Text Type' is 'Plain'", '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", '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 if (HasIMAPSupport(false)): ?>
11
- <option value="imap" <?php echo ($input_protocol == "imap") ? "selected='selected' " : "" ?>>IMAP</option>
12
- <option value="pop3-ssl" <?php echo ($input_protocol == "pop3-ssl") ? "selected='selected' " : "" ?>>POP3-SSL</option>
13
- <option value="imap-ssl" <?php echo ($input_protocol == "imap-ssl") ? "selected='selected' " : "" ?>>IMAP-SSL</option>
14
- <?php endif; ?>
15
  </select>
16
- <?php if (!HasIMAPSupport(false)): ?>
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'); ?> - 110<br />
30
- <?php _e("IMAP", 'postie'); ?> - 143<br />
31
- <?php _e("IMAP-SSL", 'postie'); ?>- 993 <br />
32
- <?php _e("POP3-SSL", 'postie'); ?> - 995 <br />
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.5
9
- Stable tag: 1.7.32
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. For usage notes, see the
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
- review video templates for compatibility with current wordpress versions
 
 
 
 
 
 
 
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 1396810 2016-04-15 19:31:58Z WayneAllen $
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 CreatePost($poster, $mimeDecodedEmail, $post_id, &$is_reply, $config, $postmodifiers) {
 
 
189
 
190
- $fulldebug = IsDebugMode();
191
- $fulldebugdump = false;
 
 
 
 
192
 
193
- extract($config);
194
 
195
- $attachments = array(
196
- "html" => array(), //holds the html for each image
197
- "cids" => array(), //holds the cids for HTML email
198
- "image_files" => array() //holds the files for each image
199
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
201
- if (array_key_exists('message-id', $mimeDecodedEmail->headers)) {
202
- DebugEcho("Message Id is :" . htmlentities($mimeDecodedEmail->headers["message-id"]));
 
 
 
 
 
 
 
 
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
- $content = GetContent($mimeDecodedEmail, $attachments, $post_id, $poster, $config);
214
- if ($fulldebug) {
215
- DebugEcho("CreatePost: '$content'");
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 = GetSubject($mimeDecodedEmail, $content, $config);
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->headers) && !empty($mimeDecodedEmail->headers["date"])) {
247
- DebugEcho("date header: {$mimeDecodedEmail->headers['date']}");
248
- $cte = "";
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
- DebugEcho("pre category: $default_categoryid");
 
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, $attachments, $config);
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 = GetParentPostForReply($subject);
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
- //if ($config['prefer_text_type'] == 'plain') {
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, $attachments["html"], $config, $id, "#eimg%#", false);
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
- $details = CreatePost($poster, $mimeDecodedEmail, $post_id, $is_reply, $config, $postmodifiers);
 
 
443
 
444
  $details = apply_filters('postie_post', $details);
445
- $details = apply_filters('postie_post_before', $details, $mimeDecodedEmail->headers);
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
- DisplayEmailPost($details);
461
 
462
- $postid = PostToDB($details, $is_reply, $custom_image_field, $postmodifiers);
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(get_post_format_slugs()))) {
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
- $theDate = null;
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 = GetNameFromEmail($theEmail);
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 GetParentPostForReply(&$subject) {
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 ConfigurePostie() {
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 FetchMail($server = NULL, $port = NULL, $email = NULL, $password = NULL, $protocol = NULL, $offset = NULL, $test = NULL, $deleteMessages = true, $maxemails = 0, $email_tls = false, $ignoreEmailState = true) {
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
- case 'pop3-ssl':
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 = POP3MessageFetch($server, $port, $email, $password, $protocol, $offset, $test, $deleteMessages, $maxemails);
712
  }
713
 
714
  return $emails;
715
  }
716
 
717
- /**
718
- * Handles fetching messages from an imap server
719
- */
720
- function IMAPMessageFetch($server = NULL, $port = NULL, $email = NULL, $password = NULL, $protocol = NULL, $offset = NULL, $test = NULL, $deleteMessages = true, $maxemails = 0, $tls = false, $ignoreMailState = true) {
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
- // loop through messages
738
- for ($i = 1; $i <= $msg_count; $i++) {
739
- $emails[$i] = $mail_server->fetchEmail($i, $ignoreMailState);
740
- if ($deleteMessages) {
741
- $mail_server->deleteMessage($i);
742
  }
743
- if ($maxemails != 0 && $i >= $maxemails) {
744
- DebugEcho("Max emails ($maxemails)");
745
- break;
 
746
  }
747
- }
748
- if ($deleteMessages) {
749
- $mail_server->expungeMessages();
750
- }
751
- //clean up
752
- $mail_server->disconnect();
753
- return $emails;
754
- }
755
 
756
- /**
757
- * Retrieves email via POP3
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
- $emails = array();
763
- $pop3 = new POP3();
764
- if (defined('POSTIE_DEBUG')) {
765
- $pop3->DEBUG = POSTIE_DEBUG;
766
- }
767
 
768
- DebugEcho("Connecting to $server:$port ($protocol)");
769
 
770
- if ($pop3->connect(trim($server), $port)) {
771
- $msg_count = $pop3->login($email, $password);
772
- if ($msg_count === false) {
773
- $msg_count = 0;
774
- }
775
- } else {
776
- if (strpos($pop3->ERROR, "POP3: premature NOOP OK, NOT an RFC 1939 Compliant server") === false) {
777
- EchoError("Mail Connection Time Out. Common Reasons: Server Down, Network Issue, Port/Protocol MisMatch");
778
- }
779
- EchoError("The Server said: $pop3->ERROR");
780
- $msg_count = 0;
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
- //clean up
813
- $pop3->quit();
814
  return $emails;
815
  }
816
 
817
  /**
818
  * This function handles putting the actual entry into the database
819
  */
820
- function PostToDB($details, $isReply, $customImageField, $postmodifiers) {
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 GetContent($part, &$attachments, $post_id, $poster, $config) {
895
- extract($config);
896
- //global $charset, $encoding;
897
- DebugEcho('GetContent: ---- start');
898
  $meta_return = '';
899
- if (property_exists($part, "ctype_primary")) {
900
- DebugEcho("GetContent: primary= " . $part->ctype_primary . ", secondary = " . $part->ctype_secondary);
901
- //DebugDump($part);
 
 
 
 
 
902
  }
 
 
903
 
904
- DecodeBase64Part($part);
 
905
 
906
- //look for banned file names
907
- if (property_exists($part, 'ctype_parameters') && is_array($part->ctype_parameters) && array_key_exists('name', $part->ctype_parameters)) {
908
- if (isBannedFileName($part->ctype_parameters['name'], $config['banned_files_list'])) {
909
- DebugEcho("GetContent: found banned filename");
910
- return NULL;
911
- }
912
  }
913
-
914
- if (property_exists($part, "ctype_primary") && $part->ctype_primary == "application" && $part->ctype_secondary == "octet-stream") {
915
- if (property_exists($part, 'disposition') && $part->disposition == "attachment") {
916
- //nothing
917
- } else {
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
- if (property_exists($part, "ctype_primary") && $part->ctype_primary == "multipart" && $part->ctype_secondary == "appledouble") {
928
- DebugEcho("GetContent: multipart appledouble");
929
- $mimeDecodedEmail = DecodeMIMEMail("Content-Type: multipart/mixed; boundary=" . $part->ctype_parameters["boundary"] . "\n" . $part->body);
930
- filter_PreferedText($mimeDecodedEmail, $config['prefer_text_type']);
931
- filter_AppleFile($mimeDecodedEmail);
932
- foreach ($mimeDecodedEmail->parts as $section) {
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
- $mimetype_primary = "";
951
- $mimetype_secondary = "";
952
 
953
- if (property_exists($part, "ctype_primary")) {
954
- $mimetype_primary = strtolower($part->ctype_primary);
955
- }
956
- if (property_exists($part, "ctype_secondary")) {
957
- $mimetype_secondary = strtolower($part->ctype_secondary);
958
- }
959
 
960
- $typeinfo = wp_check_filetype($filename);
961
- //DebugDump($typeinfo);
962
- if (!empty($typeinfo['type'])) {
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
- DebugEcho("GetContent: mimetype $mimetype_primary/$mimetype_secondary");
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
- case 'text':
996
- DebugEcho("GetContent: ctype_primary: text");
997
- //DebugDump($part);
 
 
 
998
 
999
- $charset = "";
1000
- if (property_exists($part, 'ctype_parameters') && array_key_exists('charset', $part->ctype_parameters) && !empty($part->ctype_parameters['charset'])) {
1001
- $charset = $part->ctype_parameters['charset'];
1002
- DebugEcho("GetContent: text charset: $charset");
1003
- }
1004
 
1005
- $encoding = "";
1006
- if (array_key_exists('content-transfer-encoding', $part->headers) && !empty($part->headers['content-transfer-encoding'])) {
1007
- $encoding = $part->headers['content-transfer-encoding'];
1008
- DebugEcho("GetContent: text encoding: $encoding");
1009
- }
1010
 
1011
- if ($charset !== '' || $encoding !== '') {
1012
- //DebugDump($part);
1013
- $part->body = HandleMessageEncoding($encoding, $charset, $part->body, $config['message_encoding'], $config['message_dequote']);
1014
- if (!empty($charset)) {
1015
- $part->ctype_parameters['charset'] = ""; //reset so we don't double decode
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("GetContent: image Attachement: $filename");
1076
- $file_id = postie_media_handle_upload($part, $post_id, $poster, $config['generate_thumbnails'], $mimetype_primary, $mimetype_secondary);
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
- //DebugDump($part->headers);
1112
- DebugEcho("GetContent: audio Attachement: $filename");
1113
- $file_id = postie_media_handle_upload($part, $post_id, $poster, $config['generate_thumbnails']);
1114
- if (!is_wp_error($file_id)) {
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
- EchoError("audio error: " . $file_id->get_error_message());
 
 
1132
  }
 
1133
  break;
1134
 
1135
  case 'video':
1136
- DebugEcho("GetContent: video Attachement: $filename");
1137
- $file_id = postie_media_handle_upload($part, $post_id, $poster, $config['generate_thumbnails']);
1138
- if (!is_wp_error($file_id)) {
1139
- $file = wp_get_attachment_url($file_id);
1140
- $cid = "";
1141
- if (array_key_exists('content-id', $part->headers)) {
1142
- $cid = trim($part->headers["content-id"], "<>");
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
- EchoError($file_id->get_error_message());
 
 
1161
  }
 
1162
  break;
1163
 
1164
- default:
1165
- DebugEcho("GetContent: found file type: " . $mimetype_primary);
1166
- if (in_array($mimetype_primary, $config['supported_file_types'])) {
1167
- //pgp signature - then forget it
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 filter_Ubb2HTML(&$text) {
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
- foreach ($tagArray as $tagName => $replace) {
1221
- $tagEnd = preg_replace('/\W/Ui', '', $tagName);
1222
- $text = preg_replace("|\[$tagName\](.*)\[/$tagEnd\]|Ui", "$replace[open]\\1$replace[close]", $text);
 
1223
  }
1224
- foreach ($sTagArray as $tagName => $replace) {
1225
- $text = preg_replace("|\[$tagName\]|Ui", "$replace[tag]", $text);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1226
  }
1227
- return $text;
1228
- }
1229
 
1230
- // This function turns Enriched Text into something similar to HTML
1231
- // Very basic at the moment, only supports some functionality and dumps the rest
1232
- // FIXME: fix colours: <color><param>FFFF,C2FE,0374</param>some text </color>
1233
- function filter_Etf2HTML($content) {
1234
 
1235
- $search = array(
1236
- '/<bold>/',
1237
- '/<\/bold>/',
1238
- '/<underline>/',
1239
- '/<\/underline>/',
1240
- '/<italic>/',
1241
- '/<\/italic>/',
1242
- '/<fontfamily><param>.*<\/param>/',
1243
- '/<\/fontfamily>/',
1244
- '/<x-tad-bigger>/',
1245
- '/<\/x-tad-bigger>/',
1246
- '/<bigger>/',
1247
- '</bigger>/',
1248
- '/<color>/',
1249
- '/<\/color>/',
1250
- '/<param>.+<\/param>/'
1251
- );
1252
 
1253
- $replace = array(
1254
- '<b>',
1255
- '</b>',
1256
- '<u>',
1257
- '</u>',
1258
- '<i>',
1259
- '</i>',
1260
- '',
1261
- '',
1262
- '',
1263
- '',
1264
- '',
1265
- '',
1266
- '',
1267
- '',
1268
- ''
1269
- );
1270
- // strip extra line breaks
1271
- $content = preg_replace($search, $replace, $content);
1272
- return trim($content);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 (property_exists($mimeDecodedEmail, "headers") && array_key_exists('from', $mimeDecodedEmail->headers)) {
1308
- $from = RemoveExtraCharactersInEmailAddress(trim($mimeDecodedEmail->headers["from"]));
1309
- $from = apply_filters("postie_filter_email", $from);
 
1310
  DebugEcho("ValidatePoster: post postie_filter_email $from");
1311
 
1312
  $toEmail = '';
1313
- if (isset($mimeDecodedEmail->headers["to"])) {
1314
- $toEmail = RemoveExtraCharactersInEmailAddress(trim($mimeDecodedEmail->headers["to"]));
 
1315
  }
1316
 
1317
  $replytoEmail = '';
1318
- if (isset($mimeDecodedEmail->headers["reply-to"])) {
1319
- $replytoEmail = RemoveExtraCharactersInEmailAddress(trim($mimeDecodedEmail->headers["reply-to"]));
 
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->headers);
1327
  }
1328
 
1329
- if (property_exists($mimeDecodedEmail, "headers")) {
1330
- $from = apply_filters("postie_filter_email3", $from, $mimeDecodedEmail->headers);
1331
  DebugEcho("ValidatePoster: post postie_filter_email3 $from");
1332
  }
1333
 
1334
  $resentFrom = "";
1335
- if (property_exists($mimeDecodedEmail, "headers") && array_key_exists('resent-from', $mimeDecodedEmail->headers)) {
1336
- $resentFrom = RemoveExtraCharactersInEmailAddress(trim($mimeDecodedEmail->headers["resent-from"]));
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(&$content, $config) {
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(&$content, $config) {
 
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 base, removing");
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: '$presig'");
1472
  $html->innertext = $presig;
1473
  } else {
1474
- //DebugEcho("filter_RemoveSignatureWorker: no matches {preg_last_error()} '$pattern' $html->plaintext");
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(&$content, $config) {
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(&$content, $config) {
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
- "LINEBREAK",
1524
- "LINEBREAK",
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->body);
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
- if (property_exists($part, 'ctype_parameters') && is_array($part->ctype_parameters)) {
1767
- if (array_key_exists('name', $part->ctype_parameters) && $part->ctype_parameters['name'] != '') {
1768
- $name = $part->ctype_parameters['name'];
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: attachement id: $id");
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 $upload_error_handler($file, $upload_error_strings[$file['error']]);
1920
  }
1921
  // A file with a valid mime type
1922
  if (empty($file['type'])) {
1923
- return $upload_error_handler($file, __('File type is not allowed', 'postie'));
1924
  }
1925
  // A non-empty file will pass this test.
1926
  if (!($file['size'] > 0 )) {
1927
- return $upload_error_handler($file, __('File is empty. Please upload something more substantial. This error could also be caused by uploads being disabled in your php.ini.', 'postie'));
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 $upload_error_handler($file, __('Specified file failed upload test.', 'postie'));
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 $upload_error_handler($file, __('File type does not meet security guidelines. Try another.', 'postie'));
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 $upload_error_handler($file, $uploads['error']);
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 $upload_error_handler($file, sprintf(__('The uploaded file could not be moved to %s.', 'postie'), $uploads['path']));
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["from"]);
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["boundary"];
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["content-type"] . "\n";
2121
  }
2122
  if (array_key_exists('content-transfer-encoding', $part->headers)) {
2123
- $mailtext .= "Content-Transfer-Encoding: " . $part->headers["content-transfer-encoding"] . "\n";
2124
  }
2125
  if (array_key_exists('content-disposition', $part->headers)) {
2126
- $mailtext .= "Content-Disposition: " . $part->headers["content-disposition"] . "\n";
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 the 'from:' header if available. If not
2209
  * it just returns the username (everything before @)
2210
  */
2211
- function GetNameFromEmail($address) {
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 - before: $template");
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
- DebugFiltersFor('image_downsize'); //possible overrides for image_downsize()
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 - after: $template");
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(&$content, &$attachments, $config) {
2365
- if (count($attachments["cids"])) {
2366
- DebugEcho("ReplaceImageCIDs");
2367
- $used = array();
2368
- foreach ($attachments["cids"] as $key => $info) {
2369
- DebugEcho("looking for $key in content");
2370
- $ckey = str_replace('/', '\/', $key);
2371
- $pattern = "/cid:$ckey/";
2372
- if (preg_match($pattern, $content)) {
2373
- DebugEcho("found $key replacing with $info[0]");
2374
- $content = preg_replace($pattern, $info[0], $content);
2375
- $used[] = $info[1]; //Index of html to ignore
2376
  } else {
2377
- DebugEcho("did not find $key");
2378
  }
2379
  }
2380
- if (count($used) > 0) {
2381
- DebugEcho("# cid attachments: " . count($used));
2382
-
2383
- $html = array();
2384
- $att = array_values($attachments["html"]); //make sure there are numeric indexes
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
- foreach ($attachments['html'] as $key => $value) {
2400
- DebugEcho("Looking for '$value' in attachments");
2401
- if (!in_array($value, $used)) {
2402
- DebugEcho("not found, adding as $key");
2403
- $html[$key] = $value;
2404
- }
2405
- }
2406
- DebugEcho('dumping $html[]');
2407
- DebugDump($html);
2408
- $attachments["html"] = $html;
2409
- //DebugDump($attachments);
2410
  } else {
2411
- DebugEcho("no cid attachments");
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(&$content, $attachments, $config, $post_id, $image_pattern, $autoadd_images) {
 
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
- foreach ($attachments as $attachementName => $imageTemplate) {
 
 
 
 
 
 
 
 
 
2458
  // looks for ' #img1# ' etc... and replaces with image
2459
- $img_placeholder_temp = rtrim(str_replace("%", intval($startIndex + $i), $image_pattern), '#');
2460
-
2461
- DebugEcho("img_placeholder_temp: $img_placeholder_temp");
2462
- if (stristr($content, $img_placeholder_temp)) {
2463
- // look for caption
2464
- DebugEcho("Found $img_placeholder_temp");
2465
- $caption = '';
2466
- if (preg_match("/$img_placeholder_temp caption=(.*?)#/i", $content, $matches)) {
2467
- //DebugDump($matches);
2468
- $caption = trim($matches[1]);
2469
- if (strlen($caption) > 2 && ($caption[0] == "'" || $caption[0] == '"')) {
2470
- $caption = substr($caption, 1, strlen($caption) - 2);
2471
- }
2472
- DebugEcho("caption: $caption");
 
 
 
2473
 
2474
- if (count($images) > $i) {
2475
- DebugEcho("Adding alt text to image {$images[$i]->ID}");
2476
- update_post_meta($images[$i]->ID, '_wp_attachment_image_alt', $caption);
2477
- }
2478
 
2479
- $img_placeholder_temp = substr($matches[0], 0, -1);
2480
- DebugEcho($img_placeholder_temp);
2481
- } else {
2482
- DebugEcho("No caption found");
2483
- }
2484
- DebugEcho("parameterize templete: " . $imageTemplate);
2485
- $imageTemplate = mb_str_replace('{CAPTION}', htmlspecialchars($caption, ENT_QUOTES), $imageTemplate);
2486
- DebugEcho("populated template: " . $imageTemplate);
2487
 
2488
- $img_placeholder_temp.='#';
2489
 
2490
- $content = str_ireplace($img_placeholder_temp, $imageTemplate, $content);
2491
- DebugEcho("post replace: $content");
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 GetSubject(&$mimeDecodedEmail, &$content, $config) {
2526
  //assign the default title/subject
2527
- if (!array_key_exists('subject', $mimeDecodedEmail->headers) || empty($mimeDecodedEmail->headers['subject'])) {
2528
- DebugEcho("No subject in email");
2529
- DebugDump($mimeDecodedEmail->headers);
2530
- if ($allow_subject_in_mail) {
2531
- list($subject, $content) = tag_Subject($content, $default_title);
2532
  } else {
2533
- DebugEcho("Using default subject");
2534
  $subject = $config['default_title'];
2535
  }
2536
- $mimeDecodedEmail->headers['subject'] = $subject;
2537
  } else {
2538
- $subject = $mimeDecodedEmail->headers['subject'];
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("Subject: $subject");
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 DisplayEmailPost($details) {
2732
  //DebugDump($details);
2733
  // Report
2734
- DebugEcho('Post Author: ' . $details["post_author"]);
2735
- DebugEcho('Date: ' . $details["post_date"]);
2736
- foreach ($details["post_category"] as $category) {
2737
  DebugEcho('Category: ' . $category);
2738
  }
2739
- DebugEcho('Ping Status: ' . $details["ping_status"]);
2740
- DebugEcho('Comment Status: ' . $details["comment_status"]);
2741
- DebugEcho('Subject: ' . $details["post_title"]);
2742
- DebugEcho('Postname: ' . $details["post_name"]);
2743
- DebugEcho('Post Id: ' . $details["ID"]);
2744
- DebugEcho('Post Type: ' . $details["post_type"]); /* Added by Raam Dev <raam@raamdev.com> */
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["role_access"]);
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' => 'simple_link',
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["table_prefix"] . "postie_config'");
2973
  if ($wpdb->num_rows > 0) {
2974
- $data = $wpdb->get_results("SELECT label,value FROM " . $GLOBALS["table_prefix"] . "postie_config;");
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["ADMIN_USERNAME"])) {
2996
- $config["ADMIN_USERNAME"] = 'admin';
2997
  }
2998
- if (!isset($config["PREFER_TEXT_TYPE"])) {
2999
- $config["PREFER_TEXT_TYPE"] = "plain";
3000
  }
3001
- if (!isset($config["DEFAULT_TITLE"])) {
3002
- $config["DEFAULT_TITLE"] = "Live From The Field";
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 u sed to retrieve the old (pre 1.4) config
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 DebugEmailOutput($email, $mimeDecodedEmail) {
3355
  if (IsDebugMode()) {
3356
  //DebugDump($email);
3357
  //DebugDump($mimeDecodedEmail);
3358
 
3359
- $dname = POSTIE_ROOT . DIRECTORY_SEPARATOR . "test_emails" . DIRECTORY_SEPARATOR;
3360
  if (is_dir($dname)) {
3361
- $fname = $dname . sanitize_file_name($mimeDecodedEmail->headers["message-id"]);
3362
- $file = fopen($fname . ".txt ", "w");
3363
- fwrite($file, $email);
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
- * Special Vodafone handler - their messages are mostly vendor trash - this strips them down.
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 DebugFiltersFor($hook = '') {
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
- get_currentuserinfo();
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
- switch (strtolower($config["input_protocol"])) {
 
 
 
 
 
 
3485
  case 'imap':
3486
  case 'imap-ssl':
3487
- case 'pop3-ssl':
3488
- if (!HasIMAPSupport()) {
3489
- EchoError("Sorry - you do not have IMAP php module installed - it is required for this mail setting.");
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
- DebugEcho("Successful " . strtoupper($config['input_protocol']) . " connection on port {$config["mail_server_port"]}", true);
3501
- DebugEcho("# of waiting messages: " . $mail_server->getNumberOfMessages(), true);
3502
- $mail_server->disconnect();
3503
  }
 
 
 
 
 
 
 
3504
  }
3505
  break;
 
3506
  case 'pop3':
3507
- default:
3508
- require_once(ABSPATH . WPINC . DIRECTORY_SEPARATOR . 'class-pop3.php');
3509
- $pop3 = new POP3();
3510
- if (defined('POSTIE_DEBUG')) {
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
- DebugEcho("# of waiting messages: $msgs", true);
3530
  }
3531
- $pop3->quit();
 
 
 
 
 
 
 
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 = array();
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
- $conninfo = apply_filters('postie_preconnect', $conninfo);
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
- $mimeDecodedEmail = DecodeMIMEMail($email);
3620
-
3621
- DebugEmailOutput($email, $mimeDecodedEmail);
 
 
 
 
 
 
 
 
3622
 
3623
  //Check poster to see if a valid person
3624
- $poster = ValidatePoster($mimeDecodedEmail, $config);
3625
  if (!empty($poster)) {
3626
- PostEmail($poster, $mimeDecodedEmail, $config);
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. Signifigantly upgrades the Post by Email features of Word Press.
7
- Version: 1.7.32
8
  Author: Wayne Allen
9
  Author URI: http://PostiePlugin.com/
10
  License: GPL2
@@ -28,12 +28,28 @@
28
  */
29
 
30
  /*
31
- $Id: postie.php 1396811 2016-04-15 19:40:39Z WayneAllen $
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.7.32');
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 Admin username is not a valid WordPress login. Postie may reject emails if this is not corrected.', 'postie');
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.5
9
- Stable tag: 1.7.32
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. For usage notes, see the
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