Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/events-made-easy/events-manager.php
+++ b/events-made-easy/events-manager.php
@@ -6,7 +6,7 @@
/*
Plugin Name: Events Made Easy
-Version: 3.1.3
+Version: 3.1.4
Plugin URI: https://www.e-dynamics.be/wordpress
Description: Manage and display events and memberships. Also includes recurring events; locations; widgets; maps; RSVP; ICAL and RSS feeds; Paypal, Stripe, Mollie and others.
Author: Franky Van Liedekerke
@@ -35,7 +35,7 @@
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-define( 'EME_VERSION', '3.1.3' );
+define( 'EME_VERSION', '3.1.4' );
define( 'EME_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'EME_INCLUDE_DIR', EME_PLUGIN_DIR . 'includes/' );
define( 'EME_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
--- a/events-made-easy/includes/eme-actions.php
+++ b/events-made-easy/includes/eme-actions.php
@@ -412,7 +412,14 @@
wp_register_style( 'eme-markercluster-css2', EME_PLUGIN_URL . 'js/leaflet-markercluster-1.4.1/MarkerCluster.Default.css', [], EME_VERSION );
wp_register_style( 'eme-gestures-css', EME_PLUGIN_URL . 'js/leaflet-gesturehandling-1.2.1/leaflet-gesture-handling.min.css', [], EME_VERSION );
wp_register_script( 'eme-show-maps', EME_PLUGIN_URL . 'js/eme_show_maps.js', [ 'eme-leaflet-maps' ], EME_VERSION, true );
+ $translation_array = [
+ 'translate_addressnotfound' => __('Address not found', 'events-made-easy' ),
+ 'translate_couldnotcalcroute' => __('Could not calculate route', 'events-made-easy' )
+ ];
+ wp_localize_script( 'eme-show-maps', 'emeshowmaps', $translation_array );
wp_register_script( 'eme-edit-maps', EME_PLUGIN_URL . 'js/eme_edit_maps.js', [ 'eme-leaflet-maps' ], EME_VERSION, true );
+ wp_register_script( 'eme-leaflet-routing', EME_PLUGIN_URL . 'js/leaflet-routing-machine-3.2.12/leaflet-routing-machine.min.js', [ 'eme-leaflet-maps' ], EME_VERSION, true );
+ wp_register_style( 'eme-leaflet-routing-css', EME_PLUGIN_URL . 'js/leaflet-routing-machine-3.2.12/leaflet-routing-machine.css', [ 'eme-leaflet-css' ], EME_VERSION );
$translation_array = [
'translate_map_zooming' => get_option( 'eme_map_zooming' ) ? 'true' : 'false',
'translate_default_map_icon' => get_option( 'eme_location_map_icon' ),
--- a/events-made-easy/includes/eme-events.php
+++ b/events-made-easy/includes/eme-events.php
@@ -9636,7 +9636,7 @@
'translate_name' => __( 'Name', 'events-made-easy' ),
'translate_insertnewevent' => __( 'Insert New Event', 'events-made-easy' ),
// translators: %s is the event name
- 'translate_editeventstring' => __( "Edit Event '%s'", 'events-made-easy' ),
+ 'translate_editeventstring' => __( "Edit Event", 'events-made-easy' ),
'translate_status' => __( 'Status', 'events-made-easy' ),
'translate_copy' => __( 'Copy', 'events-made-easy' ),
'translate_csv' => __( 'CSV', 'events-made-easy' ),
--- a/events-made-easy/includes/eme-formfields.php
+++ b/events-made-easy/includes/eme-formfields.php
@@ -1882,7 +1882,7 @@
// invite URL overrides
$invite_readonly = '';
- if ( eme_check_invite_url( $event['event_id'] ) && ! $eme_is_admin_request ) {
+ if ( eme_check_invite_url( $event_id ) && ! $eme_is_admin_request ) {
if ( ! empty( $_GET['eme_email'] ) ) {
$person['email'] = eme_sanitize_email( $_GET['eme_email'] );
}
@@ -2123,7 +2123,7 @@
return "<textarea {$ctx['required_att']} name='eme_rsvpcomment' $dfc placeholder='$placeholder_text' >$bookerComment</textarea>";
};
- $handlers['/#_SEATS$|#_SPACES$/'] = function( $result, $matches, $ctx ) use ( $event_id, $is_multibooking, $editing_booking_from_backend, $bookedSeats, $booked_seats_options, $waitinglist, $new_booking_in_frontend, $min_allowed_is_multi, $min_allowed, $max_allowed, $dynamic_price_class_basic ) {
+ $handlers['/#_SEATS$|#_SPACES$/'] = function( $result, $matches, $ctx ) use ( $event_id, $event, $is_multibooking, $editing_booking_from_backend, $bookedSeats, $booked_seats_options, $waitinglist, $new_booking_in_frontend, $min_allowed_is_multi, $min_allowed, $max_allowed, $dynamic_price_class_basic ) {
$var_prefix = "bookings[$event_id][";
$var_postfix = ']';
$fieldname = "{$var_prefix}bookedSeats{$var_postfix}";
--- a/events-made-easy/includes/eme-fs.php
+++ b/events-made-easy/includes/eme-fs.php
@@ -437,8 +437,7 @@
break;
case 'location_name':
$required = 1;
- $type = 'text';
- $more .= " class='clearable'";
+ $type = 'search';
break;
case 'event_name':
$required = 1;
--- a/events-made-easy/includes/eme-gdpr.php
+++ b/events-made-easy/includes/eme-gdpr.php
@@ -359,6 +359,43 @@
wp_die();
}
+ $person = eme_get_person( $person_id );
+ if ( empty( $person ) ) {
+ $message = __( "Form tampering detected. If you believe you've received this message in error please contact the site owner.", 'events-made-easy' );
+ echo wp_json_encode(
+ [
+ 'Result' => 'NOK',
+ 'htmlmessage' => $message,
+ ]
+ );
+ wp_die();
+ }
+
+ $wp_id = intval( $person['wp_id'] );
+ $current_user_id = get_current_user_id();
+
+ if ( ! empty( $wp_id ) && ! empty( $current_user_id ) && $wp_id === $current_user_id ) {
+ // logged-in user owns this person
+ } elseif ( ! isset( $_POST['eme_cpi_nonce'] ) ) {
+ $message = __( "Form tampering detected. If you believe you've received this message in error please contact the site owner.", 'events-made-easy' );
+ echo wp_json_encode(
+ [
+ 'Result' => 'NOK',
+ 'htmlmessage' => $message,
+ ]
+ );
+ wp_die();
+ } elseif ( ! wp_verify_nonce( eme_sanitize_request( $_POST['eme_cpi_nonce'] ), "change_pi $person_id " . $person['email'] ) ) {
+ $message = __( "Form tampering detected. If you believe you've received this message in error please contact the site owner.", 'events-made-easy' );
+ echo wp_json_encode(
+ [
+ 'Result' => 'NOK',
+ 'htmlmessage' => $message,
+ ]
+ );
+ wp_die();
+ }
+
$captcha_res = eme_check_captcha();
[$person_id, $add_update_message] = eme_add_update_person_from_form( $person_id );
@@ -390,6 +427,8 @@
esc_html__( 'Email: ', 'events-made-easy' ) . '#_EMAIL <br>';
$format = eme_nl2br_save_html( get_option( 'eme_cpi_form', $format_default ) );
+ $cpi_nonce = wp_create_nonce( "change_pi $person_id " . $person['email'] );
+
usleep( 2 );
$form_id = "eme_".eme_random_id(); // JS selectors need to start with a letter, so to be sure we prefix it
$form_html = "<noscript><div class='eme-noscriptmsg'>" . __( 'Javascript is required for this form to work properly', 'events-made-easy' ) . "</div></noscript>
@@ -397,6 +436,7 @@
$nonce
<span id='honeypot_check'><input type='text' name='honeypot_check' value='' autocomplete='off'></span>
<input type='hidden' name='person_id' value='" . $person_id . "'>
+ <input type='hidden' name='eme_cpi_nonce' value='" . $cpi_nonce . "'>
";
$form_html .= eme_replace_cpiform_placeholders( $format, $person );
$form_html .= '</form></div>';
--- a/events-made-easy/includes/eme-locations.php
+++ b/events-made-easy/includes/eme-locations.php
@@ -2709,22 +2709,66 @@
}
function eme_add_directions_form( $location ) {
- $locale_code = substr( get_locale(), 0, 2 );
- $res = '';
- if ( isset( $location['location_address1'] ) && isset( $location['location_city'] ) ) {
- $res .= '<form action="//maps.google.com/maps" method="get" target="_blank" rel="noopener noreferrer" style="text-align:left;">';
- $res .= '<div id="eme_direction_form"><label for="saddr">' . __( 'Your Street Address', 'events-made-easy' ) . '</label><br>';
- $res .= '<input type="text" name="saddr" id="saddr" value="">';
- $res .= '<input type="hidden" name="daddr" value="' . esc_attr( $location['location_address1'] . ', ' . $location['location_city'] ) . '">';
- $res .= '<input type="hidden" name="hl" value="' . esc_attr( $locale_code ) . '"></div>';
- $res .= '<input type="submit" value="' . esc_attr__( 'Get Directions', 'events-made-easy' ) . '">';
- $res .= '</form>';
+ if ( ! isset( $location['location_id'] ) || $location['location_id'] <= 0 ) {
+ return '';
}
+ if ( empty( $location['location_latitude'] ) || empty( $location['location_longitude'] ) ) {
+ return '';
+ }
+
+ wp_enqueue_style( 'eme-leaflet-css' );
+ wp_enqueue_style( 'eme-leaflet-routing-css' );
+ if ( get_option( 'eme_map_gesture_handling' ) ) {
+ wp_enqueue_script( 'eme-leaflet-gestures' );
+ wp_enqueue_style( 'eme-gestures-css' );
+ }
+ wp_enqueue_script( 'eme-show-maps' );
+ wp_enqueue_script( 'eme-leaflet-routing' );
- # some people might want to change the form to their liking
- if ( has_filter( 'eme_directions_form_filter' ) ) {
- $res = apply_filters( 'eme_directions_form_filter', $res );
+ $id_base = preg_replace( '/D/', '_', microtime( 1 ) );
+ if ( isset( $location['event_id'] ) ) {
+ $id_base = $location['event_id'] . '_' . $id_base;
+ } else {
+ $id_base = wp_rand() . '_' . $id_base;
}
+ $map_id = 'eme-directions-map_' . $id_base;
+ $origin_id = 'eme-directions-origin_' . $id_base;
+ $instructions_id = 'eme-directions-instructions_' . $id_base;
+ $enable_zooming = get_option( 'eme_map_zooming' ) ? 'true' : 'false';
+ $gestures = get_option( 'eme_map_gesture_handling' ) ? 'true' : 'false';
+ $zoom_factor = get_option( 'eme_indiv_zoom_factor' );
+ if ( $zoom_factor > 14 ) {
+ $zoom_factor = 14;
+ }
+
+ $dest_address_parts = array_filter( [ $location['location_address1'], $location['location_city'] ] );
+ $dest_address = esc_attr( implode( ', ', $dest_address_parts ) );
+
+ // the next line is the demo site, not to be used in prod
+ //$osrm_url = apply_filters( 'eme_osrm_service_url', 'https://router.project-osrm.org/route/v1' );
+
+ // the next one is community maintained for now
+ $osrm_url = apply_filters( 'eme_osrm_service_url', 'https://routing.openstreetmap.de/routed-car/route/v1' );
+
+ $data = "data-zoom_factor='$zoom_factor'";
+ $data .= " data-enable_zooming='$enable_zooming'";
+ $data .= " data-gestures='$gestures'";
+ $data .= " data-osrm-url='" . esc_attr( $osrm_url ) . "'";
+
+ $res = "<div class='eme-directions-form-wrapper'>";
+ $res .= "<form class='eme-directions-form' data-map-id='$map_id' data-instructions-id='$instructions_id'>";
+ $res .= '<label for="' . $origin_id . '">' . __( 'Your Street Address', 'events-made-easy' ) . '</label><br>';
+ $res .= "<input type='text' id='$origin_id' class='eme-directions-origin' value='' placeholder='" . esc_attr__( 'e.g. 123 Main St, City', 'events-made-easy' ) . "'>";
+ $res .= "<input type='hidden' name='eme_directions_dest_lat' value='" . esc_attr( $location['location_latitude'] ) . "'>";
+ $res .= "<input type='hidden' name='eme_directions_dest_lon' value='" . esc_attr( $location['location_longitude'] ) . "'>";
+ $res .= "<input type='hidden' name='eme_directions_dest_address' value='$dest_address'>";
+ $res .= "<input type='submit' value='" . esc_attr__( 'Get Directions', 'events-made-easy' ) . "'>";
+ $res .= '</form>';
+
+ $res .= "<div id='$map_id' class='eme-directions-map' style='display:none;width:100%;height:400px;' $data></div>";
+
+ $res .= "<div id='$instructions_id' class='eme-directions-instructions' style='display:none;'></div>";
+ $res .= '</div>';
return $res;
}
--- a/events-made-easy/includes/eme-mailer.php
+++ b/events-made-easy/includes/eme-mailer.php
@@ -1390,6 +1390,7 @@
$rsvp_status = $conditions['rsvp_status'] ?? 0;
$only_unpaid = $conditions['only_unpaid'] ?? 0;
$exclude_registered = $conditions['exclude_registered'] ?? 0;
+ $exclude_registered_events = $conditions['exclude_registered_events'] ?? '';
if ( ! empty( $conditions['pending_approved'] ) ) {
if ( $conditions['pending_approved'] == 1 ) $rsvp_status = EME_RSVP_STATUS_PENDING;
@@ -1411,11 +1412,11 @@
case 'all_people':
case 'all_people_not_registered':
case 'people_and_groups':
- eme_process_event_people_groups( $event, $conditions, $ignore_massmail, $rsvp_status, $only_unpaid, $exclude_registered, $mail_subject, $mail_message, $mail_text_html, $batch, $atts_arr );
+ eme_process_event_people_groups( $event, $conditions, $ignore_massmail, $rsvp_status, $only_unpaid, $exclude_registered, $exclude_registered_events, $mail_subject, $mail_message, $mail_text_html, $batch, $atts_arr );
break;
case 'all_wp':
case 'all_wp_not_registered':
- eme_process_event_wp_users( $event, $conditions, $exclude_registered, $mail_subject, $mail_message, $mail_text_html, $batch, $atts_arr );
+ eme_process_event_wp_users( $event, $conditions, $exclude_registered, $exclude_registered_events, $mail_subject, $mail_message, $mail_text_html, $batch, $atts_arr );
break;
}
}
@@ -1465,7 +1466,7 @@
/**
* Process people, groups, and members for an event and add them to the mail batch.
*/
-function eme_process_event_people_groups( $event, $conditions, $ignore_massmail, $rsvp_status, $only_unpaid, $exclude_registered, $mail_subject, $mail_message, $mail_text_html, EMEMailBatch $batch, $atts_arr ) {
+function eme_process_event_people_groups( $event, $conditions, $ignore_massmail, $rsvp_status, $only_unpaid, $exclude_registered, $exclude_registered_events, $mail_subject, $mail_message, $mail_text_html, EMEMailBatch $batch, $atts_arr ) {
$event_id = $event['event_id'];
$mail_type = $conditions['eme_mail_type'];
@@ -1496,7 +1497,14 @@
}
// Get registered person IDs if we need to exclude them
- $registered_ids = ( $exclude_registered || $mail_type == 'all_people_not_registered' ) ? eme_get_attendee_ids( $event_id ) : [];
+ $exclude_event_ids = [];
+ if ( $exclude_registered || $mail_type == 'all_people_not_registered' ) {
+ $exclude_event_ids[] = $event_id;
+ }
+ if ( ! empty( $exclude_registered_events ) ) {
+ $exclude_event_ids = array_merge( $exclude_event_ids, explode( ',', $exclude_registered_events ) );
+ }
+ $registered_emails = ! empty( $exclude_event_ids ) ? eme_get_attendee_emails( array_unique( $exclude_event_ids ) ) : [];
$handled_emails = [];
@@ -1504,11 +1512,11 @@
foreach ( $member_ids as $member_id ) {
$member = eme_get_member( $member_id );
if ( ! $member ) continue;
- if ( in_array( $member['person_id'], $registered_ids ) ) continue;
$person = eme_get_person( $member['person_id'] );
if ( ! $person ) continue;
if ( ! $ignore_massmail && ! $person['massmail'] && ! in_array( $member_id, $cond_member_ids ) ) continue;
+ if ( in_array( $person['email'], $registered_emails ) ) continue;
$handled_emails[] = $person['email'];
$person_name = eme_format_full_name( $person['firstname'], $person['lastname'], $person['email'] );
@@ -1530,10 +1538,10 @@
// Process people
foreach ( $person_ids as $person_id ) {
- if ( in_array( $person_id, $registered_ids ) ) continue;
$person = eme_get_person( $person_id );
if ( ! $person ) continue;
if ( ! $ignore_massmail && ! $person['massmail'] ) continue;
+ if ( in_array( $person['email'], $registered_emails ) ) continue;
if ( in_array( $person['email'], $handled_emails ) ) continue;
$handled_emails[] = $person['email'];
@@ -1556,10 +1564,17 @@
/**
* Process WordPress users for an event and add them to the mail batch.
*/
-function eme_process_event_wp_users( $event, $conditions, $exclude_registered, $mail_subject, $mail_message, $mail_text_html, EMEMailBatch $batch, $atts_arr ) {
+function eme_process_event_wp_users( $event, $conditions, $exclude_registered, $exclude_registered_events, $mail_subject, $mail_message, $mail_text_html, EMEMailBatch $batch, $atts_arr ) {
$mail_type = $conditions['eme_mail_type'];
$wp_users = get_users();
- $attendee_wp_ids = ( $mail_type == 'all_wp_not_registered' || $exclude_registered ) ? eme_get_wp_ids_for( $event['event_id'] ) : [];
+ $exclude_event_ids = [];
+ if ( $mail_type == 'all_wp_not_registered' || $exclude_registered ) {
+ $exclude_event_ids[] = $event['event_id'];
+ }
+ if ( ! empty( $exclude_registered_events ) ) {
+ $exclude_event_ids = array_merge( $exclude_event_ids, explode( ',', $exclude_registered_events ) );
+ }
+ $attendee_wp_ids = ! empty( $exclude_event_ids ) ? eme_get_wp_ids_for( array_unique( $exclude_event_ids ) ) : [];
$lang = eme_detect_lang();
$handled_emails = [];
@@ -2444,7 +2459,7 @@
$mail_message = $parsed['mail_message'];
$event_ids = isset( $post_data['event_ids'] ) ? wp_parse_id_list( $post_data['event_ids'] ) : [];
- if ( ! eme_is_numeric_array( $event_ids ) ) {
+ if ( empty($event_ids) || ! eme_is_numeric_array( $event_ids ) ) {
return [ 'success' => false, 'message' => "<div id='message' class='error eme-message-admin'><p>" . __( 'Please select at least one event.', 'events-made-easy' ) . '</p></div>' ];
}
@@ -2484,6 +2499,9 @@
$conditions['rsvp_status'] = isset( $post_data['rsvp_status'] ) ? intval( $post_data['rsvp_status'] ) : 0;
$conditions['only_unpaid'] = isset( $post_data['only_unpaid'] ) ? intval( $post_data['only_unpaid'] ) : 0;
$conditions['exclude_registered'] = isset( $post_data['exclude_registered'] ) ? intval( $post_data['exclude_registered'] ) : 0;
+ $conditions['exclude_registered_events'] = ( ! empty( $post_data['exclude_registered_events'] ) && eme_is_numeric_array( $post_data['exclude_registered_events'] ) )
+ ? join( ',', array_map( 'intval', $post_data['exclude_registered_events'] ) )
+ : '';
$current_userid = get_current_user_id();
$mail_problems = 0;
@@ -2556,8 +2574,10 @@
$mygroups = [];
$mymembergroups = [];
$myevents = [];
+ $myexcludeevents = [];
$person_ids = [];
$event_ids = [];
+ $exclude_registered_event_ids = [];
$membership_ids = [];
$persongroup_ids = [];
$membergroup_ids = [];
@@ -2605,6 +2625,7 @@
$exclude_registered_checked = '';
$only_unpaid_checked = '';
$eme_mail_type = '';
+ $eme_rsvp_status = '';
$send_to_all_people_checked = '';
$event_mail_subject = '';
$event_mail_message = '';
@@ -2741,6 +2762,9 @@
if ( ! empty( $conditions['eme_mail_type'] ) ) {
$eme_mail_type = $conditions['eme_mail_type'];
}
+ if ( ! empty( $conditions['rsvp_status'] ) ) {
+ $eme_rsvp_status = $conditions['rsvp_status'];
+ }
if ( ! empty( $conditions['exclude_registered'] ) ) {
$exclude_registered_checked = "checked='checked'";
}
@@ -2755,6 +2779,13 @@
$myevents[ $event['event_id'] ] = $event['event_name']. ' (' . eme_localized_date( $event['event_start'], EME_TIMEZONE, 1 ) . ')';
}
}
+ if ( ! empty( $conditions['exclude_registered_events'] ) ) {
+ $exclude_registered_event_ids = explode( ',', $conditions['exclude_registered_events'] );
+ $exclude_events = eme_get_events( extra_conditions: [ 'event_id' => array_map( 'intval', $exclude_registered_event_ids ) ] );
+ foreach ( $exclude_events as $exclude_event ) {
+ $myexcludeevents[ $exclude_event['event_id'] ] = $exclude_event['event_name']. ' (' . eme_localized_date( $exclude_event['event_start'], EME_TIMEZONE, 1 ) . ')';
+ }
+ }
if ( ! empty( $conditions['eme_eventmail_send_persons'] ) ) {
$person_ids = explode( ',', $conditions['eme_eventmail_send_persons'] );
$persons = eme_get_persons( $person_ids );
@@ -2872,7 +2903,7 @@
<h1><?php esc_html_e( 'Send event related emails', 'events-made-easy' ); ?></h1>
<form id='send_mail' name='send_mail' action="#" method="post" onsubmit="return false;">
<div id='send_event_mail_div'>
- <table>
+ <table class='widefat'>
<tr>
<td><?php
$label = esc_html__( 'Select the event(s)', 'events-made-easy' );
@@ -2880,7 +2911,7 @@
echo $label; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped at assignment
?>
</td>
- <td><?php echo eme_ui_multiselect( $event_ids, 'event_ids', $myevents, 5, '', 0, 'eme_snapselect_events_class', $aria_label ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- trusted HTML from eme_ui_multiselect() ?>
+ <td><?php echo eme_ui_multiselect( $event_ids, 'event_ids', $myevents, 5, '', 1, 'eme_snapselect_events_class', $aria_label ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- trusted HTML from eme_ui_multiselect() ?>
<br><label><input id="eventsearch_all" name='eventsearch_all' value='1' type='checkbox'> <?php esc_html_e( 'Check this box to search through all events and not just future ones.', 'events-made-easy' ); ?> </label>
<p class='eme_smaller'><?php esc_html_e( 'Remark: if you select multiple events, a mailing will be created for each selected event', 'events-made-easy' ); ?></p>
</td>
@@ -2896,18 +2927,21 @@
'people_and_groups' => __('Email to people and/or groups registered in EME', 'events-made-easy'),
'all_wp' => __('Email to all WP users', 'events-made-easy'),
];
- echo eme_ui_select( $eme_mail_type, 'eme_mail_type', $eme_mail_type_arr, ' ', 1); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- trusted HTML from eme_ui_select()
+ echo eme_ui_select( $eme_mail_type, 'eme_mail_type', $eme_mail_type_arr, ' ', 1, 'eme_snapselect'); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- trusted HTML from eme_ui_select()
?>
</td>
</tr>
<tr id="eme_rsvp_status_row">
<td><?php esc_html_e( 'Select your target audience', 'events-made-easy' ); ?></td>
<td>
- <select name="rsvp_status">
- <option value="0"><?php esc_html_e( 'All registered persons', 'events-made-easy' ); ?></option>
- <option value="<?php echo esc_attr( EME_RSVP_STATUS_APPROVED ); ?>"><?php esc_html_e( 'Only approved bookings', 'events-made-easy' ); ?></option>
- <option value="<?php echo esc_attr( EME_RSVP_STATUS_PENDING ); ?>"><?php esc_html_e( 'Only pending bookings', 'events-made-easy' ); ?></option>
- </select>
+ <?php
+ $target_audience = [
+ 0 => __( 'All registered persons', 'events-made-easy' ),
+ EME_RSVP_STATUS_APPROVED => __( 'Only approved bookings', 'events-made-easy' ),
+ EME_RSVP_STATUS_PENDING => __( 'Only pending bookings', 'events-made-easy' ),
+ ];
+ echo eme_ui_select( $eme_rsvp_status, 'rsvp_status', $target_audience, '', 0, 'eme_snapselect'); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- trusted HTML from eme_ui_select()
+ ?>
</td>
</tr>
<tr id="eme_exclude_registered_row">
@@ -2916,6 +2950,16 @@
<input type="checkbox" name="exclude_registered" value="1" <?php echo $exclude_registered_checked; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- hardcoded checked attribute ?>>
</td>
</tr>
+ <tr id="eme_exclude_registered_events_row">
+ <td><?php
+ $exclude_label = esc_html__( 'Also exclude emails of people already registered for these other event(s)', 'events-made-easy' );
+ $exclude_aria_label = 'aria-label="' . $exclude_label . '"';
+ echo $exclude_label; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped at assignment
+ ?> </td>
+ <td><?php echo eme_ui_multiselect( $exclude_registered_event_ids, 'exclude_registered_events', $myexcludeevents, 5, '', 0, 'eme_snapselect_events_class', $exclude_aria_label ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- trusted HTML from eme_ui_multiselect() ?>
+ <p class='eme_smaller'><?php esc_html_e( 'Optional: pick any event(s) whose registrants should be skipped, independent of which event(s) this mailing is about.', 'events-made-easy' ); ?></p>
+ </td>
+ </tr>
<tr id="eme_only_unpaid_row">
<td><span id="span_unpaid_attendees"><?php esc_html_e( 'Only send emails to attendees who did not pay yet', 'events-made-easy' ); ?></span>
<span id="span_unpaid_bookings"><?php esc_html_e( 'Only take unpaid bookings into account', 'events-made-easy' ); ?></span>
--- a/events-made-easy/includes/eme-members.php
+++ b/events-made-easy/includes/eme-members.php
@@ -1113,10 +1113,10 @@
} elseif ( eme_is_empty_string( $_POST['lastname'] ) ) {
// we need at least lastname
$err = __( 'Please enter at least the last name for a new member', 'events-made-easy' );
- } elseif ( ! $eme_is_admin_request && ! eme_is_email_frontend( sanitize_text_field( wp_unslash( $_POST['email'] ) ) ) ) {
+ } elseif ( ! $eme_is_admin_request && ! eme_is_email_frontend( eme_sanitize_email( $_POST['email'] ) ) ) {
// we need an email
$err = __( 'Please enter a valid email address', 'events-made-easy' );
- } elseif ( $membership['properties']['create_wp_user'] && ! eme_is_email( sanitize_text_field( wp_unslash( $_POST['email'] ) ) ) ) {
+ } elseif ( $membership['properties']['create_wp_user'] && ! eme_is_email( eme_sanitize_email( $_POST['email'] ) ) ) {
// we need an email
$err = __( 'Please enter a valid email address', 'events-made-easy' );
} else {
@@ -2838,7 +2838,7 @@
if (empty($limit_to_group)) {
?>
<button id="StoreQueryButton" class="button action eme_admin_button_middle"><?php esc_html_e( 'Store result as dynamic group', 'events-made-easy' ); ?></button>
- <div id="StoreQueryDiv"><?php esc_html_e( 'Enter a name for this dynamic group', 'events-made-easy' ); ?> <input type="text" id="dynamicgroupname" name="dynamicgroupname" class="clearable" size=20>
+ <div id="StoreQueryDiv"><?php esc_html_e( 'Enter a name for this dynamic group', 'events-made-easy' ); ?> <input type="search" id="dynamicgroupname" name="dynamicgroupname" size=20>
<button id="StoreQuerySubmitButton" class="button action"><?php esc_html_e( 'Store dynamic group', 'events-made-easy' ); ?></button>
</div>
<?php
--- a/events-made-easy/includes/eme-people.php
+++ b/events-made-easy/includes/eme-people.php
@@ -1987,7 +1987,7 @@
if (empty($limit_to_group)) {
?>
<button id="StoreQueryButton" class="button action eme_admin_button_middle"><?php esc_html_e( 'Store result as dynamic group', 'events-made-easy' ); ?></button>
- <div id="StoreQueryDiv"><?php esc_html_e( 'Enter a name for this dynamic group', 'events-made-easy' ); ?> <input type="text" id="dynamicgroupname" name="dynamicgroupname" class="clearable" size=20>
+ <div id="StoreQueryDiv"><?php esc_html_e( 'Enter a name for this dynamic group', 'events-made-easy' ); ?> <input type="search" id="dynamicgroupname" name="dynamicgroupname" size=20>
<button id="StoreQuerySubmitButton" class="button action"><?php esc_html_e( 'Store dynamic group', 'events-made-easy' ); ?></button>
</div>
<?php
@@ -2808,7 +2808,7 @@
</tr>
<tr>
<td><label for="People"><?php esc_html_e( 'People', 'events-made-easy' ); ?></label></td>
- <td><?php echo eme_ui_multiselect( $grouppersons, 'persons', $mygroups, 5, '', 1, 'eme_snapselect_people_class' ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- trusted HTML from eme_ui_multiselect() ?></td>
+ <td><?php echo eme_ui_multiselect( $grouppersons, 'persons', $mygroups, 5, '', 0, 'eme_snapselect_people_class' ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- trusted HTML from eme_ui_multiselect() ?></td>
</tr>
<?php
} elseif ( $group['type'] == 'dynamic_people' ) {
--- a/events-made-easy/includes/eme-recurrence.php
+++ b/events-made-easy/includes/eme-recurrence.php
@@ -405,12 +405,11 @@
// 2 steps for updating events for a recurrence:
// First step: check the existing events and
// if they still match the recurrence days or they have existing bookings (future events only), update them
- // otherwise delete the old event
+ // otherwise delete the old event
// Reason for doing this: we want to keep possible booking data for a recurrent event as well
- // and just deleting all current events for a recurrence and inserting new ones would break the link
- // between booking id and event id
+ // and just deleting all current events for a recurrence and inserting new ones would break the link between booking id and event id
// Second step: check all days of the recurrence and if no event exists yet, insert it
- $prepared_sql = $wpdb->prepare( "SELECT event_id,event_start FROM $events_table WHERE recurrence_id = %d AND event_status <> %d", $recurrence['recurrence_id'], EME_EVENT_STATUS_TRASH ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+ $prepared_sql = $wpdb->prepare( "SELECT event_id,event_start,event_rsvp,event_tasks FROM $events_table WHERE recurrence_id = %d AND event_status <> %d", $recurrence['recurrence_id'], EME_EVENT_STATUS_TRASH ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$events = $wpdb->get_results( $prepared_sql, ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
// in order to take tasks into account for recurring events, we need to know the difference in days between the events
@@ -425,13 +424,18 @@
$day = eme_get_date_from_dt( $existing_event['event_start'] );
$array_key = array_search( $day, $matching_days );
$existing_event_start_obj = new emeExpressiveDate( $existing_event['event_start'], EME_TIMEZONE );
- // if future events in the recurrence have bookings, we won't delete those but keep them in the recurrence series
+ // if future events in the recurrence have bookings or task signups, we won't delete those but keep them in the recurrence series
+ $bookings_count = 0;
+ $tasksignups_count = 0;
if ( $existing_event_start_obj >= $eme_date_obj_now ) {
- $bookings_count = eme_count_bookings_for( $existing_event['event_id'] );
- } else {
- $bookings_count = 0;
+ if ($existing_event['event_rsvp']) {
+ $bookings_count = eme_count_bookings_for( $existing_event['event_id'] );
+ }
+ if ($existing_event['event_tasks']) {
+ $tasksignups_count = eme_count_tasksignups_for( $existing_event['event_id'] );
+ }
}
- if ( $array_key !== false || $bookings_count > 0 ) {
+ if ( $array_key !== false || $bookings_count > 0 || $tasksignups_count > 0 ) {
if ( ! $only_change_recdates ) {
$event['event_start'] = "$day $event_start_time";
$eme_date_obj = new emeExpressiveDate( $event['event_start'], EME_TIMEZONE );
--- a/events-made-easy/includes/eme-rsvp.php
+++ b/events-made-easy/includes/eme-rsvp.php
@@ -3177,41 +3177,23 @@
return $bookings;
}
-function eme_count_bookings_for( $event_ids, $rsvp_status = 0, $paid_status = 0 ) {
+function eme_count_bookings_for( $event_id ) {
global $wpdb;
$bookings_table = EME_DB_PREFIX . EME_BOOKINGS_TBNAME;
- $bookings = [];
- if ( ! $event_ids ) {
- return $bookings;
- }
-
- $where = [];
- if ( is_array( $event_ids ) && eme_is_numeric_array( $event_ids ) ) {
- $ids_arr_int = array_map('intval', $event_ids);
- $placeholders = implode(',', array_fill(0, count($ids_arr_int), '%d'));
- $where[] = $wpdb->prepare( 'bookings.event_id IN (' . $placeholders . ')', ...$ids_arr_int );
- } elseif ( is_numeric( $event_ids ) ) {
- $event_id = intval( $event_ids );
- $where[] = $wpdb->prepare( 'bookings.event_id = %d', $event_id );
- } else {
- $where[] = 'bookings.event_id = 0';
+ if ( ! $event_id ) {
+ return false;
}
- if ( $rsvp_status ) {
- $rsvp_status_int = intval( $rsvp_status );
- $where[] = $wpdb->prepare( 'bookings.status = %d', $rsvp_status_int );
+ $where_arr = [];
+ if ( is_numeric( $event_id ) ) {
+ $where_arr[] = $wpdb->prepare( 'bookings.event_id = %d', $event_id );
} else {
- $where[] = $wpdb->prepare( 'bookings.status != %d', EME_RSVP_STATUS_TRASH );
+ $where_arr[] = 'bookings.event_id = 0';
}
+ $where_arr[] = $wpdb->prepare( 'bookings.status != %d', EME_RSVP_STATUS_TRASH );
- if ( $paid_status == 1 ) {
- $where[] = 'bookings.booking_paid=0';
- } elseif ( $paid_status == 2 ) {
- $where[] = 'bookings.booking_paid=1';
- }
- $where = 'WHERE ' . implode( ' AND ', $where );
- #$sql = "SELECT * FROM $bookings_table $where ORDER BY booking_id";
+ $where = 'WHERE ' . implode( ' AND ', $where_arr );
$sql = "SELECT COUNT(*) FROM $bookings_table AS bookings $where"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
}
@@ -3325,7 +3307,16 @@
global $wpdb;
$bookings_table = EME_DB_PREFIX . EME_BOOKINGS_TBNAME;
$people_table = EME_DB_PREFIX . EME_PEOPLE_TBNAME;
- $prepared_sql = $wpdb->prepare( "SELECT DISTINCT people.wp_id FROM $bookings_table AS bookings LEFT JOIN $people_table AS people ON bookings.person_id=people.person_id WHERE bookings.status IN (%d,%d,%d) AND bookings.event_id = %d AND people.wp_id != 0", EME_RSVP_STATUS_PENDING, EME_RSVP_STATUS_USERPENDING, EME_RSVP_STATUS_APPROVED, $event_id ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+ if ( is_array( $event_id ) && eme_is_numeric_array( $event_id ) ) {
+ $ids_arr_int = array_map( 'intval', $event_id );
+ $placeholders = implode( ',', array_fill( 0, count( $ids_arr_int ), '%d' ) );
+ $prepared_sql = $wpdb->prepare(
+ "SELECT DISTINCT people.wp_id FROM $bookings_table AS bookings LEFT JOIN $people_table AS people ON bookings.person_id=people.person_id WHERE bookings.status IN (%d,%d,%d) AND bookings.event_id IN ($placeholders) AND people.wp_id != 0",
+ EME_RSVP_STATUS_PENDING, EME_RSVP_STATUS_USERPENDING, EME_RSVP_STATUS_APPROVED, ...$ids_arr_int
+ ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+ } else {
+ $prepared_sql = $wpdb->prepare( "SELECT DISTINCT people.wp_id FROM $bookings_table AS bookings LEFT JOIN $people_table AS people ON bookings.person_id=people.person_id WHERE bookings.status IN (%d,%d,%d) AND bookings.event_id = %d AND people.wp_id != 0", EME_RSVP_STATUS_PENDING, EME_RSVP_STATUS_USERPENDING, EME_RSVP_STATUS_APPROVED, $event_id ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+ }
return $wpdb->get_col( $prepared_sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
}
@@ -3420,6 +3411,23 @@
return $attendees;
}
+function eme_get_attendee_emails( $event_id ) {
+ global $wpdb;
+ $bookings_table = EME_DB_PREFIX . EME_BOOKINGS_TBNAME;
+ $people_table = EME_DB_PREFIX . EME_PEOPLE_TBNAME;
+ if ( is_array( $event_id ) && eme_is_numeric_array( $event_id ) ) {
+ $ids_arr_int = array_map( 'intval', $event_id );
+ $placeholders = implode( ',', array_fill( 0, count( $ids_arr_int ), '%d' ) );
+ $sql = $wpdb->prepare(
+ "SELECT DISTINCT people.email FROM $bookings_table AS bookings LEFT JOIN $people_table AS people ON bookings.person_id=people.person_id WHERE bookings.event_id IN ($placeholders) AND bookings.person_id>0 AND people.email != ''",
+ ...$ids_arr_int
+ ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+ } else {
+ $sql = $wpdb->prepare( "SELECT DISTINCT people.email FROM $bookings_table AS bookings LEFT JOIN $people_table AS people ON bookings.person_id=people.person_id WHERE bookings.event_id = %d AND bookings.person_id>0 AND people.email != ''", $event_id ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+ }
+ return $wpdb->get_col( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
+}
+
// for backwards compat
function eme_get_attendees_list_for( $event, $template_id = 0, $template_id_header = 0, $template_id_footer = 0, $rsvp_status = 0, $paid_status = 0, $order = '' ) {
return eme_get_attendees_list( $event, $template_id, $template_id_header, $template_id_footer, $rsvp_status, $paid_status, $order );
--- a/events-made-easy/includes/eme-tasks.php
+++ b/events-made-easy/includes/eme-tasks.php
@@ -387,6 +387,13 @@
return $wpdb->get_var( $prepared_sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
}
+function eme_count_tasksignups_for( $event_id ) {
+ global $wpdb;
+ $table = EME_DB_PREFIX . EME_TASK_SIGNUPS_TBNAME;
+ $prepared_sql = $wpdb->prepare( "SELECT COUNT(*) FROM $table WHERE event_id=%d", $event_id ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+ return $wpdb->get_var( $prepared_sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
+}
+
function eme_count_task_approved_signups( $task_id ) {
global $wpdb;
$table = EME_DB_PREFIX . EME_TASK_SIGNUPS_TBNAME;
@@ -463,7 +470,7 @@
}
}
-function eme_count_event_task_signups( $event_id ) {
+function eme_count_event_task_signups_per_task( $event_id ) {
global $wpdb;
$table = EME_DB_PREFIX . EME_TASK_SIGNUPS_TBNAME;
$prepared_sql = $wpdb->prepare( "SELECT task_id, COUNT(*) as signup_count FROM $table WHERE event_id=%d GROUP BY task_id", $event_id ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
@@ -1892,12 +1899,12 @@
}
}
// the next is an array with as key the task id and value the number of signups for it
- $event_task_count_signups = eme_count_event_task_signups( $event_id );
+ $event_count_signups_per_task = eme_count_event_task_signups_per_task( $event_id );
foreach ( $task_id_arr as $task_id ) {
$task_id = intval( $task_id );
$task = eme_get_task( $task_id );
// if full, continue
- if ( isset( $event_task_count_signups[ $task_id ] ) && $event_task_count_signups[ $task_id ] >= $task['spaces'] ) {
+ if ( isset( $event_count_signups_per_task[ $task_id ] ) && $event_count_signups_per_task[ $task_id ] >= $task['spaces'] ) {
$message .= __( 'No more open spaces for this task', 'events-made-easy' );
$message .= '<br>';
$nok = 1;
--- a/events-made-easy/langs/events-made-easy-fr_FR.l10n.php
+++ b/events-made-easy/langs/events-made-easy-fr_FR.l10n.php
@@ -1,4 +1,4 @@
<?php
-return ['domain'=>NULL,'plural-forms'=>'nplurals=2; plural=n > 1;','language'=>'fr','project-id-version'=>'Plugins - Events Made Easy - Stable (latest release)','pot-creation-date'=>'2026-06-23T08:24:23+00:00','po-revision-date'=>'2026-06-21 15:11:02+0000','x-generator'=>'GlotPress/4.0.3','messages'=>['See <a target="_blank" rel="noopener noreferrer" href="%s">this page</a> for info on what you can enter here.'=>'Consultez <a target="_blank" rel="noopener noreferrer" href="%s">cette page</a> pour savoir ce que vous pouvez saisir ici.','For all information concerning frontend submit, see <a target='_blank' rel='noopener noreferrer' href='%s'>the documentation</a>'=>'Pour toute information concernant la soumission à partir de l‘interface publique, voir <a target='_blank' rel='noopener noreferrer' href='%s'>la documentation</a>','For all placeholders you can use here, see <a target='_blank' rel='noopener noreferrer' href='%s'>the documentation</a>'=>'Pour tous les espaces réservés que vous pouvez utiliser ici, voir <a target='_blank' rel='noopener noreferrer' href='%s'>la documentation</a>','Score threshold below which submissions are rejected'=>'Seuil de note en dessous duquel les entrées sont rejetées','Google reCAPTCHA minimum score (0.0–1.0, default 0.5)'=>'Score minimum requis pour Google reCAPTCHA (0,0–1,0, par défaut : 0,5)','Your Google cloud project ID'=>'Votre ID de projet Google Cloud','Google reCAPTCHA Cloud project ID'=>'ID du projet Google reCAPTCHA Cloud','Go to https://console.cloud.google.com/apis/credentials to create a reCAPTCHA API key for your project'=>'Rendez-vous sur https://console.cloud.google.com/apis/credentials pour créer une clé API reCAPTCHA pour votre projet','Google reCAPTCHA API key (Cloud project)'=>'Clé API Google reCAPTCHA (projet Cloud)','Go to https://console.cloud.google.com/security/recaptcha to get your reCAPTCHA key'=>'Rendez-vous sur https://console.cloud.google.com/security/recaptcha pour obtenir votre clé reCAPTCHA','The conflicting page can be edited <a href="%s" target="_blank" rel="noopener noreferrer">here</a>.'=>'La page en conflit peut être modifiée <a href="%s" target="_blank" rel="noopener noreferrer">ici</a>.','Select'=>'Sélectionner','Confirm delete'=>'Confirmer la suppression','Occurrence deleted and remaining occurrences renumbered.'=>'L'occurrence a été supprimée et les occurrences restantes ont été renumérotées.','Invalid parameters'=>'Paramètres invalides','No permission'=>'Aucun droit','Delete this group'=>'Supprimer ce groupe','Are you sure you want to delete this group? This cannot be undone.'=>'Confirmez-vous vouloir supprimer ce groupe ? Cette action ne peut pas être annulée.','RSVP and generic fields'=>'Champs « RSVP » et champs génériques','Membership fields'=>'Champs relatifs à l'adhésion','Member fields'=>'Champs des membres','Location fields'=>'Champs de localisation','Check this option if you want to use Cloudflare Turnstile on the booking/cancel/membership forms, to thwart spammers a bit. You can then either add #_CAPTCHA to your form layout yourself or it will automatically added just above the submit button if not present.'=>'Cochez cette option si vous souhaitez utiliser le Turnstile de Cloudflare sur les formulaires de réservation/annulation/adhésion, afin de contrer un peu les spammeurs. Vous pouvez alors ajouter vous-même #_CAPTCHA à la mise en page de votre formulaire ou il sera automatiquement ajouté juste au-dessus du bouton d'envoi s'il n'est pas présent.','Check this option if you want to use hCaptcha on the booking/cancel/membership forms, to thwart spammers a bit. You can then either add #_CAPTCHA to your form layout yourself or it will automatically added just above the submit button if not present.'=>'Cochez cette option si vous souhaitez utiliser hCaptcha sur les formulaires de réservation/annulation/adhésion, afin de déjouer un peu les spammeurs. Vous pouvez alors ajouter vous-même #_CAPTCHA à la mise en page de votre formulaire ou il sera automatiquement ajouté juste au-dessus du bouton d'envoi s'il n'est pas présent.','Send emails to members upon changes being made?'=>'Envoyer des e-mails aux membres lors de modifications ?','Invalid column name!'=>'Nom de colonne invalide !','The body of the email that will be sent to the contact person when a booking is made.'=>'Le corps de l’e-mail qui sera envoyé à la personne de contact lorsqu’une réservation est faite.','The subject of the email that will be sent to the contact person when a booking is made.'=>'L'objet de l'e-mail qui sera envoyé à la personne de contact quand une réservation est faite.','If pressing Save does not seem to be doing anything, then check all other tabs to make sure all required fields are filled out.'=>'Si le fait d'appuyer sur Enregistrer ne semble rien donner, vérifiez alors tous les autres onglets pour vous assurer que tous les champs obligatoires sont remplis.','Task Signup Pending Email'=>'E-mail d’inscription à une tâche, en attente','Booking cancelled and refunded'=>'Réservation annulée et remboursée','Todo reminder for event #_EVENTNAME: '=>'Rappel de tâche pour l'événement #_EVENTNAME : ','Add new todo'=>'Ajouter une nouvelle tâche','Event start offset (days)'=>'Décalage par rapport à la date de début de l’événement (jours)','When the template is being used as an attacment in a mail, the attachment has a default name. If you don't like the name given to the attachment in the mail, you can change it here. Relevant placeholders are allowed in their context (event/membership/booking/member/...). The '.pdf' extension will get added automatically, so no need to mention it.'=>'Lorsque le modèle est utilisé comme pièce jointe dans un e-mail, la pièce jointe porte un nom par défaut. Si vous n'aimez pas le nom donné à la pièce jointe dans l’e-mail, vous pouvez le modifier ici. Les espaces réservés pertinents sont autorisés dans leur contexte (événement/adhésion/réservation/membre/...). L'extension '.pdf' sera ajoutée automatiquement, il n'est donc pas nécessaire de la mentionner.','PDF mail attach format'=>'Format PDF de la pièce jointe','Newlines will get translated to HTML br-tags when/where/if appropriate but for templates that will get used in HTML output: be sure to not include newlines (certainly not empty lines) unless wanted.'=>'Les nouvelles lignes seront traduites en balises HTML br quand/où/si cela est approprié, mais pour les modèles qui seront utilisés dans la sortie HTML : assurez-vous de ne pas inclure de nouvelles lignes (et certainement pas des lignes vides) à moins que cela ne soit nécessaire.','Filter content'=>'Contenu du filtre','Task related mail'=>'E-mail lié aux tâches','Task form'=>'Formulaire de tâches','Signup failed'=>'L'inscription a échoué','Only one signup allowed'=>'Une seule inscription autorisée','Allow only one sign up for tasks per event for a person?'=>'N'autoriser qu'une seule inscription à des tâches par événement pour une personne ?','Consider pending (unapproved) task signups as available for new signups'=>'Considérer les inscriptions à des tâches en attente (non approuvées) comme disponibles pour les nouvelles inscriptions','Require approval for task signups?'=>'Exiger une approbation pour les inscriptions aux tâches ?','If the number of spaces for a task is 0, the task description will be treated as a section header for the next set of tasks.'=>'Si le nombre de places pour une tâche est de 0, la description de la tâche sera traitée comme un en-tête de section pour la prochaine série de tâches.','%d person already signed up for this task'=>'%d personne s'est déjà inscrite pour cette tâche' . "