SendGrid - Version 1.9.4

Version Description

  • Added Unsubscribe Group option
  • Improved email validation
Download this release

Release Info

Developer team-rs
Plugin Icon 128x128 SendGrid
Version 1.9.4
Comparing to
See all releases

Code changes from version 1.9.3 to 1.9.4

lib/class-sendgrid-nlvx-widget.php CHANGED
@@ -2,12 +2,19 @@
2
 
3
  require_once plugin_dir_path( __FILE__ ) . 'class-sendgrid-tools.php';
4
  require_once plugin_dir_path( __FILE__ ) . 'class-sendgrid-nlvx.php';
 
 
 
5
 
6
  class SendGrid_NLVX_Widget extends WP_Widget {
7
- const DEFAULT_TITLE = 'Newsletter Subscription';
8
- const DEFAULT_MESSAGE = 'If you want to subscribe to our monthly newsletter, please submit the form below.';
9
- const DEFAULT_ERROR_MESSAGE = 'An error occured when processing your details. Please try again.';
10
- const DEFAULT_SUBSCRIBE_MESSAGE = 'An email has been sent to your address. Please check your inbox in order to confirm your subscription.';
 
 
 
 
11
 
12
  /**
13
  * Widget class constructor
@@ -50,6 +57,12 @@ class SendGrid_NLVX_Widget extends WP_Widget {
50
  $error_text = self::DEFAULT_ERROR_MESSAGE;
51
  }
52
 
 
 
 
 
 
 
53
  if ( isset( $instance['success_text'] ) ) {
54
  $success_text = $instance['success_text'];
55
  } else {
@@ -74,6 +87,12 @@ class SendGrid_NLVX_Widget extends WP_Widget {
74
  echo '<input class="widefat" id="' . $this->get_field_id( 'error_text' ) . '" name="' . $this->get_field_name( 'error_text' ). '" type="text" value="' . esc_attr( $error_text ) . '" />';
75
  echo '</p>';
76
 
 
 
 
 
 
 
77
  // Widget success text input
78
  echo '<p>';
79
  echo '<label for="' . $this->get_field_id( 'success_text' ) . '">' . _e( 'Message to display for success:' ) . '</label>';
@@ -91,10 +110,11 @@ class SendGrid_NLVX_Widget extends WP_Widget {
91
  */
92
  public function update( $new_instance, $old_instance ) {
93
  $instance = array();
94
- $instance['title'] = ( ! empty( $new_instance['title'] ) ) ? strip_tags( $new_instance['title'] ) : '';
95
- $instance['text'] = ( ! empty( $new_instance['text'] ) ) ? $new_instance['text'] : '';
96
- $instance['error_text'] = ( ! empty( $new_instance['error_text'] ) ) ? $new_instance['error_text'] : '';
97
- $instance['success_text'] = ( ! empty( $new_instance['success_text'] ) ) ? $new_instance['success_text'] : '';
 
98
 
99
  return $instance;
100
  }
@@ -123,6 +143,11 @@ class SendGrid_NLVX_Widget extends WP_Widget {
123
  $error_text = apply_filters( 'widget_text', $instance['error_text'] );
124
  }
125
 
 
 
 
 
 
126
  $success_text = self::DEFAULT_SUBSCRIBE_MESSAGE;
127
  if ( isset( $instance['success_text'] ) ) {
128
  $success_text = apply_filters( 'widget_text', $instance['success_text'] );
@@ -137,8 +162,12 @@ class SendGrid_NLVX_Widget extends WP_Widget {
137
 
138
  // Form was submitted
139
  if ( isset( $_POST['sendgrid_mc_email'] ) ) {
140
- if ( $this->process_subscription( $_POST ) ) {
 
141
  echo '<p class="sendgrid_widget_text"> ' . $success_text . ' </p>';
 
 
 
142
  } else {
143
  echo '<p class="sendgrid_widget_text"> ' . $error_text . ' </p>';
144
  $this->display_form();
@@ -164,27 +193,44 @@ class SendGrid_NLVX_Widget extends WP_Widget {
164
  * @return void
165
  */
166
  private function process_subscription( $params ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  // Bad call
168
- if ( ! isset( $_POST['sendgrid_mc_email'] ) or ! Sendgrid_Tools::is_valid_email( $_POST['sendgrid_mc_email'] ) ) {
169
- return false;
170
  }
171
 
172
  if ( 'true' == Sendgrid_Tools::get_mc_opt_req_fname_lname() and 'true' == Sendgrid_Tools::get_mc_opt_incl_fname_lname() ) {
173
  if ( ! isset( $_POST['sendgrid_mc_first_name'] ) or empty( $_POST['sendgrid_mc_first_name'] ) ) {
174
- return false;
175
  }
176
  if ( ! isset( $_POST['sendgrid_mc_last_name'] ) or empty( $_POST['sendgrid_mc_last_name'] ) ) {
177
- return false;
178
  }
179
  }
180
 
181
  if ( isset( $_POST['sendgrid_mc_first_name'] ) and isset( $_POST['sendgrid_mc_last_name'] ) ) {
182
- Sendgrid_OptIn_API_Endpoint::send_confirmation_email( $_POST['sendgrid_mc_email'], $_POST['sendgrid_mc_first_name'], $_POST['sendgrid_mc_last_name'] );
183
  } else {
184
- Sendgrid_OptIn_API_Endpoint::send_confirmation_email( $_POST['sendgrid_mc_email'] );
185
  }
186
 
187
- return true;
188
  }
189
 
190
  /**
@@ -219,7 +265,7 @@ class SendGrid_NLVX_Widget extends WP_Widget {
219
 
220
  echo '<div class="sendgrid-mc-field">';
221
  echo '<label for="sendgrid_mc_email">Email<sup>*</sup> :&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </label>';
222
- echo '<input id="sendgrid_mc_email" name="sendgrid_mc_email" type="email" value="" required/>';
223
  echo '</div>';
224
 
225
  echo '<div class="sendgrid-mc-button">';
2
 
3
  require_once plugin_dir_path( __FILE__ ) . 'class-sendgrid-tools.php';
4
  require_once plugin_dir_path( __FILE__ ) . 'class-sendgrid-nlvx.php';
5
+ require_once plugin_dir_path( __FILE__ ) . '../vendor/punycode/Punycode.php';
6
+
7
+ use SendGridTrueBV\Punycode;
8
 
9
  class SendGrid_NLVX_Widget extends WP_Widget {
10
+ const DEFAULT_TITLE = 'Newsletter Subscription';
11
+ const DEFAULT_MESSAGE = 'If you want to subscribe to our monthly newsletter, please submit the form below.';
12
+ const DEFAULT_ERROR_MESSAGE = 'An error occured when processing your details. Please try again.';
13
+ const DEFAULT_ERROR_EMAIL_MESSAGE = 'Invalid email address.';
14
+ const DEFAULT_SUBSCRIBE_MESSAGE = 'An email has been sent to your address. Please check your inbox in order to confirm your subscription.';
15
+ const INVALID_EMAIL_ERROR = 'email_invalid';
16
+ const SUCCESS_EMAIL_SEND = 'email_sent';
17
+ const ERROR_EMAIL_SEND = 'email_error_send';
18
 
19
  /**
20
  * Widget class constructor
57
  $error_text = self::DEFAULT_ERROR_MESSAGE;
58
  }
59
 
60
+ if ( isset( $instance['error_email_text'] ) ) {
61
+ $error_email_text = $instance['error_email_text'];
62
+ } else {
63
+ $error_email_text = self::DEFAULT_ERROR_EMAIL_MESSAGE;
64
+ }
65
+
66
  if ( isset( $instance['success_text'] ) ) {
67
  $success_text = $instance['success_text'];
68
  } else {
87
  echo '<input class="widefat" id="' . $this->get_field_id( 'error_text' ) . '" name="' . $this->get_field_name( 'error_text' ). '" type="text" value="' . esc_attr( $error_text ) . '" />';
88
  echo '</p>';
89
 
90
+ // Widget email error text input
91
+ echo '<p>';
92
+ echo '<label for="' . $this->get_field_id( 'error_email_text' ) . '">' . _e( 'Message to display for invalid email address:' ) . '</label>';
93
+ echo '<input class="widefat" id="' . $this->get_field_id( 'error_email_text' ) . '" name="' . $this->get_field_name( 'error_email_text' ). '" type="text" value="' . esc_attr( $error_email_text ) . '" />';
94
+ echo '</p>';
95
+
96
  // Widget success text input
97
  echo '<p>';
98
  echo '<label for="' . $this->get_field_id( 'success_text' ) . '">' . _e( 'Message to display for success:' ) . '</label>';
110
  */
111
  public function update( $new_instance, $old_instance ) {
112
  $instance = array();
113
+ $instance['title'] = ( ! empty( $new_instance['title'] ) ) ? strip_tags( $new_instance['title'] ) : '';
114
+ $instance['text'] = ( ! empty( $new_instance['text'] ) ) ? $new_instance['text'] : '';
115
+ $instance['error_text'] = ( ! empty( $new_instance['error_text'] ) ) ? $new_instance['error_text'] : '';
116
+ $instance['error_email_text'] = ( ! empty( $new_instance['error_email_text'] ) ) ? $new_instance['error_email_text'] : '';
117
+ $instance['success_text'] = ( ! empty( $new_instance['success_text'] ) ) ? $new_instance['success_text'] : '';
118
 
119
  return $instance;
120
  }
143
  $error_text = apply_filters( 'widget_text', $instance['error_text'] );
144
  }
145
 
146
+ $error_email_text = self::DEFAULT_ERROR_EMAIL_MESSAGE;
147
+ if ( isset( $instance['error_email_text'] ) ) {
148
+ $error_email_text = apply_filters( 'widget_text', $instance['error_email_text'] );
149
+ }
150
+
151
  $success_text = self::DEFAULT_SUBSCRIBE_MESSAGE;
152
  if ( isset( $instance['success_text'] ) ) {
153
  $success_text = apply_filters( 'widget_text', $instance['success_text'] );
162
 
163
  // Form was submitted
164
  if ( isset( $_POST['sendgrid_mc_email'] ) ) {
165
+ $process_form_reponse = $this->process_subscription( $_POST );
166
+ if ( self::SUCCESS_EMAIL_SEND == $process_form_reponse ) {
167
  echo '<p class="sendgrid_widget_text"> ' . $success_text . ' </p>';
168
+ } elseif ( self::INVALID_EMAIL_ERROR == $process_form_reponse ) {
169
+ echo '<p class="sendgrid_widget_text"> ' . $error_email_text . ' </p>';
170
+ $this->display_form();
171
  } else {
172
  echo '<p class="sendgrid_widget_text"> ' . $error_text . ' </p>';
173
  $this->display_form();
193
  * @return void
194
  */
195
  private function process_subscription( $params ) {
196
+ $email_split = explode( "@", $_POST['sendgrid_mc_email'] );
197
+
198
+ if ( isset( $email_split[1] ) ) {
199
+ $email_domain = $email_split[1];
200
+
201
+ try {
202
+ $Punycode = new Punycode();
203
+ $email_domain = $Punycode->decode( $email_split[1] );
204
+ }
205
+ catch ( Exception $e ) {
206
+ }
207
+
208
+ $email = $email_split[0] . '@' . $email_domain;
209
+ } else {
210
+ $email = $_POST['sendgrid_mc_email'];
211
+ }
212
+
213
  // Bad call
214
+ if ( ! isset( $email ) or ! Sendgrid_Tools::is_valid_email( $email ) ) {
215
+ return self::INVALID_EMAIL_ERROR;
216
  }
217
 
218
  if ( 'true' == Sendgrid_Tools::get_mc_opt_req_fname_lname() and 'true' == Sendgrid_Tools::get_mc_opt_incl_fname_lname() ) {
219
  if ( ! isset( $_POST['sendgrid_mc_first_name'] ) or empty( $_POST['sendgrid_mc_first_name'] ) ) {
220
+ return self::ERROR_EMAIL_SEND;
221
  }
222
  if ( ! isset( $_POST['sendgrid_mc_last_name'] ) or empty( $_POST['sendgrid_mc_last_name'] ) ) {
223
+ return self::ERROR_EMAIL_SEND;
224
  }
225
  }
226
 
227
  if ( isset( $_POST['sendgrid_mc_first_name'] ) and isset( $_POST['sendgrid_mc_last_name'] ) ) {
228
+ Sendgrid_OptIn_API_Endpoint::send_confirmation_email( $email, $_POST['sendgrid_mc_first_name'], $_POST['sendgrid_mc_last_name'] );
229
  } else {
230
+ Sendgrid_OptIn_API_Endpoint::send_confirmation_email( $email );
231
  }
232
 
233
+ return self::SUCCESS_EMAIL_SEND;
234
  }
235
 
236
  /**
265
 
266
  echo '<div class="sendgrid-mc-field">';
267
  echo '<label for="sendgrid_mc_email">Email<sup>*</sup> :&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </label>';
268
+ echo '<input id="sendgrid_mc_email" name="sendgrid_mc_email" value="" required/>';
269
  echo '</div>';
270
 
271
  echo '<div class="sendgrid-mc-button">';
lib/class-sendgrid-settings.php CHANGED
@@ -127,6 +127,7 @@ class Sendgrid_Settings {
127
  $template = stripslashes( Sendgrid_Tools::get_template() );
128
  $port = Sendgrid_Tools::get_port();
129
  $content_type = Sendgrid_Tools::get_content_type();
 
130
  $stats_categories = stripslashes( Sendgrid_Tools::get_stats_categories() );
131
 
132
  $mc_api_key = Sendgrid_Tools::get_mc_api_key();
@@ -265,6 +266,13 @@ class Sendgrid_Settings {
265
  }
266
  }
267
 
 
 
 
 
 
 
 
268
  $is_env_auth_method = defined( 'SENDGRID_AUTH_METHOD' );
269
  $is_env_send_method = defined( 'SENDGRID_SEND_METHOD' );
270
  $is_env_username = defined( 'SENDGRID_USERNAME' );
@@ -272,6 +280,7 @@ class Sendgrid_Settings {
272
  $is_env_api_key = defined( 'SENDGRID_API_KEY' );
273
  $is_env_port = defined( 'SENDGRID_PORT' );
274
  $is_env_content_type = defined( 'SENDGRID_CONTENT_TYPE' );
 
275
  $is_env_mc_api_key = defined( 'SENDGRID_MC_API_KEY' );
276
  $is_env_mc_list_id = defined( 'SENDGRID_MC_LIST_ID' );
277
  $is_env_mc_opt_use_transactional = defined( 'SENDGRID_MC_OPT_USE_TRANSACTIONAL' );
@@ -601,6 +610,10 @@ class Sendgrid_Settings {
601
  update_option( 'sendgrid_content_type', $params['content_type'] );
602
  }
603
 
 
 
 
 
604
  if( isset( $response ) and $response['status'] == 'error') {
605
  return $response;
606
  }
127
  $template = stripslashes( Sendgrid_Tools::get_template() );
128
  $port = Sendgrid_Tools::get_port();
129
  $content_type = Sendgrid_Tools::get_content_type();
130
+ $unsubscribe_group_id = Sendgrid_Tools::get_unsubscribe_group();
131
  $stats_categories = stripslashes( Sendgrid_Tools::get_stats_categories() );
132
 
133
  $mc_api_key = Sendgrid_Tools::get_mc_api_key();
266
  }
267
  }
268
 
269
+ // get unsubscribe groups
270
+ $unsubscribe_groups = Sendgrid_Tools::get_all_unsubscribe_groups();
271
+ $no_permission_on_unsubscribe_groups = false;
272
+ if ( ( 'apikey' == $auth_method ) and ( 'true' != Sendgrid_Tools::get_asm_permission() ) ) {
273
+ $no_permission_on_unsubscribe_groups = true;
274
+ }
275
+
276
  $is_env_auth_method = defined( 'SENDGRID_AUTH_METHOD' );
277
  $is_env_send_method = defined( 'SENDGRID_SEND_METHOD' );
278
  $is_env_username = defined( 'SENDGRID_USERNAME' );
280
  $is_env_api_key = defined( 'SENDGRID_API_KEY' );
281
  $is_env_port = defined( 'SENDGRID_PORT' );
282
  $is_env_content_type = defined( 'SENDGRID_CONTENT_TYPE' );
283
+ $is_env_unsubscribe_group = defined( 'SENDGRID_UNSUBSCRIBE_GROUP' );
284
  $is_env_mc_api_key = defined( 'SENDGRID_MC_API_KEY' );
285
  $is_env_mc_list_id = defined( 'SENDGRID_MC_LIST_ID' );
286
  $is_env_mc_opt_use_transactional = defined( 'SENDGRID_MC_OPT_USE_TRANSACTIONAL' );
610
  update_option( 'sendgrid_content_type', $params['content_type'] );
611
  }
612
 
613
+ if ( isset( $params['unsubscribe_group'] ) ) {
614
+ Sendgrid_Tools::set_unsubscribe_group( $params['unsubscribe_group'] );
615
+ }
616
+
617
  if( isset( $response ) and $response['status'] == 'error') {
618
  return $response;
619
  }
lib/class-sendgrid-tools.php CHANGED
@@ -98,14 +98,14 @@ class Sendgrid_Tools
98
  return false;
99
  }
100
 
101
- if( ! isset( $response['scopes'] ) ) {
102
  return false;
103
  }
104
 
105
- foreach( $scopes as $scope ) {
106
- if( !in_array( $scope, $response['scopes'] ) ) {
107
- return false;
108
- }
109
  }
110
 
111
  return true;
@@ -139,7 +139,14 @@ class Sendgrid_Tools
139
  return true;
140
  }
141
 
142
- if( ! Sendgrid_Tools::check_api_key_scopes( $apikey, array( "mail.send" ) ) ) {
 
 
 
 
 
 
 
143
  return false;
144
  }
145
 
@@ -801,8 +808,76 @@ class Sendgrid_Tools
801
  if ( defined( 'SENDGRID_CONTENT_TYPE' ) ) {
802
  return SENDGRID_CONTENT_TYPE;
803
  } else {
804
- return get_option('sendgrid_content_type');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
805
  }
 
 
806
  }
807
 
808
  /**
@@ -874,7 +949,11 @@ class Sendgrid_Tools
874
  */
875
  public static function is_valid_email( $email )
876
  {
877
- return filter_var( $email, FILTER_VALIDATE_EMAIL );
 
 
 
 
878
  }
879
 
880
  /**
98
  return false;
99
  }
100
 
101
+ if ( ! isset( $response['scopes'] ) ) {
102
  return false;
103
  }
104
 
105
+ foreach ( $scopes as $scope ) {
106
+ if ( ! in_array( $scope, $response['scopes'] ) ) {
107
+ return false;
108
+ }
109
  }
110
 
111
  return true;
139
  return true;
140
  }
141
 
142
+ // check unsubscribe group permission
143
+ if ( Sendgrid_Tools::check_api_key_scopes( $apikey, array( "asm.groups.read" ) ) ) {
144
+ update_option( 'sendgrid_asm_permission', 'true' );
145
+ } else {
146
+ update_option( 'sendgrid_asm_permission', 'false' );
147
+ }
148
+
149
+ if ( ! Sendgrid_Tools::check_api_key_scopes( $apikey, array( "mail.send" ) ) ) {
150
  return false;
151
  }
152
 
808
  if ( defined( 'SENDGRID_CONTENT_TYPE' ) ) {
809
  return SENDGRID_CONTENT_TYPE;
810
  } else {
811
+ return get_option( 'sendgrid_content_type' );
812
+ }
813
+ }
814
+
815
+ /**
816
+ * Sets the unsubscribe group in the database
817
+ *
818
+ * @param type string $unsubscribe_group
819
+ *
820
+ * @return bool
821
+ */
822
+ public static function set_unsubscribe_group( $unsubscribe_group )
823
+ {
824
+ return update_option( 'sendgrid_unsubscribe_group', $unsubscribe_group );
825
+ }
826
+
827
+ /**
828
+ * Return unsubscribe group from the database or global variable
829
+ *
830
+ * @return mixed unsubscribe group string, false if the value is not found
831
+ */
832
+ public static function get_unsubscribe_group()
833
+ {
834
+ if ( defined( 'SENDGRID_UNSUBSCRIBE_GROUP' ) ) {
835
+ return SENDGRID_UNSUBSCRIBE_GROUP;
836
+ } else {
837
+ return get_option( 'sendgrid_unsubscribe_group' );
838
+ }
839
+ }
840
+
841
+ /**
842
+ * Get asm_permission value from db
843
+ *
844
+ * @return mixed asm_permission value
845
+ */
846
+ public static function get_asm_permission()
847
+ {
848
+ return get_option( 'sendgrid_asm_permission' );
849
+ }
850
+
851
+ /**
852
+ * Returns the unsubscribe groups from SendGrid
853
+ *
854
+ * @return mixed an array of groups if the request is successful, false otherwise.
855
+ */
856
+ public static function get_all_unsubscribe_groups()
857
+ {
858
+ $url = 'v3/asm/groups';
859
+
860
+ $parameters['auth_method'] = Sendgrid_Tools::get_auth_method();
861
+ $parameters['api_username'] = Sendgrid_Tools::get_username();
862
+ $parameters['api_password'] = Sendgrid_Tools::get_password();
863
+ $parameters['apikey'] = Sendgrid_Tools::get_api_key();
864
+
865
+ if ( ( 'apikey' == $parameters['auth_method'] ) and ( 'true' != self::get_asm_permission() ) ) {
866
+ return false;
867
+ }
868
+
869
+ $response = Sendgrid_Tools::do_request( $url, $parameters );
870
+
871
+ if ( ! $response ) {
872
+ return false;
873
+ }
874
+
875
+ $response = json_decode( $response, true );
876
+ if ( isset( $response['error'] ) or ( isset( $response['errors'] ) and isset( $response['errors'][0]['message'] ) ) ) {
877
+ return false;
878
  }
879
+
880
+ return $response;
881
  }
882
 
883
  /**
949
  */
950
  public static function is_valid_email( $email )
951
  {
952
+ if ( filter_var( $email, FILTER_VALIDATE_EMAIL ) and ( SendGrid_ThirdParty::is_email( $email ) ) ) {
953
+ return true;
954
+ }
955
+
956
+ return false;
957
  }
958
 
959
  /**
lib/sendgrid/sendgrid-wp-mail.php CHANGED
@@ -392,6 +392,12 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array()
392
  $mail->setAttachments( $attached_files );
393
  }
394
 
 
 
 
 
 
 
395
  $sendgrid = Sendgrid_WP::get_instance();
396
 
397
  if ( ! $sendgrid ) {
392
  $mail->setAttachments( $attached_files );
393
  }
394
 
395
+ // set unsubscribe group
396
+ $unsubscribe_group_id = Sendgrid_Tools::get_unsubscribe_group();
397
+ if ( $unsubscribe_group_id and $unsubscribe_group_id != 0 ) {
398
+ $mail->setAsmGroupId( $unsubscribe_group_id );
399
+ }
400
+
401
  $sendgrid = Sendgrid_WP::get_instance();
402
 
403
  if ( ! $sendgrid ) {
readme.txt CHANGED
@@ -4,7 +4,7 @@ Donate link: http://sendgrid.com/
4
  Tags: email, email reliability, email templates, sendgrid, smtp, transactional email, wp_mail,email infrastructure, email marketing, marketing email, deliverability, email deliverability, email delivery, email server, mail server, email integration, cloud email
5
  Requires at least: 4.2
6
  Tested up to: 4.5
7
- Stable tag: 1.9.3
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
@@ -69,6 +69,8 @@ SendGrid settings can optionally be defined as global variables (wp-config.php):
69
  * Categories: define('SENDGRID_CATEGORIES', 'category_1,category_2');
70
  * Template: define('SENDGRID_TEMPLATE', 'templateID');
71
  * Content-type: define('SENDGRID_CONTENT_TYPE', 'html');
 
 
72
 
73
  3. Set widget related settings:
74
  * Marketing Campaigns API key: define('SENDGRID_MC_API_KEY', 'sendgrid_mc_api_key');
@@ -228,6 +230,9 @@ You need to enable the use of the First Name and Last Name fields from the setti
228
 
229
  == Changelog ==
230
 
 
 
 
231
  = 1.9.3 =
232
  * Added BuddyPress integration
233
  * MC API Key is now saved on focusout
@@ -347,6 +352,9 @@ You need to enable the use of the First Name and Last Name fields from the setti
347
 
348
  == Upgrade notice ==
349
 
 
 
 
350
  = 1.9.3 =
351
  * Added BuddyPress integration
352
  * MC API Key is now saved on focusout
4
  Tags: email, email reliability, email templates, sendgrid, smtp, transactional email, wp_mail,email infrastructure, email marketing, marketing email, deliverability, email deliverability, email delivery, email server, mail server, email integration, cloud email
5
  Requires at least: 4.2
6
  Tested up to: 4.5
7
+ Stable tag: 1.9.4
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
69
  * Categories: define('SENDGRID_CATEGORIES', 'category_1,category_2');
70
  * Template: define('SENDGRID_TEMPLATE', 'templateID');
71
  * Content-type: define('SENDGRID_CONTENT_TYPE', 'html');
72
+ * Unsubscribe Group: define('SENDGRID_UNSUBSCRIBE_GROUP', 'unsubscribeGroupId');
73
+
74
 
75
  3. Set widget related settings:
76
  * Marketing Campaigns API key: define('SENDGRID_MC_API_KEY', 'sendgrid_mc_api_key');
230
 
231
  == Changelog ==
232
 
233
+ = 1.9.4 =
234
+ * Added Unsubscribe Group option
235
+ * Improved email validation
236
  = 1.9.3 =
237
  * Added BuddyPress integration
238
  * MC API Key is now saved on focusout
352
 
353
  == Upgrade notice ==
354
 
355
+ = 1.9.4 =
356
+ * Added Unsubscribe Group option
357
+ * Improved email validation
358
  = 1.9.3 =
359
  * Added BuddyPress integration
360
  * MC API Key is now saved on focusout
vendor/autoload.php CHANGED
@@ -1,3 +1,4 @@
1
  <?php
2
  require_once plugin_dir_path( __FILE__ ) . 'smtpapi-php/Smtpapi/Header.php';
3
- require_once plugin_dir_path( __FILE__ ) . 'sendgrid-php/SendGrid/Email.php';
 
1
  <?php
2
  require_once plugin_dir_path( __FILE__ ) . 'smtpapi-php/Smtpapi/Header.php';
3
+ require_once plugin_dir_path( __FILE__ ) . 'sendgrid-php/SendGrid/Email.php';
4
+ require_once plugin_dir_path( __FILE__ ) . 'is_mail.php';
vendor/is_mail.php ADDED
@@ -0,0 +1,1182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * To validate an email address according to RFCs 5321, 5322 and others
4
+ *
5
+ * Copyright © 2008-2011, Dominic Sayers <br>
6
+ * Test schema documentation Copyright © 2011, Daniel Marschall <br>
7
+ * All rights reserved.
8
+ *
9
+ * Redistribution and use in source and binary forms, with or without modification,
10
+ * are permitted provided that the following conditions are met:
11
+ *
12
+ * - Redistributions of source code must retain the above copyright notice,
13
+ * this list of conditions and the following disclaimer.
14
+ * - Redistributions in binary form must reproduce the above copyright notice,
15
+ * this list of conditions and the following disclaimer in the documentation
16
+ * and/or other materials provided with the distribution.
17
+ * - Neither the name of Dominic Sayers nor the names of its contributors may be
18
+ * used to endorse or promote products derived from this software without
19
+ * specific prior written permission.
20
+ *
21
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
22
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
25
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
28
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
+ *
32
+ * @package is_email
33
+ * @author Dominic Sayers <dominic@sayers.cc>
34
+ * @copyright 2008-2011 Dominic Sayers
35
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
36
+ * @link http://www.dominicsayers.com/isemail
37
+ * @version 3.04.1 - Changed my link to http://isemail.info throughout
38
+ */
39
+
40
+ // The quality of this code has been improved greatly by using PHPLint
41
+ // Copyright (c) 2010 Umberto Salsi
42
+ // This is free software; see the license for copying conditions.
43
+ // More info: http://www.icosaedro.it/phplint/
44
+ /*.
45
+ require_module 'standard';
46
+ require_module 'pcre';
47
+ .*/
48
+
49
+ if (!defined('ISEMAIL_VALID')) {
50
+ /*:diagnostic constants start:*/
51
+ // This part of the code is generated using data from test/meta.xml. Beware of making manual alterations
52
+ // Categories
53
+ define('ISEMAIL_VALID_CATEGORY', 1);
54
+ define('ISEMAIL_DNSWARN', 7);
55
+ define('ISEMAIL_RFC5321', 15);
56
+ define('ISEMAIL_CFWS', 31);
57
+ define('ISEMAIL_DEPREC', 63);
58
+ define('ISEMAIL_RFC5322', 127);
59
+ define('ISEMAIL_ERR', 255);
60
+
61
+ // Diagnoses
62
+ // Address is valid
63
+ define('ISEMAIL_VALID', 0);
64
+ // Address is valid but a DNS check was not successful
65
+ define('ISEMAIL_DNSWARN_NO_MX_RECORD', 5);
66
+ define('ISEMAIL_DNSWARN_NO_RECORD', 6);
67
+ // Address is valid for SMTP but has unusual elements
68
+ define('ISEMAIL_RFC5321_TLD', 9);
69
+ define('ISEMAIL_RFC5321_TLDNUMERIC', 10);
70
+ define('ISEMAIL_RFC5321_QUOTEDSTRING', 11);
71
+ define('ISEMAIL_RFC5321_ADDRESSLITERAL', 12);
72
+ define('ISEMAIL_RFC5321_IPV6DEPRECATED', 13);
73
+ // Address is valid within the message but cannot be used unmodified for the envelope
74
+ define('ISEMAIL_CFWS_COMMENT', 17);
75
+ define('ISEMAIL_CFWS_FWS', 18);
76
+ // Address contains deprecated elements but may still be valid in restricted contexts
77
+ define('ISEMAIL_DEPREC_LOCALPART', 33);
78
+ define('ISEMAIL_DEPREC_FWS', 34);
79
+ define('ISEMAIL_DEPREC_QTEXT', 35);
80
+ define('ISEMAIL_DEPREC_QP', 36);
81
+ define('ISEMAIL_DEPREC_COMMENT', 37);
82
+ define('ISEMAIL_DEPREC_CTEXT', 38);
83
+ define('ISEMAIL_DEPREC_CFWS_NEAR_AT', 49);
84
+ // The address is only valid according to the broad definition of RFC 5322. It is otherwise invalid.
85
+ define('ISEMAIL_RFC5322_DOMAIN', 65);
86
+ define('ISEMAIL_RFC5322_TOOLONG', 66);
87
+ define('ISEMAIL_RFC5322_LOCAL_TOOLONG', 67);
88
+ define('ISEMAIL_RFC5322_DOMAIN_TOOLONG', 68);
89
+ define('ISEMAIL_RFC5322_LABEL_TOOLONG', 69);
90
+ define('ISEMAIL_RFC5322_DOMAINLITERAL', 70);
91
+ define('ISEMAIL_RFC5322_DOMLIT_OBSDTEXT', 71);
92
+ define('ISEMAIL_RFC5322_IPV6_GRPCOUNT', 72);
93
+ define('ISEMAIL_RFC5322_IPV6_2X2XCOLON', 73);
94
+ define('ISEMAIL_RFC5322_IPV6_BADCHAR', 74);
95
+ define('ISEMAIL_RFC5322_IPV6_MAXGRPS', 75);
96
+ define('ISEMAIL_RFC5322_IPV6_COLONSTRT', 76);
97
+ define('ISEMAIL_RFC5322_IPV6_COLONEND', 77);
98
+ // Address is invalid for any purpose
99
+ define('ISEMAIL_ERR_EXPECTING_DTEXT', 129);
100
+ define('ISEMAIL_ERR_NOLOCALPART', 130);
101
+ define('ISEMAIL_ERR_NODOMAIN', 131);
102
+ define('ISEMAIL_ERR_CONSECUTIVEDOTS', 132);
103
+ define('ISEMAIL_ERR_ATEXT_AFTER_CFWS', 133);
104
+ define('ISEMAIL_ERR_ATEXT_AFTER_QS', 134);
105
+ define('ISEMAIL_ERR_ATEXT_AFTER_DOMLIT', 135);
106
+ define('ISEMAIL_ERR_EXPECTING_QPAIR', 136);
107
+ define('ISEMAIL_ERR_EXPECTING_ATEXT', 137);
108
+ define('ISEMAIL_ERR_EXPECTING_QTEXT', 138);
109
+ define('ISEMAIL_ERR_EXPECTING_CTEXT', 139);
110
+ define('ISEMAIL_ERR_BACKSLASHEND', 140);
111
+ define('ISEMAIL_ERR_DOT_START', 141);
112
+ define('ISEMAIL_ERR_DOT_END', 142);
113
+ define('ISEMAIL_ERR_DOMAINHYPHENSTART', 143);
114
+ define('ISEMAIL_ERR_DOMAINHYPHENEND', 144);
115
+ define('ISEMAIL_ERR_UNCLOSEDQUOTEDSTR', 145);
116
+ define('ISEMAIL_ERR_UNCLOSEDCOMMENT', 146);
117
+ define('ISEMAIL_ERR_UNCLOSEDDOMLIT', 147);
118
+ define('ISEMAIL_ERR_FWS_CRLF_X2', 148);
119
+ define('ISEMAIL_ERR_FWS_CRLF_END', 149);
120
+ define('ISEMAIL_ERR_CR_NO_LF', 150);
121
+ // End of generated code
122
+ /*:diagnostic constants end:*/
123
+
124
+ // function control
125
+ define('ISEMAIL_THRESHOLD' , 16);
126
+
127
+ // Email parts
128
+ define('ISEMAIL_COMPONENT_LOCALPART' , 0);
129
+ define('ISEMAIL_COMPONENT_DOMAIN' , 1);
130
+ define('ISEMAIL_COMPONENT_LITERAL' , 2);
131
+ define('ISEMAIL_CONTEXT_COMMENT' , 3);
132
+ define('ISEMAIL_CONTEXT_FWS' , 4);
133
+ define('ISEMAIL_CONTEXT_QUOTEDSTRING' , 5);
134
+ define('ISEMAIL_CONTEXT_QUOTEDPAIR' , 6);
135
+
136
+ // Miscellaneous string constants
137
+ define('ISEMAIL_STRING_AT' , '@');
138
+ define('ISEMAIL_STRING_BACKSLASH' , '\\');
139
+ define('ISEMAIL_STRING_DOT' , '.');
140
+ define('ISEMAIL_STRING_DQUOTE' , '"');
141
+ define('ISEMAIL_STRING_OPENPARENTHESIS' , '(');
142
+ define('ISEMAIL_STRING_CLOSEPARENTHESIS', ')');
143
+ define('ISEMAIL_STRING_OPENSQBRACKET' , '[');
144
+ define('ISEMAIL_STRING_CLOSESQBRACKET' , ']');
145
+ define('ISEMAIL_STRING_HYPHEN' , '-');
146
+ define('ISEMAIL_STRING_COLON' , ':');
147
+ define('ISEMAIL_STRING_DOUBLECOLON' , '::');
148
+ define('ISEMAIL_STRING_SP' , ' ');
149
+ define('ISEMAIL_STRING_HTAB' , "\t");
150
+ define('ISEMAIL_STRING_CR' , "\r");
151
+ define('ISEMAIL_STRING_LF' , "\n");
152
+ define('ISEMAIL_STRING_IPV6TAG' , 'IPv6:');
153
+ // US-ASCII visible characters not valid for atext (http://tools.ietf.org/html/rfc5322#section-3.2.3)
154
+ define('ISEMAIL_STRING_SPECIALS' , '()<>[]:;@\\,."');
155
+ }
156
+
157
+ class SendGrid_ThirdParty {
158
+ /**
159
+ * Check that an email address conforms to RFCs 5321, 5322 and others
160
+ *
161
+ * As of Version 3.0, we are now distinguishing clearly between a Mailbox
162
+ * as defined by RFC 5321 and an addr-spec as defined by RFC 5322. Depending
163
+ * on the context, either can be regarded as a valid email address. The
164
+ * RFC 5321 Mailbox specification is more restrictive (comments, white space
165
+ * and obsolete forms are not allowed)
166
+ *
167
+ * @param string $email The email address to check
168
+ * @param boolean $checkDNS If true then a DNS check for MX records will be made
169
+ * @param mixed $errorlevel Determines the boundary between valid and invalid addresses.
170
+ * Status codes above this number will be returned as-is,
171
+ * status codes below will be returned as ISEMAIL_VALID. Thus the
172
+ * calling program can simply look for ISEMAIL_VALID if it is
173
+ * only interested in whether an address is valid or not. The
174
+ * errorlevel will determine how "picky" is_email() is about
175
+ * the address.
176
+ *
177
+ * If omitted or passed as false then is_email() will return
178
+ * true or false rather than an integer error or warning.
179
+ *
180
+ * NB Note the difference between $errorlevel = false and
181
+ * $errorlevel = 0
182
+ * @param array $parsedata If passed, returns the parsed address components
183
+ */
184
+ /*.mixed.*/
185
+ public static function is_email($email, $checkDNS = false, $errorlevel = false, &$parsedata = array()) {
186
+ // Check that $email is a valid address. Read the following RFCs to understand the constraints:
187
+ // (http://tools.ietf.org/html/rfc5321)
188
+ // (http://tools.ietf.org/html/rfc5322)
189
+ // (http://tools.ietf.org/html/rfc4291#section-2.2)
190
+ // (http://tools.ietf.org/html/rfc1123#section-2.1)
191
+ // (http://tools.ietf.org/html/rfc3696) (guidance only)
192
+ // version 2.0: Enhance $diagnose parameter to $errorlevel
193
+ // version 3.0: Introduced status categories
194
+ // revision 3.1: BUG: $parsedata was passed by value instead of by reference
195
+
196
+ if (is_bool($errorlevel)) {
197
+ $threshold = ISEMAIL_VALID;
198
+ $diagnose = (bool) $errorlevel;
199
+ } else {
200
+ $diagnose = true;
201
+
202
+ switch ((int) $errorlevel) {
203
+ case E_WARNING: $threshold = ISEMAIL_THRESHOLD; break; // For backward compatibility
204
+ case E_ERROR: $threshold = ISEMAIL_VALID; break; // For backward compatibility
205
+ default: $threshold = (int) $errorlevel;
206
+ }
207
+ }
208
+
209
+ $return_status = array(ISEMAIL_VALID);
210
+
211
+ // Parse the address into components, character by character
212
+ $raw_length = strlen($email);
213
+ $context = ISEMAIL_COMPONENT_LOCALPART; // Where we are
214
+ $context_stack = array($context); // Where we have been
215
+ $context_prior = ISEMAIL_COMPONENT_LOCALPART; // Where we just came from
216
+ $token = ''; // The current character
217
+ $token_prior = ''; // The previous character
218
+ $parsedata = array(
219
+ ISEMAIL_COMPONENT_LOCALPART => '',
220
+ ISEMAIL_COMPONENT_DOMAIN => ''
221
+ ); // For the components of the address
222
+
223
+ $atomlist = array(
224
+ ISEMAIL_COMPONENT_LOCALPART => array(''),
225
+ ISEMAIL_COMPONENT_DOMAIN => array('')
226
+ ); // For the dot-atom elements of the address
227
+ $element_count = 0;
228
+ $element_len = 0;
229
+ $hyphen_flag = false; // Hyphen cannot occur at the end of a subdomain
230
+ $end_or_die = false; // CFWS can only appear at the end of the element
231
+
232
+ //-echo "<table style=\"clear:left;\">"; // debug
233
+ for ($i = 0; $i < $raw_length; $i++) {
234
+ $token = $email[$i];
235
+ //-echo "<tr><td><strong>$context|",(($end_or_die) ? 'true' : 'false'),"|$token|" . max($return_status) . "</strong></td>"; // debug
236
+
237
+ switch ($context) {
238
+ //-------------------------------------------------------------
239
+ // local-part
240
+ //-------------------------------------------------------------
241
+ case ISEMAIL_COMPONENT_LOCALPART:
242
+ // http://tools.ietf.org/html/rfc5322#section-3.4.1
243
+ // local-part = dot-atom / quoted-string / obs-local-part
244
+ //
245
+ // dot-atom = [CFWS] dot-atom-text [CFWS]
246
+ //
247
+ // dot-atom-text = 1*atext *("." 1*atext)
248
+ //
249
+ // quoted-string = [CFWS]
250
+ // DQUOTE *([FWS] qcontent) [FWS] DQUOTE
251
+ // [CFWS]
252
+ //
253
+ // obs-local-part = word *("." word)
254
+ //
255
+ // word = atom / quoted-string
256
+ //
257
+ // atom = [CFWS] 1*atext [CFWS]
258
+ switch ($token) {
259
+ // Comment
260
+ case ISEMAIL_STRING_OPENPARENTHESIS:
261
+ if ($element_len === 0)
262
+ // Comments are OK at the beginning of an element
263
+ $return_status[] = ($element_count === 0) ? ISEMAIL_CFWS_COMMENT : ISEMAIL_DEPREC_COMMENT;
264
+ else {
265
+ $return_status[] = ISEMAIL_CFWS_COMMENT;
266
+ $end_or_die = true; // We can't start a comment in the middle of an element, so this better be the end
267
+ }
268
+
269
+ $context_stack[] = $context;
270
+ $context = ISEMAIL_CONTEXT_COMMENT;
271
+ break;
272
+ // Next dot-atom element
273
+ case ISEMAIL_STRING_DOT:
274
+ if ($element_len === 0)
275
+ // Another dot, already?
276
+ $return_status[] = ($element_count === 0) ? ISEMAIL_ERR_DOT_START : ISEMAIL_ERR_CONSECUTIVEDOTS; // Fatal error
277
+ else
278
+ // The entire local-part can be a quoted string for RFC 5321
279
+ // If it's just one atom that is quoted then it's an RFC 5322 obsolete form
280
+ if ($end_or_die) $return_status[] = ISEMAIL_DEPREC_LOCALPART;
281
+
282
+ $end_or_die = false; // CFWS & quoted strings are OK again now we're at the beginning of an element (although they are obsolete forms)
283
+ $element_len = 0;
284
+ $element_count++;
285
+ $parsedata[ISEMAIL_COMPONENT_LOCALPART] .= $token;
286
+ $atomlist[ISEMAIL_COMPONENT_LOCALPART][$element_count] = '';
287
+
288
+ break;
289
+ // Quoted string
290
+ case ISEMAIL_STRING_DQUOTE:
291
+ if ($element_len === 0) {
292
+ // The entire local-part can be a quoted string for RFC 5321
293
+ // If it's just one atom that is quoted then it's an RFC 5322 obsolete form
294
+ $return_status[] = ($element_count === 0) ? ISEMAIL_RFC5321_QUOTEDSTRING : ISEMAIL_DEPREC_LOCALPART;
295
+
296
+ $parsedata[ISEMAIL_COMPONENT_LOCALPART] .= $token;
297
+ $atomlist[ISEMAIL_COMPONENT_LOCALPART][$element_count] .= $token;
298
+ $element_len++;
299
+ $end_or_die = true; // Quoted string must be the entire element
300
+ $context_stack[] = $context;
301
+ $context = ISEMAIL_CONTEXT_QUOTEDSTRING;
302
+ } else {
303
+ $return_status[] = ISEMAIL_ERR_EXPECTING_ATEXT; // Fatal error
304
+ }
305
+
306
+ break;
307
+ // Folding White Space
308
+ case ISEMAIL_STRING_CR:
309
+ case ISEMAIL_STRING_SP:
310
+ case ISEMAIL_STRING_HTAB:
311
+ if (($token === ISEMAIL_STRING_CR) && ((++$i === $raw_length) || ($email[$i] !== ISEMAIL_STRING_LF))) {$return_status[] = ISEMAIL_ERR_CR_NO_LF; break;} // Fatal error
312
+
313
+ if ($element_len === 0)
314
+ $return_status[] = ($element_count === 0) ? ISEMAIL_CFWS_FWS : ISEMAIL_DEPREC_FWS;
315
+ else
316
+ $end_or_die = true; // We can't start FWS in the middle of an element, so this better be the end
317
+
318
+ $context_stack[] = $context;
319
+ $context = ISEMAIL_CONTEXT_FWS;
320
+ $token_prior = $token;
321
+
322
+ break;
323
+ // @
324
+ case ISEMAIL_STRING_AT:
325
+ // At this point we should have a valid local-part
326
+ if (count($context_stack) !== 1) die('Unexpected item on context stack');
327
+
328
+ if ($parsedata[ISEMAIL_COMPONENT_LOCALPART] === '')
329
+ $return_status[] = ISEMAIL_ERR_NOLOCALPART; // Fatal error
330
+ elseif ($element_len === 0) $return_status[] = ISEMAIL_ERR_DOT_END; // Fatal error
331
+ // http://tools.ietf.org/html/rfc5321#section-4.5.3.1.1
332
+ // The maximum total length of a user name or other local-part is 64
333
+ // octets.
334
+ elseif (strlen($parsedata[ISEMAIL_COMPONENT_LOCALPART]) > 64)
335
+ $return_status[] = ISEMAIL_RFC5322_LOCAL_TOOLONG;
336
+ // http://tools.ietf.org/html/rfc5322#section-3.4.1
337
+ // Comments and folding white space
338
+ // SHOULD NOT be used around the "@" in the addr-spec.
339
+ //
340
+ // http://tools.ietf.org/html/rfc2119
341
+ // 4. SHOULD NOT This phrase, or the phrase "NOT RECOMMENDED" mean that
342
+ // there may exist valid reasons in particular circumstances when the
343
+ // particular behavior is acceptable or even useful, but the full
344
+ // implications should be understood and the case carefully weighed
345
+ // before implementing any behavior described with this label.
346
+ elseif (($context_prior === ISEMAIL_CONTEXT_COMMENT) || ($context_prior === ISEMAIL_CONTEXT_FWS))
347
+ $return_status[] = ISEMAIL_DEPREC_CFWS_NEAR_AT;
348
+
349
+ // Clear everything down for the domain parsing
350
+ $context = ISEMAIL_COMPONENT_DOMAIN; // Where we are
351
+ $context_stack = array($context); // Where we have been
352
+ $element_count = 0;
353
+ $element_len = 0;
354
+ $end_or_die = false; // CFWS can only appear at the end of the element
355
+
356
+ break;
357
+ // atext
358
+ default:
359
+ // http://tools.ietf.org/html/rfc5322#section-3.2.3
360
+ // atext = ALPHA / DIGIT / ; Printable US-ASCII
361
+ // "!" / "#" / ; characters not including
362
+ // "$" / "%" / ; specials. Used for atoms.
363
+ // "&" / "'" /
364
+ // "*" / "+" /
365
+ // "-" / "/" /
366
+ // "=" / "?" /
367
+ // "^" / "_" /
368
+ // "`" / "{" /
369
+ // "|" / "}" /
370
+ // "~"
371
+ if ($end_or_die) {
372
+ // We have encountered atext where it is no longer valid
373
+ switch ($context_prior) {
374
+ case ISEMAIL_CONTEXT_COMMENT:
375
+ case ISEMAIL_CONTEXT_FWS:
376
+ $return_status[] = ISEMAIL_ERR_ATEXT_AFTER_CFWS;
377
+ break;
378
+ case ISEMAIL_CONTEXT_QUOTEDSTRING:
379
+ $return_status[] = ISEMAIL_ERR_ATEXT_AFTER_QS;
380
+ break;
381
+ default:
382
+ die ("More atext found where none is allowed, but unrecognised prior context: $context_prior");
383
+ }
384
+ } else {
385
+ $context_prior = $context;
386
+ $ord = ord($token);
387
+
388
+ if (($ord < 33) || ($ord > 126) || ($ord === 10) || (!is_bool(strpos(ISEMAIL_STRING_SPECIALS, $token))))
389
+ $return_status[] = ISEMAIL_ERR_EXPECTING_ATEXT; // Fatal error
390
+
391
+ $parsedata[ISEMAIL_COMPONENT_LOCALPART] .= $token;
392
+ $atomlist[ISEMAIL_COMPONENT_LOCALPART][$element_count] .= $token;
393
+ $element_len++;
394
+ }
395
+ }
396
+
397
+ break;
398
+ //-------------------------------------------------------------
399
+ // Domain
400
+ //-------------------------------------------------------------
401
+ case ISEMAIL_COMPONENT_DOMAIN:
402
+ // http://tools.ietf.org/html/rfc5322#section-3.4.1
403
+ // domain = dot-atom / domain-literal / obs-domain
404
+ //
405
+ // dot-atom = [CFWS] dot-atom-text [CFWS]
406
+ //
407
+ // dot-atom-text = 1*atext *("." 1*atext)
408
+ //
409
+ // domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]
410
+ //
411
+ // dtext = %d33-90 / ; Printable US-ASCII
412
+ // %d94-126 / ; characters not including
413
+ // obs-dtext ; "[", "]", or "\"
414
+ //
415
+ // obs-domain = atom *("." atom)
416
+ //
417
+ // atom = [CFWS] 1*atext [CFWS]
418
+
419
+
420
+ // http://tools.ietf.org/html/rfc5321#section-4.1.2
421
+ // Mailbox = Local-part "@" ( Domain / address-literal )
422
+ //
423
+ // Domain = sub-domain *("." sub-domain)
424
+ //
425
+ // address-literal = "[" ( IPv4-address-literal /
426
+ // IPv6-address-literal /
427
+ // General-address-literal ) "]"
428
+ // ; See Section 4.1.3
429
+
430
+ // http://tools.ietf.org/html/rfc5322#section-3.4.1
431
+ // Note: A liberal syntax for the domain portion of addr-spec is
432
+ // given here. However, the domain portion contains addressing
433
+ // information specified by and used in other protocols (e.g.,
434
+ // [RFC1034], [RFC1035], [RFC1123], [RFC5321]). It is therefore
435
+ // incumbent upon implementations to conform to the syntax of
436
+ // addresses for the context in which they are used.
437
+ // is_email() author's note: it's not clear how to interpret this in
438
+ // the context of a general email address validator. The conclusion I
439
+ // have reached is this: "addressing information" must comply with
440
+ // RFC 5321 (and in turn RFC 1035), anything that is "semantically
441
+ // invisible" must comply only with RFC 5322.
442
+ switch ($token) {
443
+ // Comment
444
+ case ISEMAIL_STRING_OPENPARENTHESIS:
445
+ if ($element_len === 0)
446
+ // Comments at the start of the domain are deprecated in the text
447
+ // Comments at the start of a subdomain are obs-domain
448
+ // (http://tools.ietf.org/html/rfc5322#section-3.4.1)
449
+ $return_status[] = ($element_count === 0) ? ISEMAIL_DEPREC_CFWS_NEAR_AT : ISEMAIL_DEPREC_COMMENT;
450
+ else {
451
+ $return_status[] = ISEMAIL_CFWS_COMMENT;
452
+ $end_or_die = true; // We can't start a comment in the middle of an element, so this better be the end
453
+ }
454
+
455
+ $context_stack[] = $context;
456
+ $context = ISEMAIL_CONTEXT_COMMENT;
457
+ break;
458
+ // Next dot-atom element
459
+ case ISEMAIL_STRING_DOT:
460
+ if ($element_len === 0)
461
+ // Another dot, already?
462
+ $return_status[] = ($element_count === 0) ? ISEMAIL_ERR_DOT_START : ISEMAIL_ERR_CONSECUTIVEDOTS; // Fatal error
463
+ elseif ($hyphen_flag)
464
+ // Previous subdomain ended in a hyphen
465
+ $return_status[] = ISEMAIL_ERR_DOMAINHYPHENEND; // Fatal error
466
+ else
467
+ // Nowhere in RFC 5321 does it say explicitly that the
468
+ // domain part of a Mailbox must be a valid domain according
469
+ // to the DNS standards set out in RFC 1035, but this *is*
470
+ // implied in several places. For instance, wherever the idea
471
+ // of host routing is discussed the RFC says that the domain
472
+ // must be looked up in the DNS. This would be nonsense unless
473
+ // the domain was designed to be a valid DNS domain. Hence we
474
+ // must conclude that the RFC 1035 restriction on label length
475
+ // also applies to RFC 5321 domains.
476
+ //
477
+ // http://tools.ietf.org/html/rfc1035#section-2.3.4
478
+ // labels 63 octets or less
479
+ if ($element_len > 63) $return_status[] = ISEMAIL_RFC5322_LABEL_TOOLONG;
480
+
481
+ $end_or_die = false; // CFWS is OK again now we're at the beginning of an element (although it may be obsolete CFWS)
482
+ $element_len = 0;
483
+ $element_count++;
484
+ $atomlist[ISEMAIL_COMPONENT_DOMAIN][$element_count] = '';
485
+ $parsedata[ISEMAIL_COMPONENT_DOMAIN] .= $token;
486
+
487
+ break;
488
+ // Domain literal
489
+ case ISEMAIL_STRING_OPENSQBRACKET:
490
+ if ($parsedata[ISEMAIL_COMPONENT_DOMAIN] === '') {
491
+ $end_or_die = true; // Domain literal must be the only component
492
+ $element_len++;
493
+ $context_stack[] = $context;
494
+ $context = ISEMAIL_COMPONENT_LITERAL;
495
+ $parsedata[ISEMAIL_COMPONENT_DOMAIN] .= $token;
496
+ $atomlist[ISEMAIL_COMPONENT_DOMAIN][$element_count] .= $token;
497
+ $parsedata[ISEMAIL_COMPONENT_LITERAL] = '';
498
+ } else {
499
+ $return_status[] = ISEMAIL_ERR_EXPECTING_ATEXT; // Fatal error
500
+ }
501
+
502
+ break;
503
+ // Folding White Space
504
+ case ISEMAIL_STRING_CR:
505
+ case ISEMAIL_STRING_SP:
506
+ case ISEMAIL_STRING_HTAB:
507
+ if (($token === ISEMAIL_STRING_CR) && ((++$i === $raw_length) || ($email[$i] !== ISEMAIL_STRING_LF))) {$return_status[] = ISEMAIL_ERR_CR_NO_LF; break;} // Fatal error
508
+
509
+ if ($element_len === 0)
510
+ $return_status[] = ($element_count === 0) ? ISEMAIL_DEPREC_CFWS_NEAR_AT : ISEMAIL_DEPREC_FWS;
511
+ else {
512
+ $return_status[] = ISEMAIL_CFWS_FWS;
513
+ $end_or_die = true; // We can't start FWS in the middle of an element, so this better be the end
514
+ }
515
+
516
+ $context_stack[] = $context;
517
+ $context = ISEMAIL_CONTEXT_FWS;
518
+ $token_prior = $token;
519
+ break;
520
+ // atext
521
+ default:
522
+ // RFC 5322 allows any atext...
523
+ // http://tools.ietf.org/html/rfc5322#section-3.2.3
524
+ // atext = ALPHA / DIGIT / ; Printable US-ASCII
525
+ // "!" / "#" / ; characters not including
526
+ // "$" / "%" / ; specials. Used for atoms.
527
+ // "&" / "'" /
528
+ // "*" / "+" /
529
+ // "-" / "/" /
530
+ // "=" / "?" /
531
+ // "^" / "_" /
532
+ // "`" / "{" /
533
+ // "|" / "}" /
534
+ // "~"
535
+
536
+ // But RFC 5321 only allows letter-digit-hyphen to comply with DNS rules (RFCs 1034 & 1123)
537
+ // http://tools.ietf.org/html/rfc5321#section-4.1.2
538
+ // sub-domain = Let-dig [Ldh-str]
539
+ //
540
+ // Let-dig = ALPHA / DIGIT
541
+ //
542
+ // Ldh-str = *( ALPHA / DIGIT / "-" ) Let-dig
543
+ //
544
+ if ($end_or_die) {
545
+ // We have encountered atext where it is no longer valid
546
+ switch ($context_prior) {
547
+ case ISEMAIL_CONTEXT_COMMENT:
548
+ case ISEMAIL_CONTEXT_FWS:
549
+ $return_status[] = ISEMAIL_ERR_ATEXT_AFTER_CFWS;
550
+ break;
551
+ case ISEMAIL_COMPONENT_LITERAL:
552
+ $return_status[] = ISEMAIL_ERR_ATEXT_AFTER_DOMLIT;
553
+ break;
554
+ default:
555
+ die ("More atext found where none is allowed, but unrecognised prior context: $context_prior");
556
+ }
557
+ }
558
+
559
+ $ord = ord($token);
560
+ $hyphen_flag = false; // Assume this token isn't a hyphen unless we discover it is
561
+
562
+ if (($ord < 33) || ($ord > 126) || (!is_bool(strpos(ISEMAIL_STRING_SPECIALS, $token)))) {
563
+ $return_status[] = ISEMAIL_ERR_EXPECTING_ATEXT; // Fatal error
564
+ } elseif ($token === ISEMAIL_STRING_HYPHEN) {
565
+ if ($element_len === 0) {
566
+ // Hyphens can't be at the beginning of a subdomain
567
+ $return_status[] = ISEMAIL_ERR_DOMAINHYPHENSTART; // Fatal error
568
+ }
569
+
570
+ $hyphen_flag = true;
571
+ } elseif (!(($ord > 47 && $ord < 58) || ($ord > 64 && $ord < 91) || ($ord > 96 && $ord < 123))) {
572
+ // Not an RFC 5321 subdomain, but still OK by RFC 5322
573
+ $return_status[] = ISEMAIL_RFC5322_DOMAIN;
574
+ }
575
+
576
+ $parsedata[ISEMAIL_COMPONENT_DOMAIN] .= $token;
577
+ $atomlist[ISEMAIL_COMPONENT_DOMAIN][$element_count] .= $token;
578
+ $element_len++;
579
+ }
580
+
581
+ break;
582
+ //-------------------------------------------------------------
583
+ // Domain literal
584
+ //-------------------------------------------------------------
585
+ case ISEMAIL_COMPONENT_LITERAL:
586
+ // http://tools.ietf.org/html/rfc5322#section-3.4.1
587
+ // domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]
588
+ //
589
+ // dtext = %d33-90 / ; Printable US-ASCII
590
+ // %d94-126 / ; characters not including
591
+ // obs-dtext ; "[", "]", or "\"
592
+ //
593
+ // obs-dtext = obs-NO-WS-CTL / quoted-pair
594
+ switch ($token) {
595
+ // End of domain literal
596
+ case ISEMAIL_STRING_CLOSESQBRACKET:
597
+ if ((int) max($return_status) < ISEMAIL_DEPREC) {
598
+ // Could be a valid RFC 5321 address literal, so let's check
599
+
600
+ // http://tools.ietf.org/html/rfc5321#section-4.1.2
601
+ // address-literal = "[" ( IPv4-address-literal /
602
+ // IPv6-address-literal /
603
+ // General-address-literal ) "]"
604
+ // ; See Section 4.1.3
605
+ //
606
+ // http://tools.ietf.org/html/rfc5321#section-4.1.3
607
+ // IPv4-address-literal = Snum 3("." Snum)
608
+ //
609
+ // IPv6-address-literal = "IPv6:" IPv6-addr
610
+ //
611
+ // General-address-literal = Standardized-tag ":" 1*dcontent
612
+ //
613
+ // Standardized-tag = Ldh-str
614
+ // ; Standardized-tag MUST be specified in a
615
+ // ; Standards-Track RFC and registered with IANA
616
+ //
617
+ // dcontent = %d33-90 / ; Printable US-ASCII
618
+ // %d94-126 ; excl. "[", "\", "]"
619
+ //
620
+ // Snum = 1*3DIGIT
621
+ // ; representing a decimal integer
622
+ // ; value in the range 0 through 255
623
+ //
624
+ // IPv6-addr = IPv6-full / IPv6-comp / IPv6v4-full / IPv6v4-comp
625
+ //
626
+ // IPv6-hex = 1*4HEXDIG
627
+ //
628
+ // IPv6-full = IPv6-hex 7(":" IPv6-hex)
629
+ //
630
+ // IPv6-comp = [IPv6-hex *5(":" IPv6-hex)] "::"
631
+ // [IPv6-hex *5(":" IPv6-hex)]
632
+ // ; The "::" represents at least 2 16-bit groups of
633
+ // ; zeros. No more than 6 groups in addition to the
634
+ // ; "::" may be present.
635
+ //
636
+ // IPv6v4-full = IPv6-hex 5(":" IPv6-hex) ":" IPv4-address-literal
637
+ //
638
+ // IPv6v4-comp = [IPv6-hex *3(":" IPv6-hex)] "::"
639
+ // [IPv6-hex *3(":" IPv6-hex) ":"]
640
+ // IPv4-address-literal
641
+ // ; The "::" represents at least 2 16-bit groups of
642
+ // ; zeros. No more than 4 groups in addition to the
643
+ // ; "::" and IPv4-address-literal may be present.
644
+ //
645
+ // is_email() author's note: We can't use ip2long() to validate
646
+ // IPv4 addresses because it accepts abbreviated addresses
647
+ // (xxx.xxx.xxx), expanding the last group to complete the address.
648
+ // filter_var() validates IPv6 address inconsistently (up to PHP 5.3.3
649
+ // at least) -- see http://bugs.php.net/bug.php?id=53236 for example
650
+ $max_groups = 8;
651
+ $matchesIP = array();
652
+ /*.mixed.*/ $index = false;
653
+ $addressliteral = $parsedata[ISEMAIL_COMPONENT_LITERAL];
654
+
655
+ // Extract IPv4 part from the end of the address-literal (if there is one)
656
+ if (preg_match('/\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/', $addressliteral, $matchesIP) > 0) {
657
+ $index = strrpos($addressliteral, $matchesIP[0]);
658
+ if ($index !== 0) $addressliteral = substr($addressliteral, 0, $index) . '0:0'; // Convert IPv4 part to IPv6 format for further testing
659
+ }
660
+
661
+ if ($index === 0) {
662
+ // Nothing there except a valid IPv4 address, so...
663
+ $return_status[] = ISEMAIL_RFC5321_ADDRESSLITERAL;
664
+ } elseif (strncasecmp($addressliteral, ISEMAIL_STRING_IPV6TAG, 5) !== 0) {
665
+ $return_status[] = ISEMAIL_RFC5322_DOMAINLITERAL;
666
+ } else {
667
+ $IPv6 = substr($addressliteral, 5);
668
+ $matchesIP = explode(ISEMAIL_STRING_COLON, $IPv6); // Revision 2.7: Daniel Marschall's new IPv6 testing strategy
669
+ $groupCount = count($matchesIP);
670
+ $index = strpos($IPv6,ISEMAIL_STRING_DOUBLECOLON);
671
+
672
+ if ($index === false) {
673
+ // We need exactly the right number of groups
674
+ if ($groupCount !== $max_groups)
675
+ $return_status[] = ISEMAIL_RFC5322_IPV6_GRPCOUNT;
676
+ } else {
677
+ if ($index !== strrpos($IPv6,ISEMAIL_STRING_DOUBLECOLON))
678
+ $return_status[] = ISEMAIL_RFC5322_IPV6_2X2XCOLON;
679
+ else {
680
+ if ($index === 0 || $index === (strlen($IPv6) - 2)) $max_groups++; // RFC 4291 allows :: at the start or end of an address with 7 other groups in addition
681
+
682
+ if ($groupCount > $max_groups)
683
+ $return_status[] = ISEMAIL_RFC5322_IPV6_MAXGRPS;
684
+ elseif ($groupCount === $max_groups)
685
+ $return_status[] = ISEMAIL_RFC5321_IPV6DEPRECATED; // Eliding a single "::"
686
+ }
687
+ }
688
+
689
+ // Revision 2.7: Daniel Marschall's new IPv6 testing strategy
690
+ if ((substr($IPv6, 0, 1) === ISEMAIL_STRING_COLON) && (substr($IPv6, 1, 1) !== ISEMAIL_STRING_COLON))
691
+ $return_status[] = ISEMAIL_RFC5322_IPV6_COLONSTRT; // Address starts with a single colon
692
+ elseif ((substr($IPv6, -1) === ISEMAIL_STRING_COLON) && (substr($IPv6, -2, 1) !== ISEMAIL_STRING_COLON))
693
+ $return_status[] = ISEMAIL_RFC5322_IPV6_COLONEND; // Address ends with a single colon
694
+ elseif (count(preg_grep('/^[0-9A-Fa-f]{0,4}$/', $matchesIP, PREG_GREP_INVERT)) !== 0)
695
+ $return_status[] = ISEMAIL_RFC5322_IPV6_BADCHAR; // Check for unmatched characters
696
+ else
697
+ $return_status[] = ISEMAIL_RFC5321_ADDRESSLITERAL;
698
+ }
699
+ } else
700
+ $return_status[] = ISEMAIL_RFC5322_DOMAINLITERAL;
701
+
702
+
703
+ $parsedata[ISEMAIL_COMPONENT_DOMAIN] .= $token;
704
+ $atomlist[ISEMAIL_COMPONENT_DOMAIN][$element_count] .= $token;
705
+ $element_len++;
706
+ $context_prior = $context;
707
+ $context = (int) array_pop($context_stack);
708
+ break;
709
+ case ISEMAIL_STRING_BACKSLASH:
710
+ $return_status[] = ISEMAIL_RFC5322_DOMLIT_OBSDTEXT;
711
+ $context_stack[] = $context;
712
+ $context = ISEMAIL_CONTEXT_QUOTEDPAIR;
713
+ break;
714
+ // Folding White Space
715
+ case ISEMAIL_STRING_CR:
716
+ case ISEMAIL_STRING_SP:
717
+ case ISEMAIL_STRING_HTAB:
718
+ if (($token === ISEMAIL_STRING_CR) && ((++$i === $raw_length) || ($email[$i] !== ISEMAIL_STRING_LF))) {$return_status[] = ISEMAIL_ERR_CR_NO_LF; break;} // Fatal error
719
+
720
+ $return_status[] = ISEMAIL_CFWS_FWS;
721
+
722
+ $context_stack[] = $context;
723
+ $context = ISEMAIL_CONTEXT_FWS;
724
+ $token_prior = $token;
725
+ break;
726
+ // dtext
727
+ default:
728
+ // http://tools.ietf.org/html/rfc5322#section-3.4.1
729
+ // dtext = %d33-90 / ; Printable US-ASCII
730
+ // %d94-126 / ; characters not including
731
+ // obs-dtext ; "[", "]", or "\"
732
+ //
733
+ // obs-dtext = obs-NO-WS-CTL / quoted-pair
734
+ //
735
+ // obs-NO-WS-CTL = %d1-8 / ; US-ASCII control
736
+ // %d11 / ; characters that do not
737
+ // %d12 / ; include the carriage
738
+ // %d14-31 / ; return, line feed, and
739
+ // %d127 ; white space characters
740
+ $ord = ord($token);
741
+
742
+ // CR, LF, SP & HTAB have already been parsed above
743
+ if (($ord > 127) || ($ord === 0) || ($token === ISEMAIL_STRING_OPENSQBRACKET)) {
744
+ $return_status[] = ISEMAIL_ERR_EXPECTING_DTEXT; // Fatal error
745
+ break;
746
+ } elseif (($ord < 33) || ($ord === 127)) {
747
+ $return_status[] = ISEMAIL_RFC5322_DOMLIT_OBSDTEXT;
748
+ }
749
+
750
+ $parsedata[ISEMAIL_COMPONENT_LITERAL] .= $token;
751
+ $parsedata[ISEMAIL_COMPONENT_DOMAIN] .= $token;
752
+ $atomlist[ISEMAIL_COMPONENT_DOMAIN][$element_count] .= $token;
753
+ $element_len++;
754
+ }
755
+
756
+ break;
757
+ //-------------------------------------------------------------
758
+ // Quoted string
759
+ //-------------------------------------------------------------
760
+ case ISEMAIL_CONTEXT_QUOTEDSTRING:
761
+ // http://tools.ietf.org/html/rfc5322#section-3.2.4
762
+ // quoted-string = [CFWS]
763
+ // DQUOTE *([FWS] qcontent) [FWS] DQUOTE
764
+ // [CFWS]
765
+ //
766
+ // qcontent = qtext / quoted-pair
767
+ switch ($token) {
768
+ // Quoted pair
769
+ case ISEMAIL_STRING_BACKSLASH:
770
+ $context_stack[] = $context;
771
+ $context = ISEMAIL_CONTEXT_QUOTEDPAIR;
772
+ break;
773
+ // Folding White Space
774
+ // Inside a quoted string, spaces are allowed as regular characters.
775
+ // It's only FWS if we include HTAB or CRLF
776
+ case ISEMAIL_STRING_CR:
777
+ case ISEMAIL_STRING_HTAB:
778
+ if (($token === ISEMAIL_STRING_CR) && ((++$i === $raw_length) || ($email[$i] !== ISEMAIL_STRING_LF))) {$return_status[] = ISEMAIL_ERR_CR_NO_LF; break;} // Fatal error
779
+
780
+ // http://tools.ietf.org/html/rfc5322#section-3.2.2
781
+ // Runs of FWS, comment, or CFWS that occur between lexical tokens in a
782
+ // structured header field are semantically interpreted as a single
783
+ // space character.
784
+
785
+ // http://tools.ietf.org/html/rfc5322#section-3.2.4
786
+ // the CRLF in any FWS/CFWS that appears within the quoted-string [is]
787
+ // semantically "invisible" and therefore not part of the quoted-string
788
+ $parsedata[ISEMAIL_COMPONENT_LOCALPART] .= ISEMAIL_STRING_SP;
789
+ $atomlist[ISEMAIL_COMPONENT_LOCALPART][$element_count] .= ISEMAIL_STRING_SP;
790
+ $element_len++;
791
+
792
+ $return_status[] = ISEMAIL_CFWS_FWS;
793
+ $context_stack[] = $context;
794
+ $context = ISEMAIL_CONTEXT_FWS;
795
+ $token_prior = $token;
796
+ break;
797
+ // End of quoted string
798
+ case ISEMAIL_STRING_DQUOTE:
799
+ $parsedata[ISEMAIL_COMPONENT_LOCALPART] .= $token;
800
+ $atomlist[ISEMAIL_COMPONENT_LOCALPART][$element_count] .= $token;
801
+ $element_len++;
802
+ $context_prior = $context;
803
+ $context = (int) array_pop($context_stack);
804
+ break;
805
+ // qtext
806
+ default:
807
+ // http://tools.ietf.org/html/rfc5322#section-3.2.4
808
+ // qtext = %d33 / ; Printable US-ASCII
809
+ // %d35-91 / ; characters not including
810
+ // %d93-126 / ; "\" or the quote character
811
+ // obs-qtext
812
+ //
813
+ // obs-qtext = obs-NO-WS-CTL
814
+ //
815
+ // obs-NO-WS-CTL = %d1-8 / ; US-ASCII control
816
+ // %d11 / ; characters that do not
817
+ // %d12 / ; include the carriage
818
+ // %d14-31 / ; return, line feed, and
819
+ // %d127 ; white space characters
820
+ $ord = ord($token);
821
+
822
+ if (($ord > 127) || ($ord === 0) || ($ord === 10)) {
823
+ $return_status[] = ISEMAIL_ERR_EXPECTING_QTEXT; // Fatal error
824
+ } elseif (($ord < 32) || ($ord === 127))
825
+ $return_status[] = ISEMAIL_DEPREC_QTEXT;
826
+
827
+ $parsedata[ISEMAIL_COMPONENT_LOCALPART] .= $token;
828
+ $atomlist[ISEMAIL_COMPONENT_LOCALPART][$element_count] .= $token;
829
+ $element_len++;
830
+ }
831
+
832
+ // http://tools.ietf.org/html/rfc5322#section-3.4.1
833
+ // If the
834
+ // string can be represented as a dot-atom (that is, it contains no
835
+ // characters other than atext characters or "." surrounded by atext
836
+ // characters), then the dot-atom form SHOULD be used and the quoted-
837
+ // string form SHOULD NOT be used.
838
+ // To do
839
+ break;
840
+ //-------------------------------------------------------------
841
+ // Quoted pair
842
+ //-------------------------------------------------------------
843
+ case ISEMAIL_CONTEXT_QUOTEDPAIR:
844
+ // http://tools.ietf.org/html/rfc5322#section-3.2.1
845
+ // quoted-pair = ("\" (VCHAR / WSP)) / obs-qp
846
+ //
847
+ // VCHAR = %d33-126 ; visible (printing) characters
848
+ // WSP = SP / HTAB ; white space
849
+ //
850
+ // obs-qp = "\" (%d0 / obs-NO-WS-CTL / LF / CR)
851
+ //
852
+ // obs-NO-WS-CTL = %d1-8 / ; US-ASCII control
853
+ // %d11 / ; characters that do not
854
+ // %d12 / ; include the carriage
855
+ // %d14-31 / ; return, line feed, and
856
+ // %d127 ; white space characters
857
+ //
858
+ // i.e. obs-qp = "\" (%d0-8, %d10-31 / %d127)
859
+ $ord = ord($token);
860
+
861
+ if ($ord > 127)
862
+ $return_status[] = ISEMAIL_ERR_EXPECTING_QPAIR; // Fatal error
863
+ elseif ((($ord < 31) && ($ord !== 9)) || ($ord === 127)) // SP & HTAB are allowed
864
+ $return_status[] = ISEMAIL_DEPREC_QP;
865
+
866
+ // At this point we know where this qpair occurred so
867
+ // we could check to see if the character actually
868
+ // needed to be quoted at all.
869
+ // http://tools.ietf.org/html/rfc5321#section-4.1.2
870
+ // the sending system SHOULD transmit the
871
+ // form that uses the minimum quoting possible.
872
+ // To do: check whether the character needs to be quoted (escaped) in this context
873
+ $context_prior = $context;
874
+ $context = (int) array_pop($context_stack); // End of qpair
875
+ $token = ISEMAIL_STRING_BACKSLASH . $token;
876
+
877
+ switch ($context) {
878
+ case ISEMAIL_CONTEXT_COMMENT:
879
+ break;
880
+ case ISEMAIL_CONTEXT_QUOTEDSTRING:
881
+ $parsedata[ISEMAIL_COMPONENT_LOCALPART] .= $token;
882
+ $atomlist[ISEMAIL_COMPONENT_LOCALPART][$element_count] .= $token;
883
+ $element_len += 2; // The maximum sizes specified by RFC 5321 are octet counts, so we must include the backslash
884
+ break;
885
+ case ISEMAIL_COMPONENT_LITERAL:
886
+ $parsedata[ISEMAIL_COMPONENT_DOMAIN] .= $token;
887
+ $atomlist[ISEMAIL_COMPONENT_DOMAIN][$element_count] .= $token;
888
+ $element_len += 2; // The maximum sizes specified by RFC 5321 are octet counts, so we must include the backslash
889
+ break;
890
+ default:
891
+ die("Quoted pair logic invoked in an invalid context: $context");
892
+ }
893
+
894
+ break;
895
+ //-------------------------------------------------------------
896
+ // Comment
897
+ //-------------------------------------------------------------
898
+ case ISEMAIL_CONTEXT_COMMENT:
899
+ // http://tools.ietf.org/html/rfc5322#section-3.2.2
900
+ // comment = "(" *([FWS] ccontent) [FWS] ")"
901
+ //
902
+ // ccontent = ctext / quoted-pair / comment
903
+ switch ($token) {
904
+ // Nested comment
905
+ case ISEMAIL_STRING_OPENPARENTHESIS:
906
+ // Nested comments are OK
907
+ $context_stack[] = $context;
908
+ $context = ISEMAIL_CONTEXT_COMMENT;
909
+ break;
910
+ // End of comment
911
+ case ISEMAIL_STRING_CLOSEPARENTHESIS:
912
+ $context_prior = $context;
913
+ $context = (int) array_pop($context_stack);
914
+
915
+ // http://tools.ietf.org/html/rfc5322#section-3.2.2
916
+ // Runs of FWS, comment, or CFWS that occur between lexical tokens in a
917
+ // structured header field are semantically interpreted as a single
918
+ // space character.
919
+ //
920
+ // is_email() author's note: This *cannot* mean that we must add a
921
+ // space to the address wherever CFWS appears. This would result in
922
+ // any addr-spec that had CFWS outside a quoted string being invalid
923
+ // for RFC 5321.
924
+ // if (($context === ISEMAIL_COMPONENT_LOCALPART) || ($context === ISEMAIL_COMPONENT_DOMAIN)) {
925
+ // $parsedata[$context] .= ISEMAIL_STRING_SP;
926
+ // $atomlist[$context][$element_count] .= ISEMAIL_STRING_SP;
927
+ // $element_len++;
928
+ // }
929
+
930
+ break;
931
+ // Quoted pair
932
+ case ISEMAIL_STRING_BACKSLASH:
933
+ $context_stack[] = $context;
934
+ $context = ISEMAIL_CONTEXT_QUOTEDPAIR;
935
+ break;
936
+ // Folding White Space
937
+ case ISEMAIL_STRING_CR:
938
+ case ISEMAIL_STRING_SP:
939
+ case ISEMAIL_STRING_HTAB:
940
+ if (($token === ISEMAIL_STRING_CR) && ((++$i === $raw_length) || ($email[$i] !== ISEMAIL_STRING_LF))) {$return_status[] = ISEMAIL_ERR_CR_NO_LF; break;} // Fatal error
941
+
942
+ $return_status[] = ISEMAIL_CFWS_FWS;
943
+
944
+ $context_stack[] = $context;
945
+ $context = ISEMAIL_CONTEXT_FWS;
946
+ $token_prior = $token;
947
+ break;
948
+ // ctext
949
+ default:
950
+ // http://tools.ietf.org/html/rfc5322#section-3.2.3
951
+ // ctext = %d33-39 / ; Printable US-ASCII
952
+ // %d42-91 / ; characters not including
953
+ // %d93-126 / ; "(", ")", or "\"
954
+ // obs-ctext
955
+ //
956
+ // obs-ctext = obs-NO-WS-CTL
957
+ //
958
+ // obs-NO-WS-CTL = %d1-8 / ; US-ASCII control
959
+ // %d11 / ; characters that do not
960
+ // %d12 / ; include the carriage
961
+ // %d14-31 / ; return, line feed, and
962
+ // %d127 ; white space characters
963
+ $ord = ord($token);
964
+
965
+ if (($ord > 127) || ($ord === 0) || ($ord === 10)) {
966
+ $return_status[] = ISEMAIL_ERR_EXPECTING_CTEXT; // Fatal error
967
+ break;
968
+ } elseif (($ord < 32) || ($ord === 127)) {
969
+ $return_status[] = ISEMAIL_DEPREC_CTEXT;
970
+ }
971
+ }
972
+
973
+ break;
974
+ //-------------------------------------------------------------
975
+ // Folding White Space
976
+ //-------------------------------------------------------------
977
+ case ISEMAIL_CONTEXT_FWS:
978
+ // http://tools.ietf.org/html/rfc5322#section-3.2.2
979
+ // FWS = ([*WSP CRLF] 1*WSP) / obs-FWS
980
+ // ; Folding white space
981
+
982
+ // But note the erratum:
983
+ // http://www.rfc-editor.org/errata_search.php?rfc=5322&eid=1908:
984
+ // In the obsolete syntax, any amount of folding white space MAY be
985
+ // inserted where the obs-FWS rule is allowed. This creates the
986
+ // possibility of having two consecutive "folds" in a line, and
987
+ // therefore the possibility that a line which makes up a folded header
988
+ // field could be composed entirely of white space.
989
+ //
990
+ // obs-FWS = 1*([CRLF] WSP)
991
+ if ($token_prior === ISEMAIL_STRING_CR) {
992
+ if ($token === ISEMAIL_STRING_CR) {
993
+ $return_status[] = ISEMAIL_ERR_FWS_CRLF_X2; // Fatal error
994
+ break;
995
+ }
996
+
997
+ if (isset($crlf_count)) {
998
+ if (++$crlf_count > 1)
999
+ $return_status[] = ISEMAIL_DEPREC_FWS; // Multiple folds = obsolete FWS
1000
+ } else $crlf_count = 1;
1001
+ }
1002
+
1003
+ switch ($token) {
1004
+ case ISEMAIL_STRING_CR:
1005
+ if ((++$i === $raw_length) || ($email[$i] !== ISEMAIL_STRING_LF))
1006
+ $return_status[] = ISEMAIL_ERR_CR_NO_LF; // Fatal error
1007
+
1008
+ break;
1009
+ case ISEMAIL_STRING_SP:
1010
+ case ISEMAIL_STRING_HTAB:
1011
+ break;
1012
+ default:
1013
+ if ($token_prior === ISEMAIL_STRING_CR) {
1014
+ $return_status[] = ISEMAIL_ERR_FWS_CRLF_END; // Fatal error
1015
+ break;
1016
+ }
1017
+
1018
+ if (isset($crlf_count)) unset($crlf_count);
1019
+
1020
+ $context_prior = $context;
1021
+ $context = (int) array_pop($context_stack); // End of FWS
1022
+
1023
+ // http://tools.ietf.org/html/rfc5322#section-3.2.2
1024
+ // Runs of FWS, comment, or CFWS that occur between lexical tokens in a
1025
+ // structured header field are semantically interpreted as a single
1026
+ // space character.
1027
+ //
1028
+ // is_email() author's note: This *cannot* mean that we must add a
1029
+ // space to the address wherever CFWS appears. This would result in
1030
+ // any addr-spec that had CFWS outside a quoted string being invalid
1031
+ // for RFC 5321.
1032
+ // if (($context === ISEMAIL_COMPONENT_LOCALPART) || ($context === ISEMAIL_COMPONENT_DOMAIN)) {
1033
+ // $parsedata[$context] .= ISEMAIL_STRING_SP;
1034
+ // $atomlist[$context][$element_count] .= ISEMAIL_STRING_SP;
1035
+ // $element_len++;
1036
+ // }
1037
+
1038
+ $i--; // Look at this token again in the parent context
1039
+ }
1040
+
1041
+ $token_prior = $token;
1042
+ break;
1043
+ //-------------------------------------------------------------
1044
+ // A context we aren't expecting
1045
+ //-------------------------------------------------------------
1046
+ default:
1047
+ die("Unknown context: $context");
1048
+ }
1049
+
1050
+ //-echo "<td>$context|",(($end_or_die) ? 'true' : 'false'),"|$token|" . max($return_status) . "</td></tr>"; // debug
1051
+ if ((int) max($return_status) > ISEMAIL_RFC5322) break; // No point going on if we've got a fatal error
1052
+ }
1053
+
1054
+ // Some simple final tests
1055
+ if ((int) max($return_status) < ISEMAIL_RFC5322) {
1056
+ if ($context === ISEMAIL_CONTEXT_QUOTEDSTRING) $return_status[] = ISEMAIL_ERR_UNCLOSEDQUOTEDSTR; // Fatal error
1057
+ elseif ($context === ISEMAIL_CONTEXT_QUOTEDPAIR) $return_status[] = ISEMAIL_ERR_BACKSLASHEND; // Fatal error
1058
+ elseif ($context === ISEMAIL_CONTEXT_COMMENT) $return_status[] = ISEMAIL_ERR_UNCLOSEDCOMMENT; // Fatal error
1059
+ elseif ($context === ISEMAIL_COMPONENT_LITERAL) $return_status[] = ISEMAIL_ERR_UNCLOSEDDOMLIT; // Fatal error
1060
+ elseif ($token === ISEMAIL_STRING_CR) $return_status[] = ISEMAIL_ERR_FWS_CRLF_END; // Fatal error
1061
+ elseif ($parsedata[ISEMAIL_COMPONENT_DOMAIN] === '') $return_status[] = ISEMAIL_ERR_NODOMAIN; // Fatal error
1062
+ elseif ($element_len === 0) $return_status[] = ISEMAIL_ERR_DOT_END; // Fatal error
1063
+ elseif ($hyphen_flag) $return_status[] = ISEMAIL_ERR_DOMAINHYPHENEND; // Fatal error
1064
+ // http://tools.ietf.org/html/rfc5321#section-4.5.3.1.2
1065
+ // The maximum total length of a domain name or number is 255 octets.
1066
+ elseif (strlen($parsedata[ISEMAIL_COMPONENT_DOMAIN]) > 255)
1067
+ $return_status[] = ISEMAIL_RFC5322_DOMAIN_TOOLONG;
1068
+ // http://tools.ietf.org/html/rfc5321#section-4.1.2
1069
+ // Forward-path = Path
1070
+ //
1071
+ // Path = "<" [ A-d-l ":" ] Mailbox ">"
1072
+ //
1073
+ // http://tools.ietf.org/html/rfc5321#section-4.5.3.1.3
1074
+ // The maximum total length of a reverse-path or forward-path is 256
1075
+ // octets (including the punctuation and element separators).
1076
+ //
1077
+ // Thus, even without (obsolete) routing information, the Mailbox can
1078
+ // only be 254 characters long. This is confirmed by this verified
1079
+ // erratum to RFC 3696:
1080
+ //
1081
+ // http://www.rfc-editor.org/errata_search.php?rfc=3696&eid=1690
1082
+ // However, there is a restriction in RFC 2821 on the length of an
1083
+ // address in MAIL and RCPT commands of 254 characters. Since addresses
1084
+ // that do not fit in those fields are not normally useful, the upper
1085
+ // limit on address lengths should normally be considered to be 254.
1086
+ elseif (strlen($parsedata[ISEMAIL_COMPONENT_LOCALPART] . ISEMAIL_STRING_AT . $parsedata[ISEMAIL_COMPONENT_DOMAIN]) > 254)
1087
+ $return_status[] = ISEMAIL_RFC5322_TOOLONG;
1088
+ // http://tools.ietf.org/html/rfc1035#section-2.3.4
1089
+ // labels 63 octets or less
1090
+ elseif ($element_len > 63) $return_status[] = ISEMAIL_RFC5322_LABEL_TOOLONG;
1091
+ }
1092
+
1093
+ // Check DNS?
1094
+ $dns_checked = false;
1095
+
1096
+ if ($checkDNS && ((int) max($return_status) < ISEMAIL_DNSWARN) && function_exists('dns_get_record')) {
1097
+ // http://tools.ietf.org/html/rfc5321#section-2.3.5
1098
+ // Names that can
1099
+ // be resolved to MX RRs or address (i.e., A or AAAA) RRs (as discussed
1100
+ // in Section 5) are permitted, as are CNAME RRs whose targets can be
1101
+ // resolved, in turn, to MX or address RRs.
1102
+ //
1103
+ // http://tools.ietf.org/html/rfc5321#section-5.1
1104
+ // The lookup first attempts to locate an MX record associated with the
1105
+ // name. If a CNAME record is found, the resulting name is processed as
1106
+ // if it were the initial name. ... If an empty list of MXs is returned,
1107
+ // the address is treated as if it was associated with an implicit MX
1108
+ // RR, with a preference of 0, pointing to that host.
1109
+ //
1110
+ // is_email() author's note: We will regard the existence of a CNAME to be
1111
+ // sufficient evidence of the domain's existence. For performance reasons
1112
+ // we will not repeat the DNS lookup for the CNAME's target, but we will
1113
+ // raise a warning because we didn't immediately find an MX record.
1114
+ if ($element_count === 0) $parsedata[ISEMAIL_COMPONENT_DOMAIN] .= '.'; // Checking TLD DNS seems to work only if you explicitly check from the root
1115
+
1116
+ $result = @dns_get_record($parsedata[ISEMAIL_COMPONENT_DOMAIN], DNS_MX); // Not using checkdnsrr because of a suspected bug in PHP 5.3 (http://bugs.php.net/bug.php?id=51844)
1117
+
1118
+ if ((is_bool($result) && !(bool) $result))
1119
+ $return_status[] = ISEMAIL_DNSWARN_NO_RECORD; // Domain can't be found in DNS
1120
+ else {
1121
+ if (count($result) === 0) {
1122
+ $return_status[] = ISEMAIL_DNSWARN_NO_MX_RECORD; // MX-record for domain can't be found
1123
+ $result = @dns_get_record($parsedata[ISEMAIL_COMPONENT_DOMAIN], DNS_A + DNS_CNAME);
1124
+
1125
+ if (count($result) === 0)
1126
+ $return_status[] = ISEMAIL_DNSWARN_NO_RECORD; // No usable records for the domain can be found
1127
+ } else $dns_checked = true;
1128
+ }
1129
+ }
1130
+
1131
+ // Check for TLD addresses
1132
+ // -----------------------
1133
+ // TLD addresses are specifically allowed in RFC 5321 but they are
1134
+ // unusual to say the least. We will allocate a separate
1135
+ // status to these addresses on the basis that they are more likely
1136
+ // to be typos than genuine addresses (unless we've already
1137
+ // established that the domain does have an MX record)
1138
+ //
1139
+ // http://tools.ietf.org/html/rfc5321#section-2.3.5
1140
+ // In the case
1141
+ // of a top-level domain used by itself in an email address, a single
1142
+ // string is used without any dots. This makes the requirement,
1143
+ // described in more detail below, that only fully-qualified domain
1144
+ // names appear in SMTP transactions on the public Internet,
1145
+ // particularly important where top-level domains are involved.
1146
+ //
1147
+ // TLD format
1148
+ // ----------
1149
+ // The format of TLDs has changed a number of times. The standards
1150
+ // used by IANA have been largely ignored by ICANN, leading to
1151
+ // confusion over the standards being followed. These are not defined
1152
+ // anywhere, except as a general component of a DNS host name (a label).
1153
+ // However, this could potentially lead to 123.123.123.123 being a
1154
+ // valid DNS name (rather than an IP address) and thereby creating
1155
+ // an ambiguity. The most authoritative statement on TLD formats that
1156
+ // the author can find is in a (rejected!) erratum to RFC 1123
1157
+ // submitted by John Klensin, the author of RFC 5321:
1158
+ //
1159
+ // http://www.rfc-editor.org/errata_search.php?rfc=1123&eid=1353
1160
+ // However, a valid host name can never have the dotted-decimal
1161
+ // form #.#.#.#, since this change does not permit the highest-level
1162
+ // component label to start with a digit even if it is not all-numeric.
1163
+ if (!$dns_checked && ((int) max($return_status) < ISEMAIL_DNSWARN)) {
1164
+ if ($element_count === 0) $return_status[] = ISEMAIL_RFC5321_TLD;
1165
+
1166
+ if (is_numeric($atomlist[ISEMAIL_COMPONENT_DOMAIN][$element_count][0]))
1167
+ $return_status[] = ISEMAIL_RFC5321_TLDNUMERIC;
1168
+ }
1169
+
1170
+ $return_status = array_unique($return_status);
1171
+ $final_status = (int) max($return_status);
1172
+
1173
+ if (count($return_status) !== 1) array_shift($return_status); // remove redundant ISEMAIL_VALID
1174
+
1175
+ $parsedata['status'] = $return_status;
1176
+
1177
+ if ($final_status < $threshold) $final_status = ISEMAIL_VALID;
1178
+
1179
+ return ($diagnose) ? $final_status : ($final_status < ISEMAIL_THRESHOLD);
1180
+ }
1181
+ }
1182
+ ?>
vendor/punycode/Exception/DomainOutOfBoundsException.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace SendGridTrueBV\Exception;
4
+
5
+ require_once plugin_dir_path( __FILE__ ) . 'OutOfBoundsException.php';
6
+
7
+ /**
8
+ * Class DomainOutOfBoundsException
9
+ * @package SendGridTrueBV\Exception
10
+ * @author Sebastian Kroczek <sk@xbug.de>
11
+ */
12
+ class DomainOutOfBoundsException extends OutOfBoundsException
13
+ {
14
+
15
+ }
vendor/punycode/Exception/LabelOutOfBoundsException.php ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace SendGridTrueBV\Exception;
3
+
4
+ require_once plugin_dir_path( __FILE__ ) . 'OutOfBoundsException.php';
5
+
6
+ /**
7
+ * Class LabelOutOfBoundsException
8
+ * @package SendGridTrueBV\Exception
9
+ * @author Sebastian Kroczek <sk@xbug.de>
10
+ */
11
+ class LabelOutOfBoundsException extends OutOfBoundsException
12
+ {
13
+ }
vendor/punycode/Exception/OutOfBoundsException.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace SendGridTrueBV\Exception;
3
+ /**
4
+ * Class OutOfBoundsException
5
+ * @package SendGridTrueBV\Exception
6
+ * @author Sebastian Kroczek <sk@xbug.de>
7
+ */
8
+ class OutOfBoundsException extends \RuntimeException
9
+ {
10
+ }
vendor/punycode/LICENSE ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright (c) 2014 TrueServer B.V.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is furnished
8
+ to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
vendor/punycode/Punycode.php ADDED
@@ -0,0 +1,363 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace SendGridTrueBV;
3
+
4
+ require_once plugin_dir_path( __FILE__ ) . 'Exception/DomainOutOfBoundsException.php';
5
+ require_once plugin_dir_path( __FILE__ ) . 'Exception/LabelOutOfBoundsException.php';
6
+
7
+ use SendGridTrueBV\Exception\DomainOutOfBoundsException;
8
+ use SendGridTrueBV\Exception\LabelOutOfBoundsException;
9
+
10
+ /**
11
+ * Punycode implementation as described in RFC 3492
12
+ *
13
+ * @link http://tools.ietf.org/html/rfc3492
14
+ */
15
+ class Punycode
16
+ {
17
+
18
+ /**
19
+ * Bootstring parameter values
20
+ *
21
+ */
22
+ const BASE = 36;
23
+ const TMIN = 1;
24
+ const TMAX = 26;
25
+ const SKEW = 38;
26
+ const DAMP = 700;
27
+ const INITIAL_BIAS = 72;
28
+ const INITIAL_N = 128;
29
+ const PREFIX = 'xn--';
30
+ const DELIMITER = '-';
31
+
32
+ /**
33
+ * Encode table
34
+ *
35
+ * @param array
36
+ */
37
+ protected static $encodeTable = array(
38
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
39
+ 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
40
+ 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
41
+ );
42
+
43
+ /**
44
+ * Decode table
45
+ *
46
+ * @param array
47
+ */
48
+ protected static $decodeTable = array(
49
+ 'a' => 0, 'b' => 1, 'c' => 2, 'd' => 3, 'e' => 4, 'f' => 5,
50
+ 'g' => 6, 'h' => 7, 'i' => 8, 'j' => 9, 'k' => 10, 'l' => 11,
51
+ 'm' => 12, 'n' => 13, 'o' => 14, 'p' => 15, 'q' => 16, 'r' => 17,
52
+ 's' => 18, 't' => 19, 'u' => 20, 'v' => 21, 'w' => 22, 'x' => 23,
53
+ 'y' => 24, 'z' => 25, '0' => 26, '1' => 27, '2' => 28, '3' => 29,
54
+ '4' => 30, '5' => 31, '6' => 32, '7' => 33, '8' => 34, '9' => 35
55
+ );
56
+
57
+ /**
58
+ * Character encoding
59
+ *
60
+ * @param string
61
+ */
62
+ protected $encoding;
63
+
64
+ /**
65
+ * Constructor
66
+ *
67
+ * @param string $encoding Character encoding
68
+ */
69
+ public function __construct($encoding = 'UTF-8')
70
+ {
71
+ $this->encoding = $encoding;
72
+ }
73
+
74
+ /**
75
+ * Encode a domain to its Punycode version
76
+ *
77
+ * @param string $input Domain name in Unicode to be encoded
78
+ * @return string Punycode representation in ASCII
79
+ */
80
+ public function encode($input)
81
+ {
82
+ $input = mb_strtolower($input, $this->encoding);
83
+ $parts = explode('.', $input);
84
+ foreach ($parts as &$part) {
85
+ $length = strlen($part);
86
+ if ($length < 1) {
87
+ throw new LabelOutOfBoundsException(sprintf('The length of any one label is limited to between 1 and 63 octets, but %s given.', $length));
88
+ }
89
+ $part = $this->encodePart($part);
90
+ }
91
+ $output = implode('.', $parts);
92
+ $length = strlen($output);
93
+ if ($length > 255) {
94
+ throw new DomainOutOfBoundsException(sprintf('A full domain name is limited to 255 octets (including the separators), %s given.', $length));
95
+ }
96
+
97
+ return $output;
98
+ }
99
+
100
+ /**
101
+ * Encode a part of a domain name, such as tld, to its Punycode version
102
+ *
103
+ * @param string $input Part of a domain name
104
+ * @return string Punycode representation of a domain part
105
+ */
106
+ protected function encodePart($input)
107
+ {
108
+ $codePoints = $this->listCodePoints($input);
109
+
110
+ $n = static::INITIAL_N;
111
+ $bias = static::INITIAL_BIAS;
112
+ $delta = 0;
113
+ $h = $b = count($codePoints['basic']);
114
+
115
+ $output = '';
116
+ foreach ($codePoints['basic'] as $code) {
117
+ $output .= $this->codePointToChar($code);
118
+ }
119
+ if ($input === $output) {
120
+ return $output;
121
+ }
122
+ if ($b > 0) {
123
+ $output .= static::DELIMITER;
124
+ }
125
+
126
+ $codePoints['nonBasic'] = array_unique($codePoints['nonBasic']);
127
+ sort($codePoints['nonBasic']);
128
+
129
+ $i = 0;
130
+ $length = mb_strlen($input, $this->encoding);
131
+ while ($h < $length) {
132
+ $m = $codePoints['nonBasic'][$i++];
133
+ $delta = $delta + ($m - $n) * ($h + 1);
134
+ $n = $m;
135
+
136
+ foreach ($codePoints['all'] as $c) {
137
+ if ($c < $n || $c < static::INITIAL_N) {
138
+ $delta++;
139
+ }
140
+ if ($c === $n) {
141
+ $q = $delta;
142
+ for ($k = static::BASE;; $k += static::BASE) {
143
+ $t = $this->calculateThreshold($k, $bias);
144
+ if ($q < $t) {
145
+ break;
146
+ }
147
+
148
+ $code = $t + (($q - $t) % (static::BASE - $t));
149
+ $output .= static::$encodeTable[$code];
150
+
151
+ $q = ($q - $t) / (static::BASE - $t);
152
+ }
153
+
154
+ $output .= static::$encodeTable[$q];
155
+ $bias = $this->adapt($delta, $h + 1, ($h === $b));
156
+ $delta = 0;
157
+ $h++;
158
+ }
159
+ }
160
+
161
+ $delta++;
162
+ $n++;
163
+ }
164
+ $out = static::PREFIX . $output;
165
+ $length = strlen($out);
166
+ if ($length > 63 || $length < 1) {
167
+ throw new LabelOutOfBoundsException(sprintf('The length of any one label is limited to between 1 and 63 octets, but %s given.', $length));
168
+ }
169
+
170
+ return $out;
171
+ }
172
+
173
+ /**
174
+ * Decode a Punycode domain name to its Unicode counterpart
175
+ *
176
+ * @param string $input Domain name in Punycode
177
+ * @return string Unicode domain name
178
+ */
179
+ public function decode($input)
180
+ {
181
+ $input = strtolower($input);
182
+ $parts = explode('.', $input);
183
+ foreach ($parts as &$part) {
184
+ $length = strlen($part);
185
+ if ($length > 63 || $length < 1) {
186
+ throw new LabelOutOfBoundsException(sprintf('The length of any one label is limited to between 1 and 63 octets, but %s given.', $length));
187
+ }
188
+ if (strpos($part, static::PREFIX) !== 0) {
189
+ continue;
190
+ }
191
+
192
+ $part = substr($part, strlen(static::PREFIX));
193
+ $part = $this->decodePart($part);
194
+ }
195
+ $output = implode('.', $parts);
196
+ $length = strlen($output);
197
+ if ($length > 255) {
198
+ throw new DomainOutOfBoundsException(sprintf('A full domain name is limited to 255 octets (including the separators), %s given.', $length));
199
+ }
200
+
201
+ return $output;
202
+ }
203
+
204
+ /**
205
+ * Decode a part of domain name, such as tld
206
+ *
207
+ * @param string $input Part of a domain name
208
+ * @return string Unicode domain part
209
+ */
210
+ protected function decodePart($input)
211
+ {
212
+ $n = static::INITIAL_N;
213
+ $i = 0;
214
+ $bias = static::INITIAL_BIAS;
215
+ $output = '';
216
+
217
+ $pos = strrpos($input, static::DELIMITER);
218
+ if ($pos !== false) {
219
+ $output = substr($input, 0, $pos++);
220
+ } else {
221
+ $pos = 0;
222
+ }
223
+
224
+ $outputLength = strlen($output);
225
+ $inputLength = strlen($input);
226
+ while ($pos < $inputLength) {
227
+ $oldi = $i;
228
+ $w = 1;
229
+
230
+ for ($k = static::BASE;; $k += static::BASE) {
231
+ $digit = static::$decodeTable[$input[$pos++]];
232
+ $i = $i + ($digit * $w);
233
+ $t = $this->calculateThreshold($k, $bias);
234
+
235
+ if ($digit < $t) {
236
+ break;
237
+ }
238
+
239
+ $w = $w * (static::BASE - $t);
240
+ }
241
+
242
+ $bias = $this->adapt($i - $oldi, ++$outputLength, ($oldi === 0));
243
+ $n = $n + (int) ($i / $outputLength);
244
+ $i = $i % ($outputLength);
245
+ $output = mb_substr($output, 0, $i, $this->encoding) . $this->codePointToChar($n) . mb_substr($output, $i, $outputLength - 1, $this->encoding);
246
+
247
+ $i++;
248
+ }
249
+
250
+ return $output;
251
+ }
252
+
253
+ /**
254
+ * Calculate the bias threshold to fall between TMIN and TMAX
255
+ *
256
+ * @param integer $k
257
+ * @param integer $bias
258
+ * @return integer
259
+ */
260
+ protected function calculateThreshold($k, $bias)
261
+ {
262
+ if ($k <= $bias + static::TMIN) {
263
+ return static::TMIN;
264
+ } elseif ($k >= $bias + static::TMAX) {
265
+ return static::TMAX;
266
+ }
267
+ return $k - $bias;
268
+ }
269
+
270
+ /**
271
+ * Bias adaptation
272
+ *
273
+ * @param integer $delta
274
+ * @param integer $numPoints
275
+ * @param boolean $firstTime
276
+ * @return integer
277
+ */
278
+ protected function adapt($delta, $numPoints, $firstTime)
279
+ {
280
+ $delta = (int) (
281
+ ($firstTime)
282
+ ? $delta / static::DAMP
283
+ : $delta / 2
284
+ );
285
+ $delta += (int) ($delta / $numPoints);
286
+
287
+ $k = 0;
288
+ while ($delta > ((static::BASE - static::TMIN) * static::TMAX) / 2) {
289
+ $delta = (int) ($delta / (static::BASE - static::TMIN));
290
+ $k = $k + static::BASE;
291
+ }
292
+ $k = $k + (int) (((static::BASE - static::TMIN + 1) * $delta) / ($delta + static::SKEW));
293
+
294
+ return $k;
295
+ }
296
+
297
+ /**
298
+ * List code points for a given input
299
+ *
300
+ * @param string $input
301
+ * @return array Multi-dimension array with basic, non-basic and aggregated code points
302
+ */
303
+ protected function listCodePoints($input)
304
+ {
305
+ $codePoints = array(
306
+ 'all' => array(),
307
+ 'basic' => array(),
308
+ 'nonBasic' => array(),
309
+ );
310
+
311
+ $length = mb_strlen($input, $this->encoding);
312
+ for ($i = 0; $i < $length; $i++) {
313
+ $char = mb_substr($input, $i, 1, $this->encoding);
314
+ $code = $this->charToCodePoint($char);
315
+ if ($code < 128) {
316
+ $codePoints['all'][] = $codePoints['basic'][] = $code;
317
+ } else {
318
+ $codePoints['all'][] = $codePoints['nonBasic'][] = $code;
319
+ }
320
+ }
321
+
322
+ return $codePoints;
323
+ }
324
+
325
+ /**
326
+ * Convert a single or multi-byte character to its code point
327
+ *
328
+ * @param string $char
329
+ * @return integer
330
+ */
331
+ protected function charToCodePoint($char)
332
+ {
333
+ $code = ord($char[0]);
334
+ if ($code < 128) {
335
+ return $code;
336
+ } elseif ($code < 224) {
337
+ return (($code - 192) * 64) + (ord($char[1]) - 128);
338
+ } elseif ($code < 240) {
339
+ return (($code - 224) * 4096) + ((ord($char[1]) - 128) * 64) + (ord($char[2]) - 128);
340
+ } else {
341
+ return (($code - 240) * 262144) + ((ord($char[1]) - 128) * 4096) + ((ord($char[2]) - 128) * 64) + (ord($char[3]) - 128);
342
+ }
343
+ }
344
+
345
+ /**
346
+ * Convert a code point to its single or multi-byte character
347
+ *
348
+ * @param integer $code
349
+ * @return string
350
+ */
351
+ protected function codePointToChar($code)
352
+ {
353
+ if ($code <= 0x7F) {
354
+ return chr($code);
355
+ } elseif ($code <= 0x7FF) {
356
+ return chr(($code >> 6) + 192) . chr(($code & 63) + 128);
357
+ } elseif ($code <= 0xFFFF) {
358
+ return chr(($code >> 12) + 224) . chr((($code >> 6) & 63) + 128) . chr(($code & 63) + 128);
359
+ } else {
360
+ return chr(($code >> 18) + 240) . chr((($code >> 12) & 63) + 128) . chr((($code >> 6) & 63) + 128) . chr(($code & 63) + 128);
361
+ }
362
+ }
363
+ }
view/css/sendgrid.css CHANGED
@@ -394,6 +394,14 @@
394
  width: 435px;
395
  }
396
 
 
 
 
 
 
 
 
 
397
  .sengrid-settings-nav-bar {
398
  font-size: 0.9em;
399
  padding-bottom: 0px;
394
  width: 435px;
395
  }
396
 
397
+ .sendgrid-settings-select {
398
+ width: 350px;
399
+ }
400
+
401
+ .sendgrid-settings-key {
402
+ width: 350px;
403
+ }
404
+
405
  .sengrid-settings-nav-bar {
406
  font-size: 0.9em;
407
  padding-bottom: 0px;
view/js/sendgrid.settings-v1.7.3.js CHANGED
@@ -121,4 +121,32 @@ jQuery(document).ready(function($) {
121
  $("#sendgrid_form_mc").submit();
122
  }
123
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  });
121
  $("#sendgrid_form_mc").submit();
122
  }
123
  });
124
+
125
+ // save form on unfocus general apikey
126
+ if ( typeof old_general_api_key == 'undefined' ) {
127
+ old_general_api_key = $("#sendgrid_general_apikey").val();
128
+ }
129
+ $("#sendgrid_general_apikey").focusout(function() {
130
+ var new_general_api_key = $("#sendgrid_general_apikey").val();
131
+ if ( old_general_api_key != new_general_api_key ) {
132
+ $("#sendgrid_general_settings_form").submit();
133
+ }
134
+ });
135
+
136
+
137
+ $('#select_unsubscribe_group').select2({
138
+ minimumResultsForSearch: 20
139
+ });
140
+
141
+ $('#content_type').select2({
142
+ minimumResultsForSearch: Infinity
143
+ });
144
+
145
+ $('#auth_method').select2({
146
+ minimumResultsForSearch: Infinity
147
+ });
148
+
149
+ $('#send_method').select2({
150
+ minimumResultsForSearch: Infinity
151
+ });
152
  });
view/sendgrid_settings_general.php CHANGED
@@ -1,5 +1,5 @@
1
  <?php if ( $active_tab == 'general' ): ?>
2
- <form class="form-table" name="sendgrid_form" method="POST" action="<?php echo str_replace( '%7E', '~', $_SERVER['REQUEST_URI'] ); ?>">
3
  <table class="form-table">
4
  <tbody>
5
  <tr valign="top">
@@ -10,7 +10,7 @@
10
  <tr valign="top">
11
  <th scope="row"><?php _e("Authentication method: "); ?></th>
12
  <td>
13
- <select name="auth_method" id="auth_method" <?php disabled( $is_env_auth_method ); ?> >
14
  <option value="apikey" id="apikey" <?php echo ( 'apikey' == $auth_method ) ? 'selected' : '' ?>><?php _e('Api Key') ?></option>
15
  <option value="credentials" id="credentials" <?php echo ( 'credentials' == $auth_method ) ? 'selected' : '' ?>><?php _e('Username&Password') ?></option>
16
  <?php if ( ! in_array( $auth_method, Sendgrid_Tools::$allowed_auth_methods ) ) { ?>
@@ -20,9 +20,9 @@
20
  </td>
21
  </tr>
22
  <tr valign="top" class="apikey" style="display: none;">
23
- <th scope="row"><?php _e("API key: "); ?></th>
24
  <td>
25
- <input type="password" name="sendgrid_apikey" value="<?php echo ( $is_env_api_key ? "************" : $api_key ); ?>" size="50" <?php disabled( $is_env_api_key ); ?>>
26
  </td>
27
  </tr>
28
  <tr valign="top" class="credentials" style="display: none;">
@@ -40,7 +40,7 @@
40
  <tr valign="top" class="send_method" style="display: none;">
41
  <th scope="row"><?php _e("Send Mail with: "); ?></th>
42
  <td>
43
- <select name="send_method" id="send_method" <?php disabled( defined('SENDGRID_SEND_METHOD') ); ?>>
44
  <?php foreach ( $allowed_send_methods as $method ): ?>
45
  <option value="<?php echo strtolower( $method ); ?>" <?php echo ( strtolower( $method ) == $send_method ) ? 'selected' : '' ?>><?php _e( $method ) ?></option>
46
  <?php endforeach; ?>
@@ -124,12 +124,32 @@
124
  <tr valign="top">
125
  <th scope="row"><?php _e("Content-type: "); ?></th>
126
  <td>
127
- <select name="content_type" id="content_type" <?php disabled( $is_env_content_type ); ?> >
128
  <option value="plaintext" id="plaintext" <?php echo ( 'plaintext' == $content_type ) ? 'selected' : '' ?>><?php _e('text/plain') ?></option>
129
  <option value="html" id="html" <?php echo ( 'html' == $content_type ) ? 'selected' : '' ?>><?php _e('text/html') ?></option>
130
  </select>
131
  </td>
132
  </tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  </tbody>
134
  </table>
135
  <br />
1
  <?php if ( $active_tab == 'general' ): ?>
2
+ <form class="form-table" name="sendgrid_form" id="sendgrid_general_settings_form" method="POST" action="<?php echo str_replace( '%7E', '~', $_SERVER['REQUEST_URI'] ); ?>">
3
  <table class="form-table">
4
  <tbody>
5
  <tr valign="top">
10
  <tr valign="top">
11
  <th scope="row"><?php _e("Authentication method: "); ?></th>
12
  <td>
13
+ <select name="auth_method" class="sendgrid-settings-select" id="auth_method" <?php disabled( $is_env_auth_method ); ?> >
14
  <option value="apikey" id="apikey" <?php echo ( 'apikey' == $auth_method ) ? 'selected' : '' ?>><?php _e('Api Key') ?></option>
15
  <option value="credentials" id="credentials" <?php echo ( 'credentials' == $auth_method ) ? 'selected' : '' ?>><?php _e('Username&Password') ?></option>
16
  <?php if ( ! in_array( $auth_method, Sendgrid_Tools::$allowed_auth_methods ) ) { ?>
20
  </td>
21
  </tr>
22
  <tr valign="top" class="apikey" style="display: none;">
23
+ <th scope="row"><?php _e("API Key: "); ?></th>
24
  <td>
25
+ <input type="password" id="sendgrid_general_apikey" name="sendgrid_apikey" class="sendgrid-settings-key" value="<?php echo ( $is_env_api_key ? "************" : $api_key ); ?>" <?php disabled( $is_env_api_key ); ?>>
26
  </td>
27
  </tr>
28
  <tr valign="top" class="credentials" style="display: none;">
40
  <tr valign="top" class="send_method" style="display: none;">
41
  <th scope="row"><?php _e("Send Mail with: "); ?></th>
42
  <td>
43
+ <select name="send_method" class="sendgrid-settings-select" id="send_method" <?php disabled( defined('SENDGRID_SEND_METHOD') ); ?>>
44
  <?php foreach ( $allowed_send_methods as $method ): ?>
45
  <option value="<?php echo strtolower( $method ); ?>" <?php echo ( strtolower( $method ) == $send_method ) ? 'selected' : '' ?>><?php _e( $method ) ?></option>
46
  <?php endforeach; ?>
124
  <tr valign="top">
125
  <th scope="row"><?php _e("Content-type: "); ?></th>
126
  <td>
127
+ <select name="content_type" class="sendgrid-settings-select" id="content_type" <?php disabled( $is_env_content_type ); ?> >
128
  <option value="plaintext" id="plaintext" <?php echo ( 'plaintext' == $content_type ) ? 'selected' : '' ?>><?php _e('text/plain') ?></option>
129
  <option value="html" id="html" <?php echo ( 'html' == $content_type ) ? 'selected' : '' ?>><?php _e('text/html') ?></option>
130
  </select>
131
  </td>
132
  </tr>
133
+ <tr valign="top">
134
+ <th scope="row"><?php _e("Unsubscribe Group: "); ?></th>
135
+ <td>
136
+ <select id="select_unsubscribe_group" class="sendgrid-settings-select" name="unsubscribe_group" <?php disabled( $is_env_unsubscribe_group ); ?> <?php disabled( $no_permission_on_unsubscribe_groups ); ?>>
137
+ <option value="0"><?php _e("Global Unsubscribe"); ?></option>
138
+ <?php
139
+ if ( false != $unsubscribe_groups ) {
140
+ foreach ( $unsubscribe_groups as $key => $group ) {
141
+ if ( $unsubscribe_group_id == $group['id'] ) {
142
+ echo '<option value="' . $group['id'] . '" selected="selected">' . $group['name'] . '</option>';
143
+ } else {
144
+ echo '<option value="' . $group['id'] . '">' . $group['name'] . '</option>';
145
+ }
146
+ }
147
+ }
148
+ ?>
149
+ </select>
150
+ <p class="description"><?php _e("User will have the option to unsubscribe from the selected group. <br /> The API Key needs to have 'Unsubscribe Groups' permissions to be able to select a group.") ?></p>
151
+ </td>
152
+ </tr>
153
  </tbody>
154
  </table>
155
  <br />
view/sendgrid_settings_nlvx.php CHANGED
@@ -8,7 +8,7 @@
8
  </td>
9
  </tr>
10
  <tr valign="top" class="mc_apikey">
11
- <th scope="row"><?php _e("API key: "); ?></th>
12
  <td>
13
  <input type="password" id="mc_apikey" name="sendgrid_mc_apikey" value="<?php echo ( $is_env_mc_api_key ? "************" : $mc_api_key ); ?>" size="50" <?php disabled( $is_env_mc_api_key ); ?>>
14
  <p class="description"><?php _e('An API Key to use for uploading contacts to SendGrid. This API Key needs to have full Marketing Campaigns permissions.') ?></p>
8
  </td>
9
  </tr>
10
  <tr valign="top" class="mc_apikey">
11
+ <th scope="row"><?php _e("API Key: "); ?></th>
12
  <td>
13
  <input type="password" id="mc_apikey" name="sendgrid_mc_apikey" value="<?php echo ( $is_env_mc_api_key ? "************" : $mc_api_key ); ?>" size="50" <?php disabled( $is_env_mc_api_key ); ?>>
14
  <p class="description"><?php _e('An API Key to use for uploading contacts to SendGrid. This API Key needs to have full Marketing Campaigns permissions.') ?></p>
wpsendgrid.php CHANGED
@@ -3,7 +3,7 @@
3
  Plugin Name: SendGrid
4
  Plugin URI: http://wordpress.org/plugins/sendgrid-email-delivery-simplified/
5
  Description: Email Delivery. Simplified. SendGrid's cloud-based email infrastructure relieves businesses of the cost and complexity of maintaining custom email systems. SendGrid provides reliable delivery, scalability and real-time analytics along with flexible APIs that make custom integration a breeze.
6
- Version: 1.9.3
7
  Author: SendGrid
8
  Author URI: http://sendgrid.com
9
  Text Domain: sendgrid-email-delivery-simplified
3
  Plugin Name: SendGrid
4
  Plugin URI: http://wordpress.org/plugins/sendgrid-email-delivery-simplified/
5
  Description: Email Delivery. Simplified. SendGrid's cloud-based email infrastructure relieves businesses of the cost and complexity of maintaining custom email systems. SendGrid provides reliable delivery, scalability and real-time analytics along with flexible APIs that make custom integration a breeze.
6
+ Version: 1.9.4
7
  Author: SendGrid
8
  Author URI: http://sendgrid.com
9
  Text Domain: sendgrid-email-delivery-simplified