Events Manager - Version 5.9.7.2

Version Description

  • fixed CSV injection vulnerability which can allow malicious text to be exported to CSV files and parsed by Spreadsheet
  • fixed #_BOOKINGCUTOFF text date formats not getting translated correctly
  • added ability to programatically add attachments to booking emails for future features
  • fixed/updated casing of functions in phpMailer function calls (prefiously backward compatible),
  • added reply-to headers for wp_mail emails circumventing some plugins forcing from email address fields,
  • fixed email testing function ignoring sender name, encryption and autotls options
  • fixed ical apple structure breaking parsing in google (and possible others)
  • updated events-manager.js to replace deprecated use of delegate/bind with on/off equivalents
  • added ticket ordering
  • fixed editing booking tickets in admin causing validation errors on 0 values
  • fixed PHP Warning generated when adding Booking Notes which prevent a redirect with WP_DEBUG enabled
  • fixed Events tab on profile pages stripping last character with the BuddyPress Nouveau theme
  • changed blank value to 'no location' when viewing the event bookings admin page for locationless events
  • changed EM_Notices by making them JsonSerializable
  • fixed (hopefully!) the elusive and hard to reproduce "variable mismatch" error when submitting new form in some rare circumstances
Download this release

Release Info

Developer netweblogic
Plugin Icon 128x128 Events Manager
Version 5.9.7.2
Comparing to
See all releases

Code changes from version 5.9.7.1 to 5.9.7.2

admin/em-bookings.php CHANGED
@@ -108,7 +108,11 @@ function em_bookings_event(){
108
  </p>
109
  <p>
110
  <strong><?php esc_html_e('Location','events-manager'); ?></strong> :
111
- <a class="row-title" href="<?php echo admin_url(); ?>post.php?action=edit&amp;post=<?php echo $EM_Event->get_location()->post_id ?>"><?php echo ($EM_Event->get_location()->location_name); ?></a>
 
 
 
 
112
  </p>
113
  </div>
114
  <h2><?php esc_html_e('Bookings','events-manager'); ?></h2>
108
  </p>
109
  <p>
110
  <strong><?php esc_html_e('Location','events-manager'); ?></strong> :
111
+ <?php if( $EM_Event->location_id == 0 ): ?>
112
+ <em><?php esc_html_e('No Location', 'events-manager'); ?></em>
113
+ <?php else: ?>
114
+ <a class="row-title" href="<?php echo admin_url(); ?>post.php?action=edit&amp;post=<?php echo $EM_Event->get_location()->post_id ?>"><?php echo ($EM_Event->get_location()->location_name); ?></a>
115
+ <?php endif; ?>
116
  </p>
117
  </div>
118
  <h2><?php esc_html_e('Bookings','events-manager'); ?></h2>
admin/em-options.php CHANGED
@@ -345,7 +345,7 @@ function em_admin_email_test_ajax(){
345
  $current_user = get_user_by('id', get_current_user_id());
346
  //add filters for options used in EM_Mailer so the current supplied ones are used
347
  ob_start();
348
- function pre_option_dbem_mail_sender_name(){ return sanitize_email($_REQUEST['dbem_mail_sender_name']); }
349
  add_filter('pre_option_dbem_mail_sender_name', 'pre_option_dbem_mail_sender_name');
350
  function pre_option_dbem_mail_sender_address(){ return sanitize_text_field($_REQUEST['dbem_mail_sender_address']); }
351
  add_filter('pre_option_dbem_mail_sender_address', 'pre_option_dbem_mail_sender_address');
@@ -353,6 +353,10 @@ function em_admin_email_test_ajax(){
353
  add_filter('pre_option_dbem_rsvp_mail_send_method', 'pre_option_dbem_rsvp_mail_send_method');
354
  function pre_option_dbem_rsvp_mail_port(){ return sanitize_text_field($_REQUEST['dbem_rsvp_mail_port']); }
355
  add_filter('pre_option_dbem_rsvp_mail_port', 'pre_option_dbem_rsvp_mail_port');
 
 
 
 
356
  function pre_option_dbem_rsvp_mail_SMTPAuth(){ return sanitize_text_field($_REQUEST['dbem_rsvp_mail_SMTPAuth']); }
357
  add_filter('pre_option_dbem_rsvp_mail_SMTPAuth', 'pre_option_dbem_rsvp_mail_SMTPAuth');
358
  function pre_option_dbem_smtp_host(){ return sanitize_text_field($_REQUEST['dbem_smtp_host']); }
345
  $current_user = get_user_by('id', get_current_user_id());
346
  //add filters for options used in EM_Mailer so the current supplied ones are used
347
  ob_start();
348
+ function pre_option_dbem_mail_sender_name(){ return sanitize_text_field($_REQUEST['dbem_mail_sender_name']); }
349
  add_filter('pre_option_dbem_mail_sender_name', 'pre_option_dbem_mail_sender_name');
350
  function pre_option_dbem_mail_sender_address(){ return sanitize_text_field($_REQUEST['dbem_mail_sender_address']); }
351
  add_filter('pre_option_dbem_mail_sender_address', 'pre_option_dbem_mail_sender_address');
353
  add_filter('pre_option_dbem_rsvp_mail_send_method', 'pre_option_dbem_rsvp_mail_send_method');
354
  function pre_option_dbem_rsvp_mail_port(){ return sanitize_text_field($_REQUEST['dbem_rsvp_mail_port']); }
355
  add_filter('pre_option_dbem_rsvp_mail_port', 'pre_option_dbem_rsvp_mail_port');
356
+ function pre_option_dbem_smtp_encryption(){ return sanitize_text_field($_REQUEST['dbem_smtp_encryption']); }
357
+ add_filter('pre_option_dbem_smtp_encryption', 'pre_option_dbem_smtp_encryption');
358
+ function pre_option_dbem_smtp_autotls(){ return sanitize_text_field($_REQUEST['dbem_smtp_autotls']); }
359
+ add_filter('pre_option_dbem_smtp_autotls', 'pre_option_dbem_smtp_autotls');
360
  function pre_option_dbem_rsvp_mail_SMTPAuth(){ return sanitize_text_field($_REQUEST['dbem_rsvp_mail_SMTPAuth']); }
361
  add_filter('pre_option_dbem_rsvp_mail_SMTPAuth', 'pre_option_dbem_rsvp_mail_SMTPAuth');
362
  function pre_option_dbem_smtp_host(){ return sanitize_text_field($_REQUEST['dbem_smtp_host']); }
admin/settings/tabs/bookings.php CHANGED
@@ -102,7 +102,7 @@
102
  ?>
103
  </table>
104
  </div> <!-- . inside -->
105
- </div> <!-- .postbox -->
106
 
107
  <div class="postbox " id="em-opt-ticket-options" >
108
  <div class="handlediv" title="<?php __('Click to toggle', 'events-manager'); ?>"><br /></div><h3><span><?php echo sprintf(__( '%s Options', 'events-manager'),__('Ticket','events-manager')); ?> </span></h3>
@@ -115,12 +115,13 @@
115
  em_options_radio_binary ( __( 'Show member-only tickets?', 'events-manager'), 'dbem_bookings_tickets_show_member_tickets', sprintf(__('%s must be set to yes for this to work.', 'events-manager'), '<strong>'.__( 'Show unavailable tickets?', 'events-manager').'</strong>').' '.__( 'If there are member-only tickets, you can choose whether or not to show these tickets to guests.','events-manager') );
116
 
117
  em_options_radio_binary ( __( 'Show multiple tickets if logged out?', 'events-manager'), 'dbem_bookings_tickets_show_loggedout', __( 'If guests cannot make bookings, they will be asked to register in order to book. However, enabling this will still show available tickets.', 'events-manager') );
118
- $ticket_orders = array(
 
119
  'ticket_price DESC, ticket_name ASC'=>__('Ticket Price (Descending)','events-manager'),
120
  'ticket_price ASC, ticket_name ASC'=>__('Ticket Price (Ascending)','events-manager'),
121
  'ticket_name ASC, ticket_price DESC'=>__('Ticket Name (Ascending)','events-manager'),
122
  'ticket_name DESC, ticket_price DESC'=>__('Ticket Name (Descending)','events-manager')
123
- );
124
  em_options_select ( __( 'Order Tickets By', 'events-manager'), 'dbem_bookings_tickets_orderby', $ticket_orders, __( 'Choose which order your tickets appear.', 'events-manager') );
125
  echo $save_button;
126
  ?>
102
  ?>
103
  </table>
104
  </div> <!-- . inside -->
105
+ </div> <!-- .postbox -->
106
 
107
  <div class="postbox " id="em-opt-ticket-options" >
108
  <div class="handlediv" title="<?php __('Click to toggle', 'events-manager'); ?>"><br /></div><h3><span><?php echo sprintf(__( '%s Options', 'events-manager'),__('Ticket','events-manager')); ?> </span></h3>
115
  em_options_radio_binary ( __( 'Show member-only tickets?', 'events-manager'), 'dbem_bookings_tickets_show_member_tickets', sprintf(__('%s must be set to yes for this to work.', 'events-manager'), '<strong>'.__( 'Show unavailable tickets?', 'events-manager').'</strong>').' '.__( 'If there are member-only tickets, you can choose whether or not to show these tickets to guests.','events-manager') );
116
 
117
  em_options_radio_binary ( __( 'Show multiple tickets if logged out?', 'events-manager'), 'dbem_bookings_tickets_show_loggedout', __( 'If guests cannot make bookings, they will be asked to register in order to book. However, enabling this will still show available tickets.', 'events-manager') );
118
+ em_options_radio_binary ( __( 'Enable custom ticket ordering?', 'events-manager'), 'dbem_bookings_tickets_ordering', __( 'When enabled, users can custom-order their tickets using drag and drop. If enabled, saved ordering supercedes the default ticket ordering below.', 'events-manager') );
119
+ $ticket_orders = apply_filters('em_tickets_orderby_options', array(
120
  'ticket_price DESC, ticket_name ASC'=>__('Ticket Price (Descending)','events-manager'),
121
  'ticket_price ASC, ticket_name ASC'=>__('Ticket Price (Ascending)','events-manager'),
122
  'ticket_name ASC, ticket_price DESC'=>__('Ticket Name (Ascending)','events-manager'),
123
  'ticket_name DESC, ticket_price DESC'=>__('Ticket Name (Descending)','events-manager')
124
+ ));
125
  em_options_select ( __( 'Order Tickets By', 'events-manager'), 'dbem_bookings_tickets_orderby', $ticket_orders, __( 'Choose which order your tickets appear.', 'events-manager') );
126
  echo $save_button;
127
  ?>
admin/settings/tabs/emails.php CHANGED
@@ -14,6 +14,7 @@
14
  $email_subject_tip = __('You can disable this email by leaving the subject blank.','events-manager');
15
  em_options_input_text ( __( 'Email events admin?', 'events-manager'), 'dbem_bookings_notify_admin', __( "If you would like every event booking confirmation email sent to an administrator write their email here (leave blank to not send an email).", 'events-manager').' '.__('For multiple emails, separate by commas (e.g. email1@test.com,email2@test.com,etc.)','events-manager') );
16
  em_options_radio_binary ( __( 'Email event owner?', 'events-manager'), 'dbem_bookings_contact_email', __( 'Check this option if you want the event contact to receive an email when someone books places. An email will be sent when a booking is first made (regardless if confirmed or pending)', 'events-manager') );
 
17
  ?>
18
  <tr class="em-header"><td colspan='2'><h4><?php _e('Event Admin/Owner Emails', 'events-manager'); ?></h4></td></tr>
19
  <tbody class="em-subsection">
14
  $email_subject_tip = __('You can disable this email by leaving the subject blank.','events-manager');
15
  em_options_input_text ( __( 'Email events admin?', 'events-manager'), 'dbem_bookings_notify_admin', __( "If you would like every event booking confirmation email sent to an administrator write their email here (leave blank to not send an email).", 'events-manager').' '.__('For multiple emails, separate by commas (e.g. email1@test.com,email2@test.com,etc.)','events-manager') );
16
  em_options_radio_binary ( __( 'Email event owner?', 'events-manager'), 'dbem_bookings_contact_email', __( 'Check this option if you want the event contact to receive an email when someone books places. An email will be sent when a booking is first made (regardless if confirmed or pending)', 'events-manager') );
17
+ do_action('em_options_page_booking_email_templates_options_subtop');
18
  ?>
19
  <tr class="em-header"><td colspan='2'><h4><?php _e('Event Admin/Owner Emails', 'events-manager'); ?></h4></td></tr>
20
  <tbody class="em-subsection">
buddypress/bp-em-core.php CHANGED
@@ -78,8 +78,9 @@ class BP_EM_Component extends BP_Component {
78
  /* Add 'Events' to the main user profile navigation */
79
  $event_count = EM_Events::count( array( 'scope'=>'future', 'owner'=> bp_displayed_user_id() ));
80
  if( empty($event_count) ) $event_count = 0;
 
81
  $main_nav = array(
82
- 'name' => __( 'Events', 'events-manager'). '<span>'.esc_html($event_count).'</span>',
83
  'slug' => em_bp_get_slug(),
84
  'position' => 80,
85
  'screen_function' => 'bp_em_events',
78
  /* Add 'Events' to the main user profile navigation */
79
  $event_count = EM_Events::count( array( 'scope'=>'future', 'owner'=> bp_displayed_user_id() ));
80
  if( empty($event_count) ) $event_count = 0;
81
+ $event_count_span = $event_count > 0 ? ' <span class="count">'.esc_html($event_count).'</span>':'';
82
  $main_nav = array(
83
+ 'name' => __( 'Events', 'events-manager'). $event_count_span,
84
  'slug' => em_bp_get_slug(),
85
  'position' => 80,
86
  'screen_function' => 'bp_em_events',
classes/em-booking.php CHANGED
@@ -1007,8 +1007,8 @@ class EM_Booking extends EM_Object{
1007
  global $wpdb;
1008
  if( $this->can_manage() ){
1009
  $this->get_notes();
1010
- $note = array('author'=>get_current_user_id(),'note'=>$note_text,'timestamp'=>time());
1011
- $this->notes[] = wp_kses_data($note);
1012
  $this->feedback_message = __('Booking note successfully added.','events-manager');
1013
  return $wpdb->insert(EM_META_TABLE, array('object_id'=>$this->booking_id, 'meta_key'=>'booking-note', 'meta_value'=> serialize($note)),array('%d','%s','%s'));
1014
  }
@@ -1152,8 +1152,12 @@ class EM_Booking extends EM_Object{
1152
  if( !empty($msg['user']['subject']) && $email_attendee ){
1153
  $msg['user']['subject'] = $this->output($msg['user']['subject'], 'raw');
1154
  $msg['user']['body'] = $this->output($msg['user']['body'], 'email');
 
 
 
 
1155
  //Send to the person booking
1156
- if( !$this->email_send( $msg['user']['subject'], $msg['user']['body'], $this->get_person()->user_email) ){
1157
  $result = false;
1158
  }else{
1159
  $this->mails_sent++;
@@ -1175,8 +1179,12 @@ class EM_Booking extends EM_Object{
1175
  //Only gets sent if this is a pending booking, unless approvals are disabled.
1176
  $msg['admin']['subject'] = $this->output($msg['admin']['subject'],'raw');
1177
  $msg['admin']['body'] = $this->output($msg['admin']['body'], 'email');
 
 
 
 
1178
  //email admins
1179
- if( !$this->email_send( $msg['admin']['subject'], $msg['admin']['body'], $admin_emails) && current_user_can('manage_options') ){
1180
  $this->errors[] = __('Confirmation email could not be sent to admin. Registrant should have gotten their email (only admin see this warning).','events-manager');
1181
  $result = false;
1182
  }else{
1007
  global $wpdb;
1008
  if( $this->can_manage() ){
1009
  $this->get_notes();
1010
+ $note = array('author'=>get_current_user_id(),'note'=>wp_kses_data($note_text),'timestamp'=>time());
1011
+ $this->notes[] = $note;
1012
  $this->feedback_message = __('Booking note successfully added.','events-manager');
1013
  return $wpdb->insert(EM_META_TABLE, array('object_id'=>$this->booking_id, 'meta_key'=>'booking-note', 'meta_value'=> serialize($note)),array('%d','%s','%s'));
1014
  }
1152
  if( !empty($msg['user']['subject']) && $email_attendee ){
1153
  $msg['user']['subject'] = $this->output($msg['user']['subject'], 'raw');
1154
  $msg['user']['body'] = $this->output($msg['user']['body'], 'email');
1155
+ $attachments = array();
1156
+ if( !empty($msg['user']['attachments']) && is_array($msg['user']['attachments']) ){
1157
+ $attachments = $msg['user']['attachments'];
1158
+ }
1159
  //Send to the person booking
1160
+ if( !$this->email_send( $msg['user']['subject'], $msg['user']['body'], $this->get_person()->user_email, $attachments) ){
1161
  $result = false;
1162
  }else{
1163
  $this->mails_sent++;
1179
  //Only gets sent if this is a pending booking, unless approvals are disabled.
1180
  $msg['admin']['subject'] = $this->output($msg['admin']['subject'],'raw');
1181
  $msg['admin']['body'] = $this->output($msg['admin']['body'], 'email');
1182
+ $attachments = array();
1183
+ if( !empty($msg['admin']['attachments']) && is_array($msg['admin']['attachments']) ){
1184
+ $attachments = $msg['admin']['attachments'];
1185
+ }
1186
  //email admins
1187
+ if( !$this->email_send( $msg['admin']['subject'], $msg['admin']['body'], $admin_emails, $attachments) && current_user_can('manage_options') ){
1188
  $this->errors[] = __('Confirmation email could not be sent to admin. Registrant should have gotten their email (only admin see this warning).','events-manager');
1189
  $result = false;
1190
  }else{
classes/em-bookings-table.php CHANGED
@@ -490,7 +490,11 @@ class EM_Bookings_Table{
490
  $headers[] = '<a class="em-bookings-orderby" href="#'.$col.'">'.$this->cols_template[$col].'</a>';
491
  }
492
  */
493
- $headers[$col] = $this->cols_template[$col];
 
 
 
 
494
  }
495
  }
496
  return apply_filters('em_bookings_table_get_headers', $headers, $csv, $this);
@@ -588,19 +592,31 @@ class EM_Bookings_Table{
588
  if( $format == 'html' || empty($format) ){
589
  if( !in_array($col, array('user_login', 'user_name', 'event_name', 'actions')) ) $val = esc_html($val);
590
  }
591
- //use this
592
  $val = apply_filters('em_bookings_table_rows_col_'.$col, $val, $EM_Booking, $this, $format, $object);
593
- $cols[] = apply_filters('em_bookings_table_rows_col', $val, $col, $EM_Booking, $this, $format, $object); //use the above filter instead for better performance
 
 
 
 
 
 
594
  }
595
  return $cols;
596
  }
597
 
598
  function get_row_csv($EM_Booking){
599
  $row = $this->get_row($EM_Booking, 'csv');
600
- foreach($row as $k=>$v) $row[$k] = html_entity_decode($v); //remove things like &amp; which may have been saved to the DB directly
 
 
601
  return $row;
602
  }
603
 
 
 
 
 
604
  /**
605
  * @param EM_Booking $EM_Booking
606
  * @return mixed
490
  $headers[] = '<a class="em-bookings-orderby" href="#'.$col.'">'.$this->cols_template[$col].'</a>';
491
  }
492
  */
493
+ $v = $this->cols_template[$col];
494
+ if( $csv ){
495
+ $v = self::sanitize_spreadsheet_cell($v);
496
+ }
497
+ $headers[$col] = $v;
498
  }
499
  }
500
  return apply_filters('em_bookings_table_get_headers', $headers, $csv, $this);
592
  if( $format == 'html' || empty($format) ){
593
  if( !in_array($col, array('user_login', 'user_name', 'event_name', 'actions')) ) $val = esc_html($val);
594
  }
595
+ //use this
596
  $val = apply_filters('em_bookings_table_rows_col_'.$col, $val, $EM_Booking, $this, $format, $object);
597
+ $val = apply_filters('em_bookings_table_rows_col', $val, $col, $EM_Booking, $this, $format, $object); //use the above filter instead for better performance
598
+ //csv/excel escaping
599
+ if( $format == 'csv' || $format == 'xls' || $format == 'xlsx' ){
600
+ $val = self::sanitize_spreadsheet_cell($val);
601
+ }
602
+ //add to cols
603
+ $cols[] = $val;
604
  }
605
  return $cols;
606
  }
607
 
608
  function get_row_csv($EM_Booking){
609
  $row = $this->get_row($EM_Booking, 'csv');
610
+ foreach($row as $k=>$v){
611
+ $row[$k] = html_entity_decode($v);
612
+ } //remove things like &amp; which may have been saved to the DB directly
613
  return $row;
614
  }
615
 
616
+ public static function sanitize_spreadsheet_cell( $cell ){
617
+ return preg_replace('/^([;=@\+\-])/', "'$1", $cell);
618
+ }
619
+
620
  /**
621
  * @param EM_Booking $EM_Booking
622
  * @return mixed
classes/em-event.php CHANGED
@@ -2243,7 +2243,7 @@ class EM_Event extends EM_Object{
2243
  $replace_format = em_get_date_format() .' '. em_get_hour_format();
2244
  if( $result == '#_BOOKINGSCUTOFFDATE' ) $replace_format = em_get_date_format();
2245
  if( $result == '#_BOOKINGSCUTOFFTIME' ) $replace_format = em_get_hour_format();
2246
- $replace = $this->rsvp_end()->format($replace_format);
2247
  }
2248
  break;
2249
  //Contact Person
2243
  $replace_format = em_get_date_format() .' '. em_get_hour_format();
2244
  if( $result == '#_BOOKINGSCUTOFFDATE' ) $replace_format = em_get_date_format();
2245
  if( $result == '#_BOOKINGSCUTOFFTIME' ) $replace_format = em_get_hour_format();
2246
+ $replace = $this->rsvp_end()->i18n($replace_format);
2247
  }
2248
  break;
2249
  //Contact Person
classes/em-mailer.php CHANGED
@@ -10,6 +10,11 @@ class EM_Mailer {
10
  * @var array
11
  */
12
  public $errors = array();
 
 
 
 
 
13
 
14
  /**
15
  * Send an email via the EM-saved settings.
@@ -36,21 +41,26 @@ class EM_Mailer {
36
  }
37
  if ( $emails_ok && get_option('dbem_rsvp_mail_send_method') == 'wp_mail' ){
38
  $from = get_option('dbem_mail_sender_address');
39
- $headers = get_option('dbem_mail_sender_name') ? 'From: '.get_option('dbem_mail_sender_name').' <'.$from.'>':'From: '.$from;
 
 
40
  if( get_option('dbem_smtp_html') ){ //create filter to change content type to html in wp_mail
41
  add_filter('wp_mail_content_type','EM_Mailer::return_texthtml');
42
  }
43
  //prep attachments for WP Mail, which only accept a path
44
- $wp_mail_attachments = array();
45
- foreach( $attachments as $attachment ){
46
- $wp_mail_attachments[] = $attachment['path'];
47
- }
48
  //send and handle errors
49
- $send = wp_mail($receiver, $subject, $body, $headers, $wp_mail_attachments);
 
 
 
50
  if(!$send){
51
  global $phpmailer;
52
  $this->errors[] = $phpmailer->ErrorInfo;
53
  }
 
 
54
  return $send;
55
  }elseif( $emails_ok ){
56
  $this->load_phpmailer();
@@ -60,11 +70,13 @@ class EM_Mailer {
60
  if( get_option('dbem_smtp_html') ){
61
  $mail->isHTML();
62
  }
63
- $mail->ClearAllRecipients();
64
- $mail->ClearAddresses();
65
- $mail->ClearAttachments();
 
 
66
  $mail->CharSet = 'utf-8';
67
- $mail->SetLanguage('en', dirname(__FILE__).'/');
68
  $mail->PluginDir = dirname(__FILE__).'/phpmailer/';
69
  $host = get_option('dbem_smtp_host');
70
  //if port is supplied via the host address, give that precedence over the port setting
@@ -87,23 +99,13 @@ class EM_Mailer {
87
  }
88
  $mail->SMTPAutoTLS = get_option('dbem_smtp_autotls') == 1;
89
  //add attachments
90
- if( is_array($attachments) ){
91
- foreach($attachments as $attachment){
92
- $att = array('name'=> '', 'encoding' => 'base64', 'type' => 'application/octet-stream');
93
- if( is_array($attachment) ){
94
- $att = array_merge($att, $attachment);
95
- }else{
96
- $att['path'] = $attachment;
97
- }
98
- $mail->AddAttachment($att['path'], $att['name'], $att['encoding'], $att['type']);
99
- }
100
- }
101
  if(is_array($receiver)){
102
  foreach($receiver as $receiver_email){
103
- $mail->AddAddress($receiver_email);
104
  }
105
  }else{
106
- $mail->AddAddress($receiver);
107
  }
108
  do_action('em_mailer', $mail); //$mail will still be modified
109
 
@@ -119,18 +121,21 @@ class EM_Mailer {
119
  $mail->SMTPAuth = TRUE;
120
  }
121
  do_action('em_mailer_before_send', $mail, $subject, $body, $receiver, $attachments); //$mail can still be modified
122
- $send = $mail->Send();
123
  if(!$send){
124
  $this->errors[] = $mail->ErrorInfo;
125
  }
126
  do_action('em_mailer_sent', $mail, $send); //$mail can still be modified
 
127
  return $send;
128
  }catch( phpmailerException $ex ){
129
  $this->errors[] = $mail->ErrorInfo;
 
130
  return false;
131
  }
132
  }else{
133
  $this->errors[] = __('Please supply a valid email format.', 'events-manager');
 
134
  return false;
135
  }
136
  }
@@ -143,8 +148,93 @@ class EM_Mailer {
143
  require_once ABSPATH . WPINC . '/class-smtp.php';
144
  }
145
 
 
 
 
 
146
  public static function return_texthtml(){
147
  return "text/html";
148
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  }
150
  ?>
10
  * @var array
11
  */
12
  public $errors = array();
13
+ /**
14
+ * Array of attachments which will be added to WP_Mail's phpmailer just before sending, and subsequently emptied.
15
+ * @var array
16
+ */
17
+ public static $attachments = array();
18
 
19
  /**
20
  * Send an email via the EM-saved settings.
41
  }
42
  if ( $emails_ok && get_option('dbem_rsvp_mail_send_method') == 'wp_mail' ){
43
  $from = get_option('dbem_mail_sender_address');
44
+ $headers = array();
45
+ $headers[] = get_option('dbem_mail_sender_name') ? 'From: '.get_option('dbem_mail_sender_name').' <'.$from.'>':'From: '.$from;
46
+ $headers[] = get_option('dbem_mail_sender_name') ? 'Reply-To: '.get_option('dbem_mail_sender_name').' <'.$from.'>':'From: '.$from;
47
  if( get_option('dbem_smtp_html') ){ //create filter to change content type to html in wp_mail
48
  add_filter('wp_mail_content_type','EM_Mailer::return_texthtml');
49
  }
50
  //prep attachments for WP Mail, which only accept a path
51
+ self::$attachments = $attachments;
52
+ add_action('phpmailer_init', 'EM_Mailer::add_attachments_to_mailer', 9999, 1);
 
 
53
  //send and handle errors
54
+ $send = wp_mail($receiver, $subject, $body, $headers);
55
+ //unload attachments hook
56
+ remove_action('phpmailer_init', 'EM_Mailer::add_attachments_to_mailer', 9999);
57
+ //send email
58
  if(!$send){
59
  global $phpmailer;
60
  $this->errors[] = $phpmailer->ErrorInfo;
61
  }
62
+ //cleanup
63
+ self::delete_email_attachments($attachments);
64
  return $send;
65
  }elseif( $emails_ok ){
66
  $this->load_phpmailer();
70
  if( get_option('dbem_smtp_html') ){
71
  $mail->isHTML();
72
  }
73
+ $mail->clearAllRecipients();
74
+ $mail->clearAddresses();
75
+ $mail->clearAttachments();
76
+ $mail->clearCustomHeaders();
77
+ $mail->clearReplyTos();
78
  $mail->CharSet = 'utf-8';
79
+ $mail->setLanguage('en', dirname(__FILE__).'/');
80
  $mail->PluginDir = dirname(__FILE__).'/phpmailer/';
81
  $host = get_option('dbem_smtp_host');
82
  //if port is supplied via the host address, give that precedence over the port setting
99
  }
100
  $mail->SMTPAutoTLS = get_option('dbem_smtp_autotls') == 1;
101
  //add attachments
102
+ self::add_attachments_to_mailer($mail, $attachments);
 
 
 
 
 
 
 
 
 
 
103
  if(is_array($receiver)){
104
  foreach($receiver as $receiver_email){
105
+ $mail->addAddress($receiver_email);
106
  }
107
  }else{
108
+ $mail->addAddress($receiver);
109
  }
110
  do_action('em_mailer', $mail); //$mail will still be modified
111
 
121
  $mail->SMTPAuth = TRUE;
122
  }
123
  do_action('em_mailer_before_send', $mail, $subject, $body, $receiver, $attachments); //$mail can still be modified
124
+ $send = $mail->send();
125
  if(!$send){
126
  $this->errors[] = $mail->ErrorInfo;
127
  }
128
  do_action('em_mailer_sent', $mail, $send); //$mail can still be modified
129
+ self::delete_email_attachments($attachments);
130
  return $send;
131
  }catch( phpmailerException $ex ){
132
  $this->errors[] = $mail->ErrorInfo;
133
+ self::delete_email_attachments($attachments);
134
  return false;
135
  }
136
  }else{
137
  $this->errors[] = __('Please supply a valid email format.', 'events-manager');
138
+ self::delete_email_attachments($attachments);
139
  return false;
140
  }
141
  }
148
  require_once ABSPATH . WPINC . '/class-smtp.php';
149
  }
150
 
151
+ /**
152
+ * Shorthand function for filters to return 'text/html' string.
153
+ * @return string 'text/html'
154
+ */
155
  public static function return_texthtml(){
156
  return "text/html";
157
  }
158
+
159
+ /**
160
+ * WP_Mail doesn't accept attachment meta, only an array of paths, this function post-fixes attachments to the PHPMailer object.
161
+ * @param PHPMailer $phpmailer
162
+ * @param array $attachments
163
+ */
164
+ public static function add_attachments_to_mailer( $phpmailer, $attachments = array() ){
165
+ //add attachments
166
+ $attachments = !empty($attachments) ? $attachments : self::$attachments;
167
+ if( !empty($attachments) ){
168
+ foreach($attachments as $attachment){
169
+ $att = array('name'=> '', 'encoding' => 'base64', 'type' => 'application/octet-stream');
170
+ if( is_array($attachment) ){
171
+ $att = array_merge($att, $attachment);
172
+ }else{
173
+ $att['path'] = $attachment;
174
+ }
175
+ try{
176
+ $phpmailer->addAttachment($att['path'], $att['name'], $att['encoding'], $att['type']);
177
+ }catch( phpmailerException $ex ){
178
+ //do nothing
179
+ }
180
+ }
181
+ }
182
+ self::$attachments = array();
183
+ }
184
+
185
+ public static function delete_email_attachments( $attachments ){
186
+ foreach( $attachments as $attachment ){
187
+ if( !empty($attachment['delete']) ){
188
+ @unlink( $attachment['path']);
189
+ }
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Returns the path of the attachments folder, creating it if non-existent. Returns false if folder could not be created.
195
+ * A .htaccess file is also attempted to be created, although this will still return as true even if it cannot be created.
196
+ * @return bool|string
197
+ */
198
+ public static function get_attachments_dir(){
199
+ //get and possibly create attachment directory path
200
+ $upload_dir = wp_upload_dir();
201
+ $attachments_dir = trailingslashit($upload_dir['basedir'])."em-email-attachments/";
202
+ if( !is_dir($attachments_dir) ){
203
+ //try to make a directory and create an .htaccess file
204
+ if( @mkdir($attachments_dir, 0755) ){
205
+ return $attachments_dir;
206
+ }
207
+ //could not create directory
208
+ return false;
209
+ }
210
+ //add .htaccess file to prevent access to folder by guessing filenames
211
+ if( !file_exists($attachments_dir.'.htaccess') ){
212
+ $file = @fopen($attachments_dir.'.htaccess','w');
213
+ if( $file ){
214
+ fwrite($file, 'deny from all');
215
+ fclose($file);
216
+ }
217
+ }
218
+ return $attachments_dir;
219
+ }
220
+
221
+ /**
222
+ * Adds file to email attachments folder, which defaults to wp-content/uploads/em-email-attachments/ and returns the location of said file, false if file could not be created.
223
+ * @param $file_name
224
+ * @param $file_content
225
+ * @return bool|string
226
+ */
227
+ public static function add_email_attachment( $file_name, $file_content ){
228
+ $attachment_dir = self::get_attachments_dir();
229
+ if( $attachment_dir ){
230
+ $file = fopen($attachment_dir.$file_name,'w+');
231
+ if( $file ){
232
+ fwrite($file, $file_content);
233
+ fclose($file);
234
+ return $attachment_dir . $file_name;
235
+ }
236
+ }
237
+ return false;
238
+ }
239
  }
240
  ?>
classes/em-notices.php CHANGED
@@ -4,7 +4,7 @@
4
  * @author marcus
5
  *
6
  */
7
- class EM_Notices implements Iterator {
8
  /**
9
  * If object has been displayed, this gets set to true, can be checked to avoid duplicates.
10
  * @var boolean
@@ -222,7 +222,18 @@
222
  }
223
  function count_confirms(){
224
  return $this->count('confirms');
225
- }
 
 
 
 
 
 
 
 
 
 
 
226
 
227
  //Iterator Implementation
228
  function rewind(){
@@ -244,8 +255,8 @@
244
  $key = key($this->bookings);
245
  $var = ($key !== NULL && $key !== FALSE);
246
  return $var;
247
- }
248
-
249
  }
250
  function em_notices_init(){
251
  global $EM_Notices;
4
  * @author marcus
5
  *
6
  */
7
+ class EM_Notices implements Iterator, JsonSerializable {
8
  /**
9
  * If object has been displayed, this gets set to true, can be checked to avoid duplicates.
10
  * @var boolean
222
  }
223
  function count_confirms(){
224
  return $this->count('confirms');
225
+ }
226
+
227
+ // Encoiding in JsonSerializable
228
+ function jsonSerialize(){
229
+ $notices = array();
230
+ foreach( $notices as $k => $v ){
231
+ if( !empty($v) ){
232
+ $notices[$k] = $v;
233
+ }
234
+ }
235
+ return $notices;
236
+ }
237
 
238
  //Iterator Implementation
239
  function rewind(){
255
  $key = key($this->bookings);
256
  $var = ($key !== NULL && $key !== FALSE);
257
  return $var;
258
+ }
259
+
260
  }
261
  function em_notices_init(){
262
  global $EM_Notices;
classes/em-object.php CHANGED
@@ -1362,15 +1362,16 @@ class EM_Object {
1362
  * @param string $subject
1363
  * @param string $body
1364
  * @param string $email
 
1365
  * @return string
1366
  */
1367
- function email_send($subject, $body, $email){
1368
  global $EM_Mailer;
1369
  if( !empty($subject) ){
1370
  if( !is_object($EM_Mailer) ){
1371
  $EM_Mailer = new EM_Mailer();
1372
  }
1373
- if( !$EM_Mailer->send($subject,$body,$email) ){
1374
  if( is_array($EM_Mailer->errors) ){
1375
  foreach($EM_Mailer->errors as $error){
1376
  $this->errors[] = $error;
1362
  * @param string $subject
1363
  * @param string $body
1364
  * @param string $email
1365
+ * @param array $attachments
1366
  * @return string
1367
  */
1368
+ function email_send($subject, $body, $email, $attachments = array()){
1369
  global $EM_Mailer;
1370
  if( !empty($subject) ){
1371
  if( !is_object($EM_Mailer) ){
1372
  $EM_Mailer = new EM_Mailer();
1373
  }
1374
+ if( !$EM_Mailer->send($subject,$body,$email, $attachments) ){
1375
  if( is_array($EM_Mailer->errors) ){
1376
  foreach($EM_Mailer->errors as $error){
1377
  $this->errors[] = $error;
classes/em-ticket-booking.php CHANGED
@@ -215,8 +215,18 @@ class EM_Ticket_Booking extends EM_Object{
215
  */
216
  function delete(){
217
  global $wpdb;
218
- $sql = $wpdb->prepare("DELETE FROM ". EM_TICKETS_BOOKINGS_TABLE . " WHERE ticket_booking_id=%d", $this->ticket_booking_id);
219
- $result = $wpdb->query( $sql );
 
 
 
 
 
 
 
 
 
 
220
  return apply_filters('em_ticket_booking_delete', ($result !== false ), $this);
221
  }
222
 
215
  */
216
  function delete(){
217
  global $wpdb;
218
+ if( $this->ticket_booking_id ){
219
+ $sql = $wpdb->prepare("DELETE FROM ". EM_TICKETS_BOOKINGS_TABLE . " WHERE ticket_booking_id=%d LIMIT 1", $this->ticket_booking_id);
220
+ }elseif( !empty($this->ticket_id) && !empty($this->booking_id) ){
221
+ //in the event a ticket_booking_id isn't available we can delete via the booking and ticket id
222
+ $sql = $wpdb->prepare("DELETE FROM ". EM_TICKETS_BOOKINGS_TABLE . " WHERE ticket_id=%d AND booking_id=%d LIMIT 1", $this->ticket_id, $this->booking_id);
223
+ }else{
224
+ //cannot delete ticket
225
+ $result = false;
226
+ }
227
+ if( !empty($sql) ){
228
+ $result = $wpdb->query( $sql );
229
+ }
230
  return apply_filters('em_ticket_booking_delete', ($result !== false ), $this);
231
  }
232
 
classes/em-ticket.php CHANGED
@@ -17,6 +17,7 @@ class EM_Ticket extends EM_Object{
17
  var $ticket_required = false;
18
  var $ticket_parent;
19
  var $ticket_meta = array();
 
20
  var $fields = array(
21
  'ticket_id' => array('name'=>'id','type'=>'%d'),
22
  'event_id' => array('name'=>'event_id','type'=>'%d'),
@@ -33,7 +34,8 @@ class EM_Ticket extends EM_Object{
33
  'ticket_guests' => array('name'=>'guests','type'=>'%d','null'=>1),
34
  'ticket_required' => array('name'=>'required','type'=>'%d','null'=>1),
35
  'ticket_parent' => array('type'=>'%d','null'=>1),
36
- 'ticket_meta' => array('name'=>'ticket_meta','type'=>'%s','null'=>1)
 
37
  );
38
  //Other Vars
39
  /**
17
  var $ticket_required = false;
18
  var $ticket_parent;
19
  var $ticket_meta = array();
20
+ var $ticket_order;
21
  var $fields = array(
22
  'ticket_id' => array('name'=>'id','type'=>'%d'),
23
  'event_id' => array('name'=>'event_id','type'=>'%d'),
34
  'ticket_guests' => array('name'=>'guests','type'=>'%d','null'=>1),
35
  'ticket_required' => array('name'=>'required','type'=>'%d','null'=>1),
36
  'ticket_parent' => array('type'=>'%d','null'=>1),
37
+ 'ticket_meta' => array('name'=>'ticket_meta','type'=>'%s','null'=>1),
38
+ 'ticket_order' => array('type'=>'%d','null'=>1),
39
  );
40
  //Other Vars
41
  /**
classes/em-tickets-bookings.php CHANGED
@@ -8,9 +8,14 @@ class EM_Tickets_Bookings extends EM_Object implements Iterator, Countable {
8
 
9
  /**
10
  * Array of EM_Ticket_Booking objects for a specific event
11
- * @var array
12
  */
13
  var $tickets_bookings = array();
 
 
 
 
 
14
  /**
15
  * This object belongs to this booking object
16
  * @var EM_Booking
@@ -57,14 +62,22 @@ class EM_Tickets_Bookings extends EM_Object implements Iterator, Countable {
57
  * @return boolean
58
  */
59
  function save(){
60
- global $wpdb;
61
  do_action('em_tickets_bookings_save_pre',$this);
62
- foreach( $this->tickets_bookings as $EM_Ticket_Booking ){ /* @var $EM_Ticket_Booking EM_Ticket_Booking */
 
63
  $result = $EM_Ticket_Booking->save();
64
  if(!$result){
65
  $this->errors = array_merge($this->errors, $EM_Ticket_Booking->get_errors());
66
  }
67
  }
 
 
 
 
 
 
 
 
68
  if( count($this->errors) > 0 ){
69
  $this->feedback_message = __('There was a problem saving the booking.', 'events-manager');
70
  $this->errors[] = __('There was a problem saving the booking.', 'events-manager');
@@ -85,9 +98,15 @@ class EM_Tickets_Bookings extends EM_Object implements Iterator, Countable {
85
  $ticket_booking_key = $this->has_ticket($EM_Ticket_Booking->ticket_id);
86
  $this->price = 0; //so price calculations are reset
87
  if( $ticket_booking_key !== false && is_object($this->tickets_bookings[$EM_Ticket_Booking->ticket_id]) ){
88
- //previously booked ticket, so let's just reset spaces/prices and replace it
89
- $this->tickets_bookings[$EM_Ticket_Booking->ticket_id]->ticket_booking_spaces = $EM_Ticket_Booking->get_spaces();
90
- $this->tickets_bookings[$EM_Ticket_Booking->ticket_id]->ticket_booking_price = $EM_Ticket_Booking->get_price();
 
 
 
 
 
 
91
  return apply_filters('em_tickets_bookings_add', true, $this, $EM_Ticket_Booking);
92
  }elseif( $EM_Ticket_Booking->get_spaces() > 0 ){
93
  //new ticket in booking
8
 
9
  /**
10
  * Array of EM_Ticket_Booking objects for a specific event
11
+ * @var array[EM_Ticket_Booking]
12
  */
13
  var $tickets_bookings = array();
14
+ /**
15
+ * When adding existing booked tickets via add() with 0 spaces, they get slotted here for deletion during save() so they circumvent validation.
16
+ * @var array[EM_Ticket_Booking]
17
+ */
18
+ var $tickets_bookings_deleted = array();
19
  /**
20
  * This object belongs to this booking object
21
  * @var EM_Booking
62
  * @return boolean
63
  */
64
  function save(){
 
65
  do_action('em_tickets_bookings_save_pre',$this);
66
+ //save/update tickets
67
+ foreach( $this->tickets_bookings as $EM_Ticket_Booking ){
68
  $result = $EM_Ticket_Booking->save();
69
  if(!$result){
70
  $this->errors = array_merge($this->errors, $EM_Ticket_Booking->get_errors());
71
  }
72
  }
73
+ //delete old tickets if set to 0 in an update
74
+ foreach($this->tickets_bookings_deleted as $EM_Ticket_Booking ){
75
+ $result = $EM_Ticket_Booking->delete();
76
+ if(!$result){
77
+ $this->errors = array_merge($this->errors, $EM_Ticket_Booking->get_errors());
78
+ }
79
+ }
80
+ //return result
81
  if( count($this->errors) > 0 ){
82
  $this->feedback_message = __('There was a problem saving the booking.', 'events-manager');
83
  $this->errors[] = __('There was a problem saving the booking.', 'events-manager');
98
  $ticket_booking_key = $this->has_ticket($EM_Ticket_Booking->ticket_id);
99
  $this->price = 0; //so price calculations are reset
100
  if( $ticket_booking_key !== false && is_object($this->tickets_bookings[$EM_Ticket_Booking->ticket_id]) ){
101
+ if( $EM_Ticket_Booking->get_spaces() > 0 ){
102
+ //previously booked ticket, so let's just reset spaces/prices and replace it
103
+ $this->tickets_bookings[$EM_Ticket_Booking->ticket_id]->ticket_booking_spaces = $EM_Ticket_Booking->get_spaces();
104
+ $this->tickets_bookings[$EM_Ticket_Booking->ticket_id]->ticket_booking_price = $EM_Ticket_Booking->get_price();
105
+ }else{
106
+ //remove ticket from bookings and set for deletion if this is saved
107
+ unset($this->tickets_bookings[$EM_Ticket_Booking->ticket_id]);
108
+ $this->tickets_bookings_deleted[$EM_Ticket_Booking->ticket_id] = $EM_Ticket_Booking;
109
+ }
110
  return apply_filters('em_tickets_bookings_add', true, $this, $EM_Ticket_Booking);
111
  }elseif( $EM_Ticket_Booking->get_spaces() > 0 ){
112
  //new ticket in booking
classes/em-tickets.php CHANGED
@@ -30,10 +30,23 @@ class EM_Tickets extends EM_Object implements Iterator, Countable {
30
  global $wpdb;
31
  if( is_numeric($object) || (is_object($object) && in_array(get_class($object), array("EM_Event","EM_Booking"))) ){
32
  $this->event_id = (is_object($object)) ? $object->event_id:$object;
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  if( is_object($object) && get_class($object) == 'EM_Booking' ){
34
- $sql = "SELECT * FROM ". EM_TICKETS_TABLE ." WHERE ticket_id IN (SELECT ticket_id FROM ".EM_TICKETS_BOOKINGS_TABLE." WHERE booking_id='{$object->booking_id}') ORDER BY ".get_option('dbem_bookings_tickets_orderby');
35
  }else{
36
- $sql = "SELECT * FROM ". EM_TICKETS_TABLE ." WHERE event_id ='{$this->event_id}' ORDER BY ".get_option('dbem_bookings_tickets_orderby');
37
  }
38
  $tickets = $wpdb->get_results($sql, ARRAY_A);
39
  foreach ($tickets as $ticket){
@@ -147,6 +160,7 @@ class EM_Tickets extends EM_Object implements Iterator, Countable {
147
  if( !empty($_POST['em_tickets']) && is_array($_POST['em_tickets']) ){
148
  //get all ticket data and create objects
149
  global $allowedposttags;
 
150
  foreach($_POST['em_tickets'] as $row => $ticket_data){
151
  if( $row > 0 ){
152
  if( !empty($ticket_data['ticket_id']) && !empty($current_tickets[$ticket_data['ticket_id']]) ){
@@ -156,11 +170,13 @@ class EM_Tickets extends EM_Object implements Iterator, Countable {
156
  }
157
  $ticket_data['event_id'] = $this->event_id;
158
  $EM_Ticket->get_post($ticket_data);
 
159
  if( $EM_Ticket->ticket_id ){
160
  $this->tickets[$EM_Ticket->ticket_id] = $EM_Ticket;
161
  }else{
162
  $this->tickets[] = $EM_Ticket;
163
  }
 
164
  }
165
  }
166
  }else{
30
  global $wpdb;
31
  if( is_numeric($object) || (is_object($object) && in_array(get_class($object), array("EM_Event","EM_Booking"))) ){
32
  $this->event_id = (is_object($object)) ? $object->event_id:$object;
33
+ $orderby_option = get_option('dbem_bookings_tickets_orderby');
34
+ $order_by = get_option('dbem_bookings_tickets_ordering') ? array('ticket_order ASC') : array();
35
+ $ticket_orderby_options = apply_filters('em_tickets_orderby_options', array(
36
+ 'ticket_price DESC, ticket_name ASC'=>__('Ticket Price (Descending)','events-manager'),
37
+ 'ticket_price ASC, ticket_name ASC'=>__('Ticket Price (Ascending)','events-manager'),
38
+ 'ticket_name ASC, ticket_price DESC'=>__('Ticket Name (Ascending)','events-manager'),
39
+ 'ticket_name DESC, ticket_price DESC'=>__('Ticket Name (Descending)','events-manager')
40
+ ));
41
+ if( array_key_exists($orderby_option, $ticket_orderby_options) ){
42
+ $order_by[] = $orderby_option;
43
+ }else{
44
+ $order_by[] = 'ticket_price DESC, ticket_name ASC';
45
+ }
46
  if( is_object($object) && get_class($object) == 'EM_Booking' ){
47
+ $sql = "SELECT * FROM ". EM_TICKETS_TABLE ." WHERE ticket_id IN (SELECT ticket_id FROM ".EM_TICKETS_BOOKINGS_TABLE." WHERE booking_id='{$object->booking_id}') ORDER BY ".implode(',', $order_by);
48
  }else{
49
+ $sql = "SELECT * FROM ". EM_TICKETS_TABLE ." WHERE event_id ='{$this->event_id}' ORDER BY ".implode(',', $order_by);
50
  }
51
  $tickets = $wpdb->get_results($sql, ARRAY_A);
52
  foreach ($tickets as $ticket){
160
  if( !empty($_POST['em_tickets']) && is_array($_POST['em_tickets']) ){
161
  //get all ticket data and create objects
162
  global $allowedposttags;
163
+ $order = 1;
164
  foreach($_POST['em_tickets'] as $row => $ticket_data){
165
  if( $row > 0 ){
166
  if( !empty($ticket_data['ticket_id']) && !empty($current_tickets[$ticket_data['ticket_id']]) ){
170
  }
171
  $ticket_data['event_id'] = $this->event_id;
172
  $EM_Ticket->get_post($ticket_data);
173
+ $EM_Ticket->ticket_order = $order;
174
  if( $EM_Ticket->ticket_id ){
175
  $this->tickets[$EM_Ticket->ticket_id] = $EM_Ticket;
176
  }else{
177
  $this->tickets[] = $EM_Ticket;
178
  }
179
+ $order++;
180
  }
181
  }
182
  }else{
em-install.php CHANGED
@@ -333,6 +333,7 @@ function em_create_tickets_table() {
333
  ticket_guests INT( 1 ) NULL ,
334
  ticket_required INT( 1 ) NULL ,
335
  ticket_parent BIGINT( 20 ) UNSIGNED NULL,
 
336
  ticket_meta LONGTEXT NULL,
337
  PRIMARY KEY (ticket_id)
338
  ) DEFAULT CHARSET=utf8 ;";
@@ -759,6 +760,7 @@ function em_add_options() {
759
  'dbem_bookings_email_registration_subject' => $booking_registration_email_subject,
760
  'dbem_bookings_email_registration_body' => str_replace("<br/>", "\n\r", $booking_registration_email_body),
761
  //Ticket Specific Options
 
762
  'dbem_bookings_tickets_orderby' => 'ticket_price DESC, ticket_name ASC',
763
  'dbem_bookings_tickets_priority' => 0,
764
  'dbem_bookings_tickets_show_unavailable' => 0,
333
  ticket_guests INT( 1 ) NULL ,
334
  ticket_required INT( 1 ) NULL ,
335
  ticket_parent BIGINT( 20 ) UNSIGNED NULL,
336
+ ticket_order INT( 2 ) UNSIGNED NULL,
337
  ticket_meta LONGTEXT NULL,
338
  PRIMARY KEY (ticket_id)
339
  ) DEFAULT CHARSET=utf8 ;";
760
  'dbem_bookings_email_registration_subject' => $booking_registration_email_subject,
761
  'dbem_bookings_email_registration_body' => str_replace("<br/>", "\n\r", $booking_registration_email_body),
762
  //Ticket Specific Options
763
+ 'dbem_bookings_tickets_ordering' => 1,
764
  'dbem_bookings_tickets_orderby' => 'ticket_price DESC, ticket_name ASC',
765
  'dbem_bookings_tickets_priority' => 0,
766
  'dbem_bookings_tickets_show_unavailable' => 0,
em-template-tags.php CHANGED
@@ -201,7 +201,9 @@ function em_event_form($args = array()){
201
  em_locate_template('forms/event-editor.php',true, array('args'=>$args));
202
  }
203
  if( get_option('dbem_css_editors') ) echo '</div>';
 
204
  }
 
205
  /**
206
  * Retreives the event submission form for guests and members.
207
  * @param array $args
201
  em_locate_template('forms/event-editor.php',true, array('args'=>$args));
202
  }
203
  if( get_option('dbem_css_editors') ) echo '</div>';
204
+ wp_enqueue_style('dashicons');
205
  }
206
+
207
  /**
208
  * Retreives the event submission form for guests and members.
209
  * @param array $args
events-manager.php CHANGED
@@ -1,7 +1,7 @@
1
  <?php
2
  /*
3
  Plugin Name: Events Manager
4
- Version: 5.9.7.1
5
  Plugin URI: http://wp-events-plugin.com
6
  Description: Event registration and booking management for WordPress. Recurring events, locations, google maps, rss, ical, booking registration and more!
7
  Author: Marcus Sykes
@@ -10,7 +10,7 @@ Text Domain: events-manager
10
  */
11
 
12
  /*
13
- Copyright (c) 2019, Marcus Sykes
14
 
15
  This program is free software; you can redistribute it and/or
16
  modify it under the terms of the GNU General Public License
@@ -28,8 +28,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
28
  */
29
 
30
  // Setting constants
31
- define('EM_VERSION', 5.97); //self expanatory
32
- define('EM_PRO_MIN_VERSION', 2.64); //self expanatory
33
  define('EM_PRO_MIN_VERSION_CRITICAL', 2.377); //self expanatory
34
  define('EM_DIR', dirname( __FILE__ )); //an absolute path to this directory
35
  define('EM_DIR_URI', trailingslashit(plugins_url('',__FILE__))); //an absolute path to this directory
1
  <?php
2
  /*
3
  Plugin Name: Events Manager
4
+ Version: 5.9.7.2
5
  Plugin URI: http://wp-events-plugin.com
6
  Description: Event registration and booking management for WordPress. Recurring events, locations, google maps, rss, ical, booking registration and more!
7
  Author: Marcus Sykes
10
  */
11
 
12
  /*
13
+ Copyright (c) 2020, Marcus Sykes
14
 
15
  This program is free software; you can redistribute it and/or
16
  modify it under the terms of the GNU General Public License
28
  */
29
 
30
  // Setting constants
31
+ define('EM_VERSION', 5.972); //self expanatory
32
+ define('EM_PRO_MIN_VERSION', 2.6712); //self expanatory
33
  define('EM_PRO_MIN_VERSION_CRITICAL', 2.377); //self expanatory
34
  define('EM_DIR', dirname( __FILE__ )); //an absolute path to this directory
35
  define('EM_DIR_URI', trailingslashit(plugins_url('',__FILE__))); //an absolute path to this directory
includes/css/events_manager.css CHANGED
@@ -135,11 +135,17 @@ div#em-loading { position:absolute; width:100%; height:100%; background:#FFFFFF
135
  #event-rsvp-box { margin:10px; }
136
  #event-rsvp-options label { font-weight:bold; }
137
  /*Tickets*/
138
- .em-tickets-row .ticket-status span.ticket_on { display:block; width:10px; height:10px; background:green; }
139
- .em-tickets-row .ticket-status span.ticket_off { display:block; width:10px; height:10px; background:red; }
140
- .em-tickets-row .ticket-status span.ticket_new { display:block; width:10px; height:10px; background:grey; }
141
  #em-tickets-form th { width:auto; }
142
- #em-tickets-form th.ticket-status { width:20px; }
 
 
 
 
 
 
 
 
143
  /* Ticket Forms */
144
  .em-ticket-form .ticket-options { margin-top:10px; }
145
  .em-ticket-form > div > div, #em-tickets-form .ticket-dates > div { clear:both; padding-top:4px; }
135
  #event-rsvp-box { margin:10px; }
136
  #event-rsvp-options label { font-weight:bold; }
137
  /*Tickets*/
138
+ #em-tickets-form tbody.em-ticket-template { display:none; }
 
 
139
  #em-tickets-form th { width:auto; }
140
+ #em-tickets-form th.ticket-status, .em-tickets-row .ticket-status { width:20px; }
141
+ #em-tickets-form .em-tickets-row .ticket-status span.dashicons { display:block; width:16px; height:16px; line-height:16px; font-size:16px; font-weight: bolder; }
142
+ #em-tickets-form .em-tickets-row .ticket-status.single span.dashicons { cursor:auto; }
143
+ #em-tickets-form .em-tickets-row .ticket-status span.ticket-on { color: #008000; }
144
+ #em-tickets-form .em-tickets-row .ticket-status span.ticket-off { color: #ff0000; }
145
+ #em-tickets-form .em-tickets-row .ticket-status span.ticket_new { color: #808080; }
146
+ #em-tickets-form.em-tickets-sortable .em-tickets-row .ticket-status span.dashicons { cursor:move; }
147
+ #em-tickets-form .em-ticket-sortable-placeholder { border:2px dashed #dedede; background:#efefef; }
148
+ #em-tickets-form .ui-sortable-helper { cursor:move; }
149
  /* Ticket Forms */
150
  .em-ticket-form .ticket-options { margin-top:10px; }
151
  .em-ticket-form > div > div, #em-tickets-form .ticket-dates > div { clear:both; padding-top:4px; }
includes/css/events_manager_admin.css CHANGED
@@ -45,11 +45,17 @@ div.em-location-data .em-location-map-404 { vertical-align:middle; text-align: c
45
  #event-rsvp-options h4 { font-size:14px; }
46
  #event-rsvp-options label { font-weight:bold; }
47
  /*Tickets*/
48
- .em-tickets-row .ticket-status span.ticket_on { display:block; width:10px; height:10px; background:green; }
49
- .em-tickets-row .ticket-status span.ticket_off { display:block; width:10px; height:10px; background:red; }
50
- .em-tickets-row .ticket-status span.ticket_new { display:block; width:10px; height:10px; background:grey; }
51
  #em-tickets-form th { width:auto; }
52
- #em-tickets-form th.ticket-status { width:20px; }
 
 
 
 
 
 
 
 
53
  /* Ticket Forms */
54
  .em-ticket-form .ticket-options { margin-top:10px; }
55
  .em-ticket-form > div > div, #em-tickets-form .ticket-dates > div { clear:both; padding-top:4px; }
45
  #event-rsvp-options h4 { font-size:14px; }
46
  #event-rsvp-options label { font-weight:bold; }
47
  /*Tickets*/
48
+ #em-tickets-form tbody.em-ticket-template { display:none; }
 
 
49
  #em-tickets-form th { width:auto; }
50
+ #em-tickets-form th.ticket-status, .em-tickets-row .ticket-status { width:20px; }
51
+ #em-tickets-form .em-tickets-row .ticket-status span.dashicons { display:block; width:16px; height:16px; line-height:16px; font-size:16px; font-weight: bolder; }
52
+ #em-tickets-form .em-tickets-row .ticket-status.single span.dashicons { cursor:auto; }
53
+ #em-tickets-form .em-tickets-row .ticket-status span.ticket-on { color: #008000; }
54
+ #em-tickets-form .em-tickets-row .ticket-status span.ticket-off { color: #ff0000; }
55
+ #em-tickets-form .em-tickets-row .ticket-status span.ticket_new { color: #808080; }
56
+ #em-tickets-form.em-tickets-sortable .em-tickets-row .ticket-status span.dashicons { cursor:move; }
57
+ #em-tickets-form .em-ticket-sortable-placeholder { border:2px dashed #dedede; background:#efefef; }
58
+ #em-tickets-form .ui-sortable-helper { cursor:move; }
59
  /* Ticket Forms */
60
  .em-ticket-form .ticket-options { margin-top:10px; }
61
  .em-ticket-form > div > div, #em-tickets-form .ticket-dates > div { clear:both; padding-top:4px; }
includes/js/events-manager.js CHANGED
@@ -8,9 +8,8 @@ jQuery(document).ready( function($){
8
  em_setup_timepicker('body');
9
  }
10
  /* Calendar AJAX */
11
- $('.em-calendar-wrapper a').unbind("click");
12
- $('.em-calendar-wrapper a').undelegate("click");
13
- $('.em-calendar-wrapper').delegate('a.em-calnav, a.em-calnav', 'click', function(e){
14
  e.preventDefault();
15
  $(this).closest('.em-calendar-wrapper').prepend('<div class="loading" id="em-loading"></div>');
16
  var url = em_ajaxify($(this).attr('href'));
@@ -18,7 +17,7 @@ jQuery(document).ready( function($){
18
  } );
19
 
20
  //Events Search
21
- $(document).delegate('.em-toggle', 'click change', function(e){
22
  e.preventDefault();
23
  //show or hide advanced tickets, hidden by default
24
  var el = $(this);
@@ -98,7 +97,7 @@ jQuery(document).ready( function($){
98
  });
99
 
100
  //in order for this to work, you need the above classes to be present in your templates
101
- $(document).delegate('.em-search-form, .em-events-search-form', 'submit', function(e){
102
  var form = $(this);
103
  if( this.em_search && this.em_search.value == EM.txt_search){ this.em_search.value = ''; }
104
  var results_wrapper = form.closest('.em-search-wrapper').find('.em-search-ajax');
@@ -130,7 +129,7 @@ jQuery(document).ready( function($){
130
  }
131
  });
132
  if( $('.em-search-ajax').length > 0 ){
133
- $(document).delegate('.em-search-ajax a.page-numbers', 'click', function(e){
134
  var a = $(this);
135
  var data = a.closest('.em-pagination').attr('data-em-ajax');
136
  var wrapper = a.closest('.em-search-ajax');
@@ -255,7 +254,7 @@ jQuery(document).ready( function($){
255
  //create copy of template slot, insert so ready for population
256
  var tickets = $('#em-tickets-form table tbody');
257
  var rowNo = tickets.length+1;
258
- var slot = tickets.first().clone(true).attr('id','em-ticket-'+ rowNo).appendTo($('#em-tickets-form table'));
259
  //change the index of the form element names
260
  slot.find('*[name]').each( function(index,el){
261
  el = $(el);
@@ -269,9 +268,10 @@ jQuery(document).ready( function($){
269
  em_setup_datepicker(slot);
270
  em_setup_timepicker(slot);
271
  $('html, body').animate({ scrollTop: slot.offset().top - 30 }); //sends user to form
 
272
  });
273
  //Edit a Ticket
274
- $(document).delegate('.ticket-actions-edit', 'click', function(e){
275
  e.preventDefault();
276
  reset_ticket_forms();
277
  var tbody = $(this).closest('tbody');
@@ -279,7 +279,7 @@ jQuery(document).ready( function($){
279
  tbody.find('tr.em-tickets-row-form').fadeIn();
280
  return false;
281
  });
282
- $(document).delegate('.ticket-actions-edited', 'click', function(e){
283
  e.preventDefault();
284
  var tbody = $(this).closest('tbody');
285
  var rowNo = tbody.attr('id').replace('em-ticket-','');
@@ -322,7 +322,7 @@ jQuery(document).ready( function($){
322
  $('html, body').animate({ scrollTop: tbody.parent().offset().top - 30 }); //sends user back to top of form
323
  return false;
324
  });
325
- $(document).delegate('.em-ticket-form select.ticket_type','change', function(e){
326
  //check if ticket is for all users or members, if members, show roles to limit the ticket to
327
  var el = $(this);
328
  if( el.find('option:selected').val() == 'members' ){
@@ -331,7 +331,7 @@ jQuery(document).ready( function($){
331
  el.closest('.em-ticket-form').find('.ticket-roles').hide();
332
  }
333
  });
334
- $(document).delegate('.em-ticket-form .ticket-options-advanced','click', function(e){
335
  //show or hide advanced tickets, hidden by default
336
  e.preventDefault();
337
  var el = $(this);
@@ -356,7 +356,7 @@ jQuery(document).ready( function($){
356
  if( show_advanced ) el.find('.ticket-options-advanced').trigger('click');
357
  });
358
  //Delete a ticket
359
- $(document).delegate('.ticket-actions-delete', 'click', function(e){
360
  e.preventDefault();
361
  var el = $(this);
362
  var tbody = el.closest('tbody');
@@ -375,13 +375,38 @@ jQuery(document).ready( function($){
375
  //not saved to db yet, so just remove
376
  tbody.remove();
377
  }
 
378
  return false;
379
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
380
  }
381
  //Manageing Bookings
382
  if( $('#em-bookings-table').length > 0 ){
383
  //Pagination link clicks
384
- $(document).delegate('#em-bookings-table .tablenav-pages a', 'click', function(){
385
  var el = $(this);
386
  var form = el.parents('#em-bookings-table form.bookings-filter');
387
  //get page no from url, change page, submit form
@@ -441,10 +466,10 @@ jQuery(document).ready( function($){
441
  if( $("#em-bookings-table-settings").length > 0 ){
442
  //Settings Overlay
443
  $("#em-bookings-table-settings").dialog(em_bookings_settings_dialog);
444
- $(document).delegate('#em-bookings-table-settings-trigger','click', function(e){ e.preventDefault(); $("#em-bookings-table-settings").dialog('open'); });
445
  //Export Overlay
446
  $("#em-bookings-table-export").dialog(em_bookings_export_dialog);
447
- $(document).delegate('#em-bookings-table-export-trigger','click', function(e){ e.preventDefault(); $("#em-bookings-table-export").dialog('open'); });
448
  var export_overlay_show_tickets = function(){
449
  if( $('#em-bookings-table-export-form input[name=show_tickets]').is(':checked') ){
450
  $('#em-bookings-table-export-form .em-bookings-col-item-ticket').show();
@@ -479,7 +504,7 @@ jQuery(document).ready( function($){
479
  load_ui_css = true;
480
  }
481
  //Widgets and filter submissions
482
- $(document).delegate('#em-bookings-table form.bookings-filter', 'submit', function(e){
483
  var el = $(this);
484
  //append loading spinner
485
  el.parents('#em-bookings-table').find('.table-wrap').first().append('<div id="em-loading" />');
@@ -495,7 +520,7 @@ jQuery(document).ready( function($){
495
  return false;
496
  });
497
  //Approve/Reject Links
498
- $(document).delegate('.em-bookings-approve,.em-bookings-reject,.em-bookings-unapprove,.em-bookings-delete', 'click', function(){
499
  var el = $(this);
500
  if( el.hasClass('em-bookings-delete') ){
501
  if( !confirm(EM.booking_delete) ){ return false; }
@@ -510,7 +535,7 @@ jQuery(document).ready( function($){
510
  //Old Bookings Table - depreciating soon
511
  if( $('.em_bookings_events_table').length > 0 ){
512
  //Widgets and filter submissions
513
- $(document).delegate('.em_bookings_events_table form', 'submit', function(e){
514
  var el = $(this);
515
  var url = em_ajaxify( el.attr('action') );
516
  el.parents('.em_bookings_events_table').find('.table-wrap').first().append('<div id="em-loading" />');
@@ -520,7 +545,7 @@ jQuery(document).ready( function($){
520
  return false;
521
  });
522
  //Pagination link clicks
523
- $(document).delegate('.em_bookings_events_table .tablenav-pages a', 'click', function(){
524
  var el = $(this);
525
  var url = em_ajaxify( el.attr('href') );
526
  el.parents('.em_bookings_events_table').find('.table-wrap').first().append('<div id="em-loading" />');
8
  em_setup_timepicker('body');
9
  }
10
  /* Calendar AJAX */
11
+ $('.em-calendar-wrapper a').off("click");
12
+ $('.em-calendar-wrapper').on('click', 'a.em-calnav, a.em-calnav', function(e){
 
13
  e.preventDefault();
14
  $(this).closest('.em-calendar-wrapper').prepend('<div class="loading" id="em-loading"></div>');
15
  var url = em_ajaxify($(this).attr('href'));
17
  } );
18
 
19
  //Events Search
20
+ $(document).on('click change', '.em-toggle', function(e){
21
  e.preventDefault();
22
  //show or hide advanced tickets, hidden by default
23
  var el = $(this);
97
  });
98
 
99
  //in order for this to work, you need the above classes to be present in your templates
100
+ $(document).on('submit', '.em-search-form, .em-events-search-form', function(e){
101
  var form = $(this);
102
  if( this.em_search && this.em_search.value == EM.txt_search){ this.em_search.value = ''; }
103
  var results_wrapper = form.closest('.em-search-wrapper').find('.em-search-ajax');
129
  }
130
  });
131
  if( $('.em-search-ajax').length > 0 ){
132
+ $(document).on('click', '.em-search-ajax a.page-numbers', function(e){
133
  var a = $(this);
134
  var data = a.closest('.em-pagination').attr('data-em-ajax');
135
  var wrapper = a.closest('.em-search-ajax');
254
  //create copy of template slot, insert so ready for population
255
  var tickets = $('#em-tickets-form table tbody');
256
  var rowNo = tickets.length+1;
257
+ var slot = tickets.first('.em-ticket-template').clone(true).attr('id','em-ticket-'+ rowNo).removeClass('em-ticket-template').addClass('em-ticket').appendTo($('#em-tickets-form table'));
258
  //change the index of the form element names
259
  slot.find('*[name]').each( function(index,el){
260
  el = $(el);
268
  em_setup_datepicker(slot);
269
  em_setup_timepicker(slot);
270
  $('html, body').animate({ scrollTop: slot.offset().top - 30 }); //sends user to form
271
+ check_ticket_sortability();
272
  });
273
  //Edit a Ticket
274
+ $(document).on('click', '.ticket-actions-edit', function(e){
275
  e.preventDefault();
276
  reset_ticket_forms();
277
  var tbody = $(this).closest('tbody');
279
  tbody.find('tr.em-tickets-row-form').fadeIn();
280
  return false;
281
  });
282
+ $(document).on('click', '.ticket-actions-edited', function(e){
283
  e.preventDefault();
284
  var tbody = $(this).closest('tbody');
285
  var rowNo = tbody.attr('id').replace('em-ticket-','');
322
  $('html, body').animate({ scrollTop: tbody.parent().offset().top - 30 }); //sends user back to top of form
323
  return false;
324
  });
325
+ $(document).on('change', '.em-ticket-form select.ticket_type', function(e){
326
  //check if ticket is for all users or members, if members, show roles to limit the ticket to
327
  var el = $(this);
328
  if( el.find('option:selected').val() == 'members' ){
331
  el.closest('.em-ticket-form').find('.ticket-roles').hide();
332
  }
333
  });
334
+ $(document).on('click', '.em-ticket-form .ticket-options-advanced', function(e){
335
  //show or hide advanced tickets, hidden by default
336
  e.preventDefault();
337
  var el = $(this);
356
  if( show_advanced ) el.find('.ticket-options-advanced').trigger('click');
357
  });
358
  //Delete a ticket
359
+ $(document).on('click', '.ticket-actions-delete', function(e){
360
  e.preventDefault();
361
  var el = $(this);
362
  var tbody = el.closest('tbody');
375
  //not saved to db yet, so just remove
376
  tbody.remove();
377
  }
378
+ check_ticket_sortability();
379
  return false;
380
  });
381
+ //Sort Tickets
382
+ $('#em-tickets-form.em-tickets-sortable table').sortable({
383
+ items: '> tbody',
384
+ placeholder: "em-ticket-sortable-placeholder",
385
+ handle:'.ticket-status',
386
+ helper: function( event, el ){
387
+ var helper = $(el).clone().addClass('em-ticket-sortable-helper');
388
+ var tds = helper.find('.em-tickets-row td').length;
389
+ helper.children().remove();
390
+ helper.append('<tr class="em-tickets-row"><td colspan="'+tds+'" style="text-align:left; padding-left:15px;"><span class="dashicons dashicons-tickets-alt"></span></td></tr>');
391
+ return helper;
392
+ },
393
+ });
394
+ var check_ticket_sortability = function(){
395
+ var em_tickets = $('#em-tickets-form table tbody.em-ticket');
396
+ if( em_tickets.length == 1 ){
397
+ em_tickets.find('.ticket-status').addClass('single');
398
+ $('#em-tickets-form table').sortable( "option", "disabled", true );
399
+ }else{
400
+ em_tickets.find('.ticket-status').removeClass('single');
401
+ $('#em-tickets-form table').sortable( "option", "disabled", false );
402
+ }
403
+ };
404
+ check_ticket_sortability();
405
  }
406
  //Manageing Bookings
407
  if( $('#em-bookings-table').length > 0 ){
408
  //Pagination link clicks
409
+ $(document).on('click', '#em-bookings-table .tablenav-pages a', function(){
410
  var el = $(this);
411
  var form = el.parents('#em-bookings-table form.bookings-filter');
412
  //get page no from url, change page, submit form
466
  if( $("#em-bookings-table-settings").length > 0 ){
467
  //Settings Overlay
468
  $("#em-bookings-table-settings").dialog(em_bookings_settings_dialog);
469
+ $(document).on('click', '#em-bookings-table-settings-trigger', function(e){ e.preventDefault(); $("#em-bookings-table-settings").dialog('open'); });
470
  //Export Overlay
471
  $("#em-bookings-table-export").dialog(em_bookings_export_dialog);
472
+ $(document).on('click', '#em-bookings-table-export-trigger', function(e){ e.preventDefault(); $("#em-bookings-table-export").dialog('open'); });
473
  var export_overlay_show_tickets = function(){
474
  if( $('#em-bookings-table-export-form input[name=show_tickets]').is(':checked') ){
475
  $('#em-bookings-table-export-form .em-bookings-col-item-ticket').show();
504
  load_ui_css = true;
505
  }
506
  //Widgets and filter submissions
507
+ $(document).on('submit', '#em-bookings-table form.bookings-filter', function(e){
508
  var el = $(this);
509
  //append loading spinner
510
  el.parents('#em-bookings-table').find('.table-wrap').first().append('<div id="em-loading" />');
520
  return false;
521
  });
522
  //Approve/Reject Links
523
+ $(document).on('click', '.em-bookings-approve,.em-bookings-reject,.em-bookings-unapprove,.em-bookings-delete', function(){
524
  var el = $(this);
525
  if( el.hasClass('em-bookings-delete') ){
526
  if( !confirm(EM.booking_delete) ){ return false; }
535
  //Old Bookings Table - depreciating soon
536
  if( $('.em_bookings_events_table').length > 0 ){
537
  //Widgets and filter submissions
538
+ $(document).on('submit', '.em_bookings_events_table form', function(e){
539
  var el = $(this);
540
  var url = em_ajaxify( el.attr('action') );
541
  el.parents('.em_bookings_events_table').find('.table-wrap').first().append('<div id="em-loading" />');
545
  return false;
546
  });
547
  //Pagination link clicks
548
+ $(document).on('click', '.em_bookings_events_table .tablenav-pages a', function(){
549
  var el = $(this);
550
  var url = em_ajaxify( el.attr('href') );
551
  el.parents('.em_bookings_events_table').find('.table-wrap').first().append('<div id="em-loading" />');
readme.txt CHANGED
@@ -5,7 +5,7 @@ Tags: bookings, calendar, tickets, events, buddypress, event management, google
5
  Text Domain: events-manager
6
  Requires at least: 4.8
7
  Tested up to: 5.3
8
- Stable tag: 5.9.7.1
9
  Requires PHP: 5.3
10
 
11
  Fully featured event registration management including recurring events, locations management, calendar, Google map integration, booking management
@@ -111,6 +111,23 @@ See our [FAQ](http://wp-events-plugin.com/documentation/faq/) page, which is upd
111
  6. Manage attendees with various booking reports
112
 
113
  == Changelog ==
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  = 5.9.7.1 =
115
  * fixed minor typo in new email setting description
116
  * fixed CSV booking export files turning out blank due to change in EM_Bookings::__isset() in 5.9.7
5
  Text Domain: events-manager
6
  Requires at least: 4.8
7
  Tested up to: 5.3
8
+ Stable tag: 5.9.7.2
9
  Requires PHP: 5.3
10
 
11
  Fully featured event registration management including recurring events, locations management, calendar, Google map integration, booking management
111
  6. Manage attendees with various booking reports
112
 
113
  == Changelog ==
114
+ = 5.9.7.2 =
115
+ * fixed CSV injection vulnerability which can allow malicious text to be exported to CSV files and parsed by Spreadsheet
116
+ * fixed #_BOOKINGCUTOFF text date formats not getting translated correctly
117
+ * added ability to programatically add attachments to booking emails for future features
118
+ * fixed/updated casing of functions in phpMailer function calls (prefiously backward compatible),
119
+ * added reply-to headers for wp_mail emails circumventing some plugins forcing from email address fields,
120
+ * fixed email testing function ignoring sender name, encryption and autotls options
121
+ * fixed ical apple structure breaking parsing in google (and possible others)
122
+ * updated events-manager.js to replace deprecated use of delegate/bind with on/off equivalents
123
+ * added ticket ordering
124
+ * fixed editing booking tickets in admin causing validation errors on 0 values
125
+ * fixed PHP Warning generated when adding Booking Notes which prevent a redirect with WP_DEBUG enabled
126
+ * fixed Events tab on profile pages stripping last character with the BuddyPress Nouveau theme
127
+ * changed blank value to 'no location' when viewing the event bookings admin page for locationless events
128
+ * changed EM_Notices by making them JsonSerializable
129
+ * fixed (hopefully!) the elusive and hard to reproduce "variable mismatch" error when submitting new form in some rare circumstances
130
+
131
  = 5.9.7.1 =
132
  * fixed minor typo in new email setting description
133
  * fixed CSV booking export files turning out blank due to change in EM_Bookings::__isset() in 5.9.7
templates/forms/event-editor.php CHANGED
@@ -23,7 +23,7 @@ if( !empty($_REQUEST['success']) ){
23
  if(!get_option('dbem_events_form_reshow')) return false;
24
  }
25
  ?>
26
- <form enctype='multipart/form-data' id="event-form" class="em-event-admin-editor <?php if( $EM_Event->is_recurring() ) echo 'em-event-admin-recurring' ?>" method="post" action="<?php echo esc_url(add_query_arg(array('success'=>null))); ?>">
27
  <div class="wrap">
28
  <?php do_action('em_front_event_form_header', $EM_Event); ?>
29
  <?php if(get_option('dbem_events_anonymous_submissions') && !is_user_logged_in()): ?>
23
  if(!get_option('dbem_events_form_reshow')) return false;
24
  }
25
  ?>
26
+ <form enctype='multipart/form-data' id="event-form" class="em-event-admin-editor <?php if( $EM_Event->is_recurring() ) echo 'em-event-admin-recurring' ?>" method="post" action="<?php echo esc_url(add_query_arg(array('success'=>null, 'action'=>null))); ?>">
27
  <div class="wrap">
28
  <?php do_action('em_front_event_form_header', $EM_Event); ?>
29
  <?php if(get_option('dbem_events_anonymous_submissions') && !is_user_logged_in()): ?>
templates/forms/event/bookings.php CHANGED
@@ -42,8 +42,11 @@ $reschedule_warnings = !empty($EM_Event->event_id) && $EM_Event->is_recurring()
42
  </div>
43
  <?php
44
  }
 
 
 
45
  ?>
46
- <div id="em-tickets-form" class="em-tickets-form<?php if( $reschedule_warnings && empty($_REQUEST['recreate_tickets']) ) echo ' reschedule-hidden' ?>">
47
  <?php
48
  //output ticket options
49
  if( get_option('dbem_bookings_tickets_single') && count($EM_Tickets->tickets) == 1 ){
@@ -62,12 +65,11 @@ $reschedule_warnings = !empty($EM_Event->event_id) && $EM_Event->is_recurring()
62
  <th><?php esc_html_e('Start/End','events-manager'); ?></th>
63
  <th><?php esc_html_e('Avail. Spaces','events-manager'); ?></th>
64
  <th><?php esc_html_e('Booked Spaces','events-manager'); ?></th>
65
- <th>&nbsp;</th>
66
  </tr>
67
  </thead>
68
  <tfoot>
69
  <tr valign="top">
70
- <td colspan="8">
71
  <a href="#" id="em-tickets-add"><?php esc_html_e('Add new ticket','events-manager'); ?></a>
72
  </td>
73
  </tr>
@@ -79,15 +81,18 @@ $reschedule_warnings = !empty($EM_Event->event_id) && $EM_Event->is_recurring()
79
  $col_count = 0;
80
  foreach( $EM_Tickets->tickets as $EM_Ticket){
81
  /* @var $EM_Ticket EM_Ticket */
 
82
  ?>
83
- <tbody id="em-ticket-<?php echo $col_count ?>" <?php if( $col_count == 0 ) echo 'style="display:none;"' ?>>
84
  <tr class="em-tickets-row">
85
- <td class="ticket-status"><span class="<?php if($EM_Ticket->ticket_id && $EM_Ticket->is_available(true, true)){ echo 'ticket_on'; }elseif($EM_Ticket->ticket_id > 0){ echo 'ticket_off'; }else{ echo 'ticket_new'; } ?>"></span></td>
 
 
86
  <td class="ticket-name">
87
  <span class="ticket_name"><?php if($EM_Ticket->ticket_members) echo '* ';?><?php echo wp_kses_data($EM_Ticket->ticket_name); ?></span>
88
  <div class="ticket_description"><?php echo wp_kses($EM_Ticket->ticket_description,$allowedposttags); ?></div>
89
  <div class="ticket-actions">
90
- <a href="#" class="ticket-actions-edit"><?php esc_html_e('Edit','events-manager'); ?></a>
91
  <?php if( $EM_Ticket->get_bookings_count() == 0 ): ?>
92
  | <a href="<?php bloginfo('wpurl'); ?>/wp-load.php" class="ticket-actions-delete"><?php esc_html_e('Delete','events-manager'); ?></a>
93
  <?php else: ?>
@@ -101,7 +106,7 @@ $reschedule_warnings = !empty($EM_Event->event_id) && $EM_Event->is_recurring()
101
  <td class="ticket-limit">
102
  <span class="ticket_min">
103
  <?php echo ( !empty($EM_Ticket->ticket_min) ) ? esc_html($EM_Ticket->ticket_min):'-'; ?>
104
- </span> /
105
  <span class="ticket_max"><?php echo ( !empty($EM_Ticket->ticket_max) ) ? esc_html($EM_Ticket->ticket_max):'-'; ?></span>
106
  </td>
107
  <td class="ticket-time">
42
  </div>
43
  <?php
44
  }
45
+ $container_classes = array();
46
+ if( $reschedule_warnings && empty($_REQUEST['recreate_tickets']) ) $container_classes[] = 'reschedule-hidden';
47
+ if( get_option('dbem_bookings_tickets_ordering') ) $container_classes[] = 'em-tickets-sortable';
48
  ?>
49
+ <div id="em-tickets-form" class="em-tickets-form <?php echo implode(' ', $container_classes); ?>">
50
  <?php
51
  //output ticket options
52
  if( get_option('dbem_bookings_tickets_single') && count($EM_Tickets->tickets) == 1 ){
65
  <th><?php esc_html_e('Start/End','events-manager'); ?></th>
66
  <th><?php esc_html_e('Avail. Spaces','events-manager'); ?></th>
67
  <th><?php esc_html_e('Booked Spaces','events-manager'); ?></th>
 
68
  </tr>
69
  </thead>
70
  <tfoot>
71
  <tr valign="top">
72
+ <td colspan="7">
73
  <a href="#" id="em-tickets-add"><?php esc_html_e('Add new ticket','events-manager'); ?></a>
74
  </td>
75
  </tr>
81
  $col_count = 0;
82
  foreach( $EM_Tickets->tickets as $EM_Ticket){
83
  /* @var $EM_Ticket EM_Ticket */
84
+ $class_name = $col_count == 0 ? 'em-ticket-template':'em-ticket';
85
  ?>
86
+ <tbody id="em-ticket-<?php echo $col_count ?>" class="<?php echo $class_name; ?>">
87
  <tr class="em-tickets-row">
88
+ <td class="ticket-status">
89
+ <span class="dashicons dashicons-menu <?php if($EM_Ticket->ticket_id && $EM_Ticket->is_available(true, true)){ echo 'ticket-on'; }elseif($EM_Ticket->ticket_id > 0){ echo 'ticket-off'; }else{ echo 'ticket-new'; } ?>"></span>
90
+ </td>
91
  <td class="ticket-name">
92
  <span class="ticket_name"><?php if($EM_Ticket->ticket_members) echo '* ';?><?php echo wp_kses_data($EM_Ticket->ticket_name); ?></span>
93
  <div class="ticket_description"><?php echo wp_kses($EM_Ticket->ticket_description,$allowedposttags); ?></div>
94
  <div class="ticket-actions">
95
+ <a href="#" class="ticket-actions-edit"><?php esc_html_e('Edit','events-manager'); ?></a>
96
  <?php if( $EM_Ticket->get_bookings_count() == 0 ): ?>
97
  | <a href="<?php bloginfo('wpurl'); ?>/wp-load.php" class="ticket-actions-delete"><?php esc_html_e('Delete','events-manager'); ?></a>
98
  <?php else: ?>
106
  <td class="ticket-limit">
107
  <span class="ticket_min">
108
  <?php echo ( !empty($EM_Ticket->ticket_min) ) ? esc_html($EM_Ticket->ticket_min):'-'; ?>
109
+ </span> /
110
  <span class="ticket_max"><?php echo ( !empty($EM_Ticket->ticket_max) ) ? esc_html($EM_Ticket->ticket_max):'-'; ?></span>
111
  </td>
112
  <td class="ticket-time">
templates/templates/ical.php CHANGED
@@ -92,8 +92,8 @@ while ( count($EM_Events) > 0 ){
92
  $geo = 'GEO:'.$EM_Event->get_location()->location_latitude.";".$EM_Event->get_location()->location_longitude;
93
  }
94
  if( !defined('EM_ICAL_APPLE_STRUCT') || !EM_ICAL_APPLE_STRUCT ){
95
- $apple_location = $EM_Event->output('#_LOCATIONFULLLINE, #_LOCATIONCOUNTRY', 'ical');
96
- $apple_location_title = $EM_Event->output('#_LOCATIONNAME', 'ical');
97
  $apple_geo = !empty($geo) ? $EM_Event->get_location()->location_latitude.",".$EM_Event->get_location()->location_longitude:'0,0';
98
  $apple_structured_location = "X-APPLE-STRUCTURED-LOCATION;VALUE=URI;X-ADDRESS={$apple_location};X-APPLE-RADIUS=100;X-TITLE={$apple_location_title}:geo:{$apple_geo}";
99
  $apple_structured_location = str_replace('"', '\"', $apple_structured_location); //google chucks a wobbly with these on this line
92
  $geo = 'GEO:'.$EM_Event->get_location()->location_latitude.";".$EM_Event->get_location()->location_longitude;
93
  }
94
  if( !defined('EM_ICAL_APPLE_STRUCT') || !EM_ICAL_APPLE_STRUCT ){
95
+ $apple_location = str_replace(';', '', html_entity_decode(str_replace('\;', ';', $EM_Event->output('#_LOCATIONFULLLINE, #_LOCATIONCOUNTRY', 'ical'))));
96
+ $apple_location_title = str_replace('\;', '', html_entity_decode(str_replace('\;', ';', $EM_Event->output('#_LOCATIONNAME', 'ical'))));
97
  $apple_geo = !empty($geo) ? $EM_Event->get_location()->location_latitude.",".$EM_Event->get_location()->location_longitude:'0,0';
98
  $apple_structured_location = "X-APPLE-STRUCTURED-LOCATION;VALUE=URI;X-ADDRESS={$apple_location};X-APPLE-RADIUS=100;X-TITLE={$apple_location_title}:geo:{$apple_geo}";
99
  $apple_structured_location = str_replace('"', '\"', $apple_structured_location); //google chucks a wobbly with these on this line