My Calendar - Version 3.3.9

Version Description

  • Feature: Ability to merge duplicate locations.
  • Bug fix: New locations created with events were not properly saved with the event, leading to possible location duplications.
  • Bug fix: Add location to table should not be checked when copying an event.
  • Bug fix: Possible fix to meta permissions.
  • Bug fix: Fall back to non-fulltext queries if search term below length limit.
  • Bug fix: 'search' nav item not rendering.
Download this release

Release Info

Developer joedolson
Plugin Icon 128x128 My Calendar
Version 3.3.9
Comparing to
See all releases

Code changes from version 3.3.8 to 3.3.9

css/mc-styles.css CHANGED
@@ -960,6 +960,26 @@ input[name="mc_uri"] {
960
  text-align: center;
961
  }
962
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
963
  .my-calendar-admin .mc-clear-filters {
964
  position: absolute;
965
  top: -3em;
960
  text-align: center;
961
  }
962
 
963
+ .my-calendar-admin .mass-replace-wrap {
964
+ position: relative;
965
+ }
966
+
967
+ .my-calendar-admin .mass-replace-container {
968
+ display: none;
969
+ position: absolute;
970
+ left: 1rem;
971
+ top: 2rem;
972
+ background: #fff;
973
+ padding: 1rem;
974
+ box-shadow: 3px 3px 3px #c3c4c7;
975
+ border: 1px solid #c3c4c7;
976
+ width: 13em;
977
+ }
978
+
979
+ .my-calendar-admin .mass-replace-container label {
980
+ display: block;
981
+ }
982
+
983
  .my-calendar-admin .mc-clear-filters {
984
  position: absolute;
985
  top: -3em;
includes/screen-options.php CHANGED
@@ -176,3 +176,19 @@ function mc_add_help_tab() {
176
  )
177
  );
178
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  )
177
  );
178
  }
179
+
180
+ /**
181
+ * Add help tab on events.
182
+ */
183
+ function mc_location_help_tab() {
184
+ $screen = get_current_screen();
185
+ $content = '<ul><li>' . __( 'Check the locations you wish to remove', 'my-calendar' ) . '</li><li>' . __( 'Check the "Merge Duplicates" option at the top of the table', 'my-calendar' ) . '</li><li>' . __( 'Enter the ID of the location you want to have replace these locations', 'my-calendar' ) . '</li><li>' . __( 'Submit the form', 'my-calendar' ) . '</li></ul><p>' . __( 'The checked locations will be deleted. Events using those locations will be updated to use the provided ID', 'my-calendar' ) . '</p>';
186
+
187
+ $screen->add_help_tab(
188
+ array(
189
+ 'id' => 'mc_location_help_tab',
190
+ 'title' => __( 'Merging Duplicate Locations', 'my-calendar' ),
191
+ 'content' => $content,
192
+ )
193
+ );
194
+ }
js/jquery.admin.js CHANGED
@@ -229,6 +229,16 @@ jQuery(document).ready(function ($) {
229
  $( '.event_span' ).hide();
230
  $( '.mc-actions input[type="submit"]' ).attr( 'disabled', 'disabled' );
231
 
 
 
 
 
 
 
 
 
 
 
232
  $( '.selectall' ).on( 'click', function() {
233
  var checked_status = $(this).prop('checked');
234
  if ( checked_status ) {
@@ -262,7 +272,7 @@ jQuery(document).ready(function ($) {
262
  $( '#mc_bulk_actions' ).val( value );
263
  });
264
 
265
- $( '#my-calendar-admin-table input, .mc-actions input' ).on( 'change', function (e) {
266
  var checked_status = $(this).prop('checked');
267
  var groups_table = $(this).parents( 'table' ).hasClass( 'mc-groups-table' );
268
  var checkboxes = $( '#my-calendar-admin-table input:checked' );
@@ -270,10 +280,12 @@ jQuery(document).ready(function ($) {
270
  if ( checked_status ) {
271
  if ( ( groups_table && checked > 1 ) || ! groups_table ) {
272
  $( '.mc-actions input[type="submit"]' ).removeAttr( 'disabled' );
 
273
  }
274
  } else {
275
  if ( ( groups_table && checked < 2 ) || ( ! groups_table && checked == 0 ) ) {
276
  $( '.mc-actions input[type="submit"]' ).attr( 'disabled', 'disabled' );
 
277
  }
278
  }
279
  });
229
  $( '.event_span' ).hide();
230
  $( '.mc-actions input[type="submit"]' ).attr( 'disabled', 'disabled' );
231
 
232
+ $( '#mass_replace_on' ).on( 'click', function() {
233
+ var checked_status = $(this).prop('checked');
234
+ if ( checked_status ) {
235
+ // Activate actions on bulk checked.
236
+ $( '.mass-replace-container' ).show();
237
+ } else {
238
+ $( '.mass-replace-container' ).hide();
239
+ }
240
+ });
241
+
242
  $( '.selectall' ).on( 'click', function() {
243
  var checked_status = $(this).prop('checked');
244
  if ( checked_status ) {
272
  $( '#mc_bulk_actions' ).val( value );
273
  });
274
 
275
+ $( '#my-calendar-admin-table input, .mc-actions input:not(#mass_replace_on)' ).on( 'change', function (e) {
276
  var checked_status = $(this).prop('checked');
277
  var groups_table = $(this).parents( 'table' ).hasClass( 'mc-groups-table' );
278
  var checkboxes = $( '#my-calendar-admin-table input:checked' );
280
  if ( checked_status ) {
281
  if ( ( groups_table && checked > 1 ) || ! groups_table ) {
282
  $( '.mc-actions input[type="submit"]' ).removeAttr( 'disabled' );
283
+ $( '.mc-actions #mass_replace_on' ).removeAttr( 'disabled' );
284
  }
285
  } else {
286
  if ( ( groups_table && checked < 2 ) || ( ! groups_table && checked == 0 ) ) {
287
  $( '.mc-actions input[type="submit"]' ).attr( 'disabled', 'disabled' );
288
+ $( '.mc-actions #mass_replace_on' ).attr( 'disabled', 'disabled' );
289
  }
290
  }
291
  });
my-calendar-core.php CHANGED
@@ -2190,7 +2190,7 @@ function mc_register_meta() {
2190
  'page',
2191
  '_mc_calendar',
2192
  array(
2193
- 'show_in_rest' => array(
2194
  'schema' => array(
2195
  'type' => 'object',
2196
  'properties' => array(
@@ -2206,9 +2206,17 @@ function mc_register_meta() {
2206
  ),
2207
  ),
2208
  ),
2209
- 'single' => true,
2210
- 'type' => 'array',
 
2211
  )
2212
  );
2213
  }
2214
  add_action( 'init', 'mc_register_meta' );
 
 
 
 
 
 
 
2190
  'page',
2191
  '_mc_calendar',
2192
  array(
2193
+ 'show_in_rest' => array(
2194
  'schema' => array(
2195
  'type' => 'object',
2196
  'properties' => array(
2206
  ),
2207
  ),
2208
  ),
2209
+ 'single' => true,
2210
+ 'type' => 'array',
2211
+ 'auth_callback' => 'mc_can_update_meta',
2212
  )
2213
  );
2214
  }
2215
  add_action( 'init', 'mc_register_meta' );
2216
+
2217
+ /**
2218
+ * Verify if a user can edit meta fields.
2219
+ */
2220
+ function mc_can_update_meta() {
2221
+ return current_user_can( 'edit_posts' );
2222
+ }
my-calendar-event-editor.php CHANGED
@@ -158,7 +158,7 @@ function mc_add_post_meta_data( $post_id, $post, $data, $event_id ) {
158
  $event_date = ( is_array( $data['event_begin'] ) ) ? $data['event_begin'][0] : $data['event_begin'];
159
  update_post_meta( $post_id, '_mc_event_date', strtotime( $event_date ) );
160
  }
161
- $location_id = ( isset( $post['location_preset'] ) ) ? (int) $post['location_preset'] : false;
162
  if ( $location_id ) { // only change location ID if dropdown set.
163
  update_post_meta( $post_id, '_mc_event_location', $location_id );
164
  mc_update_event( 'event_location', $location_id, $event_id );
@@ -189,16 +189,15 @@ function mc_create_event_post( $data, $event_id ) {
189
  }
190
  $terms[] = (int) $term;
191
  }
192
-
193
  $title = $data['event_title'];
194
  $template = apply_filters( 'mc_post_template', 'details', $term );
195
  $data['shortcode'] = "[my_calendar_event event='$event_id' template='$template' list='']";
196
  $description = isset( $data['event_desc'] ) ? $data['event_desc'] : '';
197
  $excerpt = isset( $data['event_short'] ) ? $data['event_short'] : '';
198
- $location_id = 0;
199
- if ( isset( $_POST['location_preset'] ) ) {
200
  $location_id = (int) $_POST['location_preset'];
201
- } elseif ( isset( $data['location_preset'] ) ) {
202
  $location_id = $data['location_preset'];
203
  }
204
  $post_status = $privacy;
@@ -393,7 +392,7 @@ function my_calendar_save( $action, $output, $event_id = false ) {
393
  global $wpdb;
394
  $proceed = (bool) $output[0];
395
  $message = '';
396
- $formats = array( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%f', '%f' );
397
 
398
  if ( ( 'add' === $action || 'copy' === $action ) && true === $proceed ) {
399
  $add = $output[2]; // add format here.
@@ -774,7 +773,7 @@ function mc_edit_block_is_visible( $field ) {
774
  } else {
775
  if ( 'off' === $input[ $field ] || '' === $input[ $field ] ) {
776
  return false;
777
- } elseif ( 'off' === $show[ $field ] ) {
778
  return false;
779
  } else {
780
  return true;
@@ -1731,6 +1730,7 @@ function mc_check_data( $action, $post, $i, $ignore_required = false ) {
1731
  $event_hide_end = '';
1732
  $event_longitude = '';
1733
  $event_latitude = '';
 
1734
 
1735
  if ( version_compare( PHP_VERSION, '7.4', '<' ) && get_magic_quotes_gpc() ) {
1736
  $post = array_map( 'stripslashes_deep', $post );
@@ -1897,12 +1897,12 @@ function mc_check_data( $action, $post, $i, $ignore_required = false ) {
1897
  $event_access = ! empty( $post['event_access_hidden'] ) ? unserialize( $post['event_access_hidden'] ) : $event_access;
1898
  $has_location_data = false;
1899
 
1900
- if ( '' !== trim( $event_label . $event_street . $event_street2 . $event_city . $event_state . $event_postcode . $event_region . $event_country . $event_url . $event_longitude . $event_latitude . $event_zoom . $event_phone . $event_phone2 . $event_access ) ) {
1901
  $has_location_data = true;
1902
  }
1903
  if ( $has_location_data && isset( $post['mc_copy_location'] ) && 'on' === $post['mc_copy_location'] && 0 === $i ) {
1904
  // Only add this with the first event, if adding multiples.
1905
- $add_loc = array(
1906
  'location_label' => $event_label,
1907
  'location_street' => $event_street,
1908
  'location_street2' => $event_street2,
@@ -1919,7 +1919,8 @@ function mc_check_data( $action, $post, $i, $ignore_required = false ) {
1919
  'location_phone2' => $event_phone2,
1920
  'location_access' => ( is_array( $event_access ) ) ? serialize( $event_access ) : '',
1921
  );
1922
- $loc_id = mc_insert_location( $add_loc );
 
1923
  do_action( 'mc_save_location', $loc_id, $add_loc, $add_loc );
1924
  }
1925
  }
@@ -2049,6 +2050,7 @@ function mc_check_data( $action, $post, $i, $ignore_required = false ) {
2049
  'event_group_id' => $event_group_id,
2050
  'event_span' => $event_span,
2051
  'event_hide_end' => $event_hide_end,
 
2052
  // Begin floats.
2053
  'event_longitude' => $event_longitude,
2054
  'event_latitude' => $event_latitude,
158
  $event_date = ( is_array( $data['event_begin'] ) ) ? $data['event_begin'][0] : $data['event_begin'];
159
  update_post_meta( $post_id, '_mc_event_date', strtotime( $event_date ) );
160
  }
161
+ $location_id = ( isset( $post['location_preset'] ) && is_numeric( $post['location_preset'] ) ) ? (int) $post['location_preset'] : false;
162
  if ( $location_id ) { // only change location ID if dropdown set.
163
  update_post_meta( $post_id, '_mc_event_location', $location_id );
164
  mc_update_event( 'event_location', $location_id, $event_id );
189
  }
190
  $terms[] = (int) $term;
191
  }
 
192
  $title = $data['event_title'];
193
  $template = apply_filters( 'mc_post_template', 'details', $term );
194
  $data['shortcode'] = "[my_calendar_event event='$event_id' template='$template' list='']";
195
  $description = isset( $data['event_desc'] ) ? $data['event_desc'] : '';
196
  $excerpt = isset( $data['event_short'] ) ? $data['event_short'] : '';
197
+ $location_id = isset( $data['event_location'] ) ? $data['event_location'] : 0;
198
+ if ( isset( $_POST['location_preset'] ) && is_numeric( $_POST['location_preset'] ) ) {
199
  $location_id = (int) $_POST['location_preset'];
200
+ } elseif ( isset( $data['location_preset'] ) && is_numeric( $data['location_preset'] ) ) {
201
  $location_id = $data['location_preset'];
202
  }
203
  $post_status = $privacy;
392
  global $wpdb;
393
  $proceed = (bool) $output[0];
394
  $message = '';
395
+ $formats = array( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%f', '%f' );
396
 
397
  if ( ( 'add' === $action || 'copy' === $action ) && true === $proceed ) {
398
  $add = $output[2]; // add format here.
773
  } else {
774
  if ( 'off' === $input[ $field ] || '' === $input[ $field ] ) {
775
  return false;
776
+ } elseif ( isset( $show[ $field ] ) && 'off' === $show[ $field ] ) {
777
  return false;
778
  } else {
779
  return true;
1730
  $event_hide_end = '';
1731
  $event_longitude = '';
1732
  $event_latitude = '';
1733
+ $event_location = '';
1734
 
1735
  if ( version_compare( PHP_VERSION, '7.4', '<' ) && get_magic_quotes_gpc() ) {
1736
  $post = array_map( 'stripslashes_deep', $post );
1897
  $event_access = ! empty( $post['event_access_hidden'] ) ? unserialize( $post['event_access_hidden'] ) : $event_access;
1898
  $has_location_data = false;
1899
 
1900
+ if ( '' !== trim( $event_label . $event_street . $event_street2 . $event_city . $event_state . $event_postcode . $event_region . $event_country . $event_url . $event_longitude . $event_latitude . $event_zoom . $event_phone . $event_phone2 ) ) {
1901
  $has_location_data = true;
1902
  }
1903
  if ( $has_location_data && isset( $post['mc_copy_location'] ) && 'on' === $post['mc_copy_location'] && 0 === $i ) {
1904
  // Only add this with the first event, if adding multiples.
1905
+ $add_loc = array(
1906
  'location_label' => $event_label,
1907
  'location_street' => $event_street,
1908
  'location_street2' => $event_street2,
1919
  'location_phone2' => $event_phone2,
1920
  'location_access' => ( is_array( $event_access ) ) ? serialize( $event_access ) : '',
1921
  );
1922
+ $loc_id = mc_insert_location( $add_loc );
1923
+ $event_location = $loc_id;
1924
  do_action( 'mc_save_location', $loc_id, $add_loc, $add_loc );
1925
  }
1926
  }
2050
  'event_group_id' => $event_group_id,
2051
  'event_span' => $event_span,
2052
  'event_hide_end' => $event_hide_end,
2053
+ 'event_location' => $event_location,
2054
  // Begin floats.
2055
  'event_longitude' => $event_longitude,
2056
  'event_latitude' => $event_latitude,
my-calendar-limits.php CHANGED
@@ -23,10 +23,11 @@ if ( ! defined( 'ABSPATH' ) ) {
23
  function mc_prepare_search_query( $query ) {
24
  global $wpdb;
25
  $db_type = mc_get_db_type();
 
26
  $search = '';
27
  if ( '' !== trim( $query ) ) {
28
  $query = esc_sql( urldecode( urldecode( $query ) ) );
29
- if ( 'MyISAM' === $db_type ) {
30
  $search = ' AND MATCH(' . apply_filters( 'mc_search_fields', 'event_title,event_desc,event_short,event_label,event_city,event_postcode,event_registration' ) . ") AGAINST ( '$query' IN BOOLEAN MODE ) ";
31
  } else {
32
  $search = " AND ( event_title LIKE '%$query%' OR event_desc LIKE '%$query%' OR event_short LIKE '%$query%' OR event_label LIKE '%$query%' OR event_city LIKE '%$query%' OR event_postcode LIKE '%$query%' OR event_registration LIKE '%$query%' ) ";
23
  function mc_prepare_search_query( $query ) {
24
  global $wpdb;
25
  $db_type = mc_get_db_type();
26
+ $length = strlen( $query );
27
  $search = '';
28
  if ( '' !== trim( $query ) ) {
29
  $query = esc_sql( urldecode( urldecode( $query ) ) );
30
+ if ( 'MyISAM' === $db_type && $length > 3 ) {
31
  $search = ' AND MATCH(' . apply_filters( 'mc_search_fields', 'event_title,event_desc,event_short,event_label,event_city,event_postcode,event_registration' ) . ") AGAINST ( '$query' IN BOOLEAN MODE ) ";
32
  } else {
33
  $search = " AND ( event_title LIKE '%$query%' OR event_desc LIKE '%$query%' OR event_short LIKE '%$query%' OR event_label LIKE '%$query%' OR event_city LIKE '%$query%' OR event_postcode LIKE '%$query%' OR event_registration LIKE '%$query%' ) ";
my-calendar-location-manager.php CHANGED
@@ -23,6 +23,7 @@ function my_calendar_manage_locations() {
23
  my_calendar_check_db();
24
  // We do some checking to see what we're doing.
25
  mc_mass_delete_locations();
 
26
  if ( ! empty( $_POST ) && ( ! isset( $_POST['mc_locations'] ) && ! isset( $_POST['mass_delete'] ) ) ) {
27
  $nonce = $_REQUEST['_wpnonce'];
28
  if ( ! wp_verify_nonce( $nonce, 'my-calendar-nonce' ) ) {
@@ -95,6 +96,64 @@ function mc_default_location() {
95
  return $output;
96
  }
97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  /**
99
  * Mass delete locations.
100
  *
@@ -112,20 +171,21 @@ function mc_mass_delete_locations() {
112
  $i = 0;
113
  $total = 0;
114
  $deleted = array();
115
- $prepare = array();
116
  foreach ( $locations as $value ) {
117
- $total = count( $locations );
118
- $deleted[] = absint( $value );
119
- $prepare[] = '%d';
 
 
 
 
120
  $i ++;
121
  }
122
- $prepared = implode( ',', $prepare );
123
- $result = $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . my_calendar_locations_table() . " WHERE location_id IN ($prepared)", $deleted ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
124
- if ( 0 !== $result && false !== $result ) {
125
  // Argument: array of event IDs.
126
- do_action( 'mc_mass_delete_locations', $deleted );
127
  // Translators: Number of locations deleted, number selected.
128
- $message = mc_show_notice( sprintf( __( '%1$d locations deleted successfully out of %2$d selected', 'my-calendar' ), $i, $total ), false );
129
  } else {
130
  $message = mc_show_error( __( 'Your locations have not been deleted. Please investigate.', 'my-calendar' ), false );
131
  }
@@ -180,9 +240,10 @@ function mc_manage_locations() {
180
  $current = empty( $_GET['paged'] ) ? 1 : intval( $_GET['paged'] );
181
  if ( isset( $_POST['mcl'] ) ) {
182
  $query = esc_sql( $_POST['mcl'] );
 
183
  $db_type = mc_get_db_type();
184
  if ( '' !== $query ) {
185
- if ( 'MyISAM' === $db_type ) {
186
  $search = ' WHERE MATCH(' . apply_filters( 'mc_search_fields', 'location_label,location_city,location_state,location_region,location_country,location_street,location_street2,location_phone' ) . ") AGAINST ( '$query' IN BOOLEAN MODE ) ";
187
  } else {
188
  $search = " WHERE location_label LIKE '%$query%' OR location_city LIKE '%$query%' OR location_state LIKE '%$query%' OR location_region LIKE '%$query%' OR location_country LIKE '%$query%' OR location_street LIKE '%$query%' OR location_street2 LIKE '%$query%' OR location_phone LIKE '%$query%' ";
@@ -235,6 +296,13 @@ function mc_manage_locations() {
235
  <div><input type="hidden" name="_wpnonce" value="<?php echo wp_create_nonce( 'my-calendar-nonce' ); ?>"/></div>
236
  <div class='mc-actions'>
237
  <input type="submit" class="button-secondary delete" name="mass_delete" value="<?php esc_attr_e( 'Delete locations', 'my-calendar' ); ?>" />
 
 
 
 
 
 
 
238
  <div><input type='checkbox' class='selectall' id='mass_edit' data-action="mass_edit" /> <label for='mass_edit'><?php esc_html_e( 'Check all', 'my-calendar' ); ?></label></div>
239
  </div>
240
  <table class="widefat striped page" id="my-calendar-admin-table">
23
  my_calendar_check_db();
24
  // We do some checking to see what we're doing.
25
  mc_mass_delete_locations();
26
+ mc_clean_duplicate_locations();
27
  if ( ! empty( $_POST ) && ( ! isset( $_POST['mc_locations'] ) && ! isset( $_POST['mass_delete'] ) ) ) {
28
  $nonce = $_REQUEST['_wpnonce'];
29
  if ( ! wp_verify_nonce( $nonce, 'my-calendar-nonce' ) ) {
96
  return $output;
97
  }
98
 
99
+
100
+ /**
101
+ * Mass replace locations.
102
+ *
103
+ * @return mixed boolean/int query result.
104
+ */
105
+ function mc_clean_duplicate_locations() {
106
+ global $wpdb;
107
+ // Mass delete locations.
108
+ if ( ! empty( $_POST['mass_edit'] ) && isset( $_POST['mass_replace'] ) ) {
109
+ $nonce = $_REQUEST['_wpnonce'];
110
+ if ( ! wp_verify_nonce( $nonce, 'my-calendar-nonce' ) ) {
111
+ die( 'Security check failed' );
112
+ }
113
+ $locations = $_POST['mass_edit'];
114
+ $replace = $_POST['mass_replace_id'];
115
+ $location = mc_get_location( $replace );
116
+ if ( ! $location ) {
117
+ // If this isn't a valid location, don't continue.
118
+ echo mc_show_error( __( 'An invalid ID was provided for the replacement location.', 'my-calendar' ) );
119
+ return;
120
+ }
121
+ $i = 0;
122
+ $total = 0;
123
+ $deleted = array();
124
+ foreach ( $locations as $value ) {
125
+ $total = count( $locations );
126
+ $result = mc_delete_location( $value, 'bool' );
127
+ if ( ! $result ) {
128
+ $failed[] = absint( $value );
129
+ } else {
130
+ $deleted[] = absint( $value );
131
+ }
132
+ $change = $wpdb->update(
133
+ my_calendar_table(),
134
+ array(
135
+ 'event_location' => $replace,
136
+ ),
137
+ array(
138
+ 'event_location' => $value,
139
+ ),
140
+ '%d',
141
+ '%d'
142
+ );
143
+ $i ++;
144
+ }
145
+ if ( ! empty( $deleted ) ) {
146
+ // Argument: array of event IDs.
147
+ do_action( 'mc_clean_duplicate_locations', $deleted );
148
+ // Translators: Number of locations deleted, number selected.
149
+ $message = mc_show_notice( sprintf( __( '%1$d locations deleted successfully out of %2$d selected', 'my-calendar' ), count( $deleted ), $total ), false );
150
+ } else {
151
+ $message = mc_show_error( __( 'Your locations have not been deleted. Please investigate.', 'my-calendar' ), false );
152
+ }
153
+ echo wp_kses_post( $message );
154
+ }
155
+ }
156
+
157
  /**
158
  * Mass delete locations.
159
  *
171
  $i = 0;
172
  $total = 0;
173
  $deleted = array();
 
174
  foreach ( $locations as $value ) {
175
+ $total = count( $locations );
176
+ $result = mc_delete_location( $value, 'bool' );
177
+ if ( ! $result ) {
178
+ $failed[] = absint( $value );
179
+ } else {
180
+ $deleted[] = absint( $value );
181
+ }
182
  $i ++;
183
  }
184
+ if ( ! empty( $deleted ) ) {
 
 
185
  // Argument: array of event IDs.
186
+ do_action( 'mc_mass_delete_locations', $deleted, $failed );
187
  // Translators: Number of locations deleted, number selected.
188
+ $message = mc_show_notice( sprintf( __( '%1$d locations deleted successfully out of %2$d selected', 'my-calendar' ), count( $deleted ), $total ), false );
189
  } else {
190
  $message = mc_show_error( __( 'Your locations have not been deleted. Please investigate.', 'my-calendar' ), false );
191
  }
240
  $current = empty( $_GET['paged'] ) ? 1 : intval( $_GET['paged'] );
241
  if ( isset( $_POST['mcl'] ) ) {
242
  $query = esc_sql( $_POST['mcl'] );
243
+ $length = strlen( $query );
244
  $db_type = mc_get_db_type();
245
  if ( '' !== $query ) {
246
+ if ( 'MyISAM' === $db_type && $length > 3 ) {
247
  $search = ' WHERE MATCH(' . apply_filters( 'mc_search_fields', 'location_label,location_city,location_state,location_region,location_country,location_street,location_street2,location_phone' ) . ") AGAINST ( '$query' IN BOOLEAN MODE ) ";
248
  } else {
249
  $search = " WHERE location_label LIKE '%$query%' OR location_city LIKE '%$query%' OR location_state LIKE '%$query%' OR location_region LIKE '%$query%' OR location_country LIKE '%$query%' OR location_street LIKE '%$query%' OR location_street2 LIKE '%$query%' OR location_phone LIKE '%$query%' ";
296
  <div><input type="hidden" name="_wpnonce" value="<?php echo wp_create_nonce( 'my-calendar-nonce' ); ?>"/></div>
297
  <div class='mc-actions'>
298
  <input type="submit" class="button-secondary delete" name="mass_delete" value="<?php esc_attr_e( 'Delete locations', 'my-calendar' ); ?>" />
299
+ <div class="mass-replace-wrap">
300
+ <input type="checkbox" name="mass_replace_on" disabled id="mass_replace_on" value="true"><label for="mass_replace_on"><?php esc_attr_e( 'Merge duplicates', 'my-calendar' ); ?></label>
301
+ <div class="mass-replace-container">
302
+ <label for="mass-replace"><?php _e( 'Replacement ID', 'my-calendar' ); ?></label><input type="text" size="4" id="mass-replace" name="mass_replace_id" class="mass-replace" value="" />
303
+ <input type="submit" class="button-secondary delete" name="mass_replace" value="<?php _e( 'Replace', 'my-calendar' ); ?>" />
304
+ </div>
305
+ </div>
306
  <div><input type='checkbox' class='selectall' id='mass_edit' data-action="mass_edit" /> <label for='mass_edit'><?php esc_html_e( 'Check all', 'my-calendar' ); ?></label></div>
307
  </div>
308
  <table class="widefat striped page" id="my-calendar-admin-table">
my-calendar-locations.php CHANGED
@@ -312,26 +312,29 @@ function mc_modify_location( $update, $where ) {
312
  /**
313
  * Delete a single location.
314
  *
315
- * @param int $location Location ID.
 
316
  *
317
  * @return string
318
  */
319
- function mc_delete_location( $location ) {
320
  global $wpdb;
321
  $location = (int) ( ( isset( $_GET['location_id'] ) ) ? $_GET['location_id'] : $location );
322
  $results = $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . my_calendar_locations_table() . ' WHERE location_id=%d', $location ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
323
  do_action( 'mc_delete_location', $results, $location );
324
  if ( $results ) {
 
325
  $return = mc_show_notice( __( 'Location deleted successfully', 'my-calendar' ), false );
326
  $default_location = get_option( 'mc_default_location', false );
327
  if ( (int) $default_location === $location ) {
328
  delete_option( 'mc_default_location' );
329
  }
330
  } else {
 
331
  $return = mc_show_error( __( 'Location could not be deleted', 'my-calendar' ), false );
332
  }
333
 
334
- return $return;
335
  }
336
 
337
  /**
@@ -699,7 +702,8 @@ function mc_locations_fields( $has_data, $data, $context = 'location', $group_id
699
  $return = '<div class="mc-locations" id="location-fields">';
700
  if ( current_user_can( 'mc_edit_locations' ) && 'event' === $context ) {
701
  $checked = ' checked="checked"';
702
- if ( $has_data && ! empty( $data->event_location ) ) {
 
703
  $checked = '';
704
  }
705
  $return .= '<p class="checkboxes"><input type="checkbox" value="on" name="mc_copy_location" id="mc_copy_location"' . $checked . ' /> <label for="mc_copy_location">' . __( 'Copy new location into the locations table', 'my-calendar' ) . '</label></p>';
@@ -1193,11 +1197,12 @@ function mc_core_search_locations( $query = '' ) {
1193
  $current = empty( $_GET['paged'] ) ? 1 : intval( $_GET['paged'] );
1194
  $db_type = mc_get_db_type();
1195
  $query = esc_sql( $query );
 
1196
 
1197
  if ( '' !== $query ) {
1198
  // Fulltext is supported in InnoDB since MySQL 5.6; minimum required by WP is 5.0 as of WP 5.5.
1199
  // 37% of installs still below 5.6 as of 11/30/2020.
1200
- if ( 'MyISAM' === $db_type ) {
1201
  $search = ' WHERE MATCH(' . apply_filters( 'mc_search_fields', 'location_label' ) . ") AGAINST ( '$query' IN BOOLEAN MODE ) ";
1202
  } else {
1203
  $search = " WHERE location_label LIKE '%$query%' ";
312
  /**
313
  * Delete a single location.
314
  *
315
+ * @param int $location Location ID.
316
+ * @param string $type Return type.
317
  *
318
  * @return string
319
  */
320
+ function mc_delete_location( $location, $type = 'string' ) {
321
  global $wpdb;
322
  $location = (int) ( ( isset( $_GET['location_id'] ) ) ? $_GET['location_id'] : $location );
323
  $results = $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . my_calendar_locations_table() . ' WHERE location_id=%d', $location ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
324
  do_action( 'mc_delete_location', $results, $location );
325
  if ( $results ) {
326
+ $value = true;
327
  $return = mc_show_notice( __( 'Location deleted successfully', 'my-calendar' ), false );
328
  $default_location = get_option( 'mc_default_location', false );
329
  if ( (int) $default_location === $location ) {
330
  delete_option( 'mc_default_location' );
331
  }
332
  } else {
333
+ $value = false;
334
  $return = mc_show_error( __( 'Location could not be deleted', 'my-calendar' ), false );
335
  }
336
 
337
+ return ( 'string' === $type ) ? $return : $value;
338
  }
339
 
340
  /**
702
  $return = '<div class="mc-locations" id="location-fields">';
703
  if ( current_user_can( 'mc_edit_locations' ) && 'event' === $context ) {
704
  $checked = ' checked="checked"';
705
+ // Default unchecked if event has a location or is being copied.
706
+ if ( ( $has_data && ! empty( $data->event_location ) ) || isset( $_GET['mode'] ) && 'copy' === $_GET['mode'] ) {
707
  $checked = '';
708
  }
709
  $return .= '<p class="checkboxes"><input type="checkbox" value="on" name="mc_copy_location" id="mc_copy_location"' . $checked . ' /> <label for="mc_copy_location">' . __( 'Copy new location into the locations table', 'my-calendar' ) . '</label></p>';
1197
  $current = empty( $_GET['paged'] ) ? 1 : intval( $_GET['paged'] );
1198
  $db_type = mc_get_db_type();
1199
  $query = esc_sql( $query );
1200
+ $length = strlen( $query );
1201
 
1202
  if ( '' !== $query ) {
1203
  // Fulltext is supported in InnoDB since MySQL 5.6; minimum required by WP is 5.0 as of WP 5.5.
1204
  // 37% of installs still below 5.6 as of 11/30/2020.
1205
+ if ( 'MyISAM' === $db_type && $length > 3 ) {
1206
  $search = ' WHERE MATCH(' . apply_filters( 'mc_search_fields', 'location_label' ) . ") AGAINST ( '$query' IN BOOLEAN MODE ) ";
1207
  } else {
1208
  $search = " WHERE location_label LIKE '%$query%' ";
my-calendar-navigation.php CHANGED
@@ -165,7 +165,7 @@ function mc_generate_calendar_nav( $params, $cat, $start_of_week, $show_months,
165
  $access = ( in_array( 'access', $used, true ) ) ? mc_filters( $acc_args, mc_get_current_url() ) : '';
166
 
167
  // Set up search.
168
- $search = ( in_array( 'access', $used, true ) ) ? my_calendar_searchform( 'simple', mc_get_current_url() ) : '';
169
 
170
  // Set up navigation links.
171
  if ( in_array( 'nav', $used, true ) ) {
165
  $access = ( in_array( 'access', $used, true ) ) ? mc_filters( $acc_args, mc_get_current_url() ) : '';
166
 
167
  // Set up search.
168
+ $search = ( in_array( 'search', $used, true ) ) ? my_calendar_searchform( 'simple', mc_get_current_url() ) : '';
169
 
170
  // Set up navigation links.
171
  if ( in_array( 'nav', $used, true ) ) {
my-calendar.php CHANGED
@@ -17,7 +17,7 @@
17
  * License: GPL-2.0+
18
  * License URI: http://www.gnu.org/license/gpl-2.0.txt
19
  * Domain Path: lang
20
- * Version: 3.3.8
21
  */
22
 
23
  /*
@@ -42,7 +42,7 @@ if ( ! defined( 'ABSPATH' ) ) {
42
  }
43
 
44
  global $mc_version, $wpdb;
45
- $mc_version = '3.3.8';
46
 
47
  define( 'MC_DEBUG', false );
48
 
@@ -426,7 +426,8 @@ function my_calendar_menu() {
426
  $page_title = __( 'Add New Location', 'my-calendar' );
427
  }
428
  add_submenu_page( 'my-calendar', $page_title, __( 'Add New Location', 'my-calendar' ), 'mc_edit_locations', 'my-calendar-locations', 'my_calendar_add_locations' );
429
- add_submenu_page( 'my-calendar', __( 'Locations', 'my-calendar' ), __( 'Locations', 'my-calendar' ), 'mc_edit_locations', 'my-calendar-location-manager', 'my_calendar_manage_locations' );
 
430
  add_submenu_page( 'my-calendar', __( 'Categories', 'my-calendar' ), __( 'Categories', 'my-calendar' ), 'mc_edit_cats', 'my-calendar-categories', 'my_calendar_manage_categories' );
431
  }
432
  $permission = 'manage_options';
17
  * License: GPL-2.0+
18
  * License URI: http://www.gnu.org/license/gpl-2.0.txt
19
  * Domain Path: lang
20
+ * Version: 3.3.9
21
  */
22
 
23
  /*
42
  }
43
 
44
  global $mc_version, $wpdb;
45
+ $mc_version = '3.3.9';
46
 
47
  define( 'MC_DEBUG', false );
48
 
426
  $page_title = __( 'Add New Location', 'my-calendar' );
427
  }
428
  add_submenu_page( 'my-calendar', $page_title, __( 'Add New Location', 'my-calendar' ), 'mc_edit_locations', 'my-calendar-locations', 'my_calendar_add_locations' );
429
+ $locations = add_submenu_page( 'my-calendar', __( 'Locations', 'my-calendar' ), __( 'Locations', 'my-calendar' ), 'mc_edit_locations', 'my-calendar-location-manager', 'my_calendar_manage_locations' );
430
+ add_action( "load-$locations", 'mc_location_help_tab' );
431
  add_submenu_page( 'my-calendar', __( 'Categories', 'my-calendar' ), __( 'Categories', 'my-calendar' ), 'mc_edit_cats', 'my-calendar-categories', 'my_calendar_manage_categories' );
432
  }
433
  $permission = 'manage_options';
readme.txt CHANGED
@@ -6,7 +6,7 @@ Requires at least: 4.4
6
  Tested up to: 5.9
7
  Requires PHP: 7.0
8
  Text domain: my-calendar
9
- Stable tag: 3.3.8
10
  License: GPLv2 or later
11
 
12
  Accessible WordPress event calendar plugin. Show events from multiple calendars on pages, in posts, or in widgets.
@@ -84,6 +84,15 @@ Translating my plugins is always appreciated. Visit <a href="https://translate.w
84
 
85
  == Changelog ==
86
 
 
 
 
 
 
 
 
 
 
87
  = 3.3.8 =
88
 
89
  * Bug fix: Generated a duplicate location if event with location unselected location.
6
  Tested up to: 5.9
7
  Requires PHP: 7.0
8
  Text domain: my-calendar
9
+ Stable tag: 3.3.9
10
  License: GPLv2 or later
11
 
12
  Accessible WordPress event calendar plugin. Show events from multiple calendars on pages, in posts, or in widgets.
84
 
85
  == Changelog ==
86
 
87
+ = 3.3.9 =
88
+
89
+ * Feature: Ability to merge duplicate locations.
90
+ * Bug fix: New locations created with events were not properly saved with the event, leading to possible location duplications.
91
+ * Bug fix: Add location to table should not be checked when copying an event.
92
+ * Bug fix: Possible fix to meta permissions.
93
+ * Bug fix: Fall back to non-fulltext queries if search term below length limit.
94
+ * Bug fix: 'search' nav item not rendering.
95
+
96
  = 3.3.8 =
97
 
98
  * Bug fix: Generated a duplicate location if event with location unselected location.