en the date postfix is added, which takes into account multibyte and url-encoded characters and WP unique suffixes. * If a url-encoded slug is nearing 200 characters (the data character limit in the db table), adding a date to the end will cause issues when saving to the db. * This function checks if the final slug is longer than 200 characters and removes one entire character rather than part of a hex-encoded character, until the right size is met. * @param string $post_name * @param string $post_slug_postfix * @return string */ public function sanitize_recurrence_slug( $post_name, $post_slug_postfix ){ if( strlen($post_name.'-'.$post_slug_postfix) > 200 ){ if( preg_match('/^(.+)(\-[0-9]+)$/', $post_name, $post_name_parts) ){ $post_name_decoded = urldecode($post_name_parts[1]); $post_name_suffix = $post_name_parts[2]; }else{ $post_name_decoded = urldecode($post_name); $post_name_suffix = ''; } $post_name_maxlength = 200 - strlen( $post_name_suffix . '-' . $post_slug_postfix); if ( $post_name_parts[0] === $post_name_decoded.$post_name_suffix ){ $post_name = substr( $post_name_decoded, 0, $post_name_maxlength ); }else{ $post_name = utf8_uri_encode( $post_name_decoded, $post_name_maxlength ); } $post_name = rtrim( $post_name, '-' ). $post_name_suffix; } return apply_filters('em_event_sanitize_recurrence_slug', $post_name, $post_slug_postfix, $this); } /** * Removes all recurrences of a recurring event. * @return null */ function delete_events(){ global $wpdb; do_action('em_event_delete_events_pre', $this); //So we don't do something we'll regret later, we could just supply the get directly into the delete, but this is safer $result = false; $events_array = array(); if( $this->can_manage('delete_events', 'delete_others_events') ){ //delete events from em_events table $sql = $wpdb->prepare('SELECT event_id FROM '.EM_EVENTS_TABLE.' WHERE (recurrence!=1 OR recurrence IS NULL) AND recurrence_id=%d', $this->event_id); $event_ids = $wpdb->get_col( $sql ); // go through each event and delete individually so individual hooks are fired appropriately foreach($event_ids as $event_id){ $EM_Event = em_get_event( $event_id ); if($EM_Event->recurrence_id == $this->event_id){ $EM_Event->delete(true); $events_array[] = $EM_Event; } } $result = !empty($events_array) || (is_array($event_ids) && empty($event_ids)); // success if we deleted something, or if there was nothing to delete in the first place } $result = apply_filters('delete_events', $result, $this, $events_array); //Deprecated, use em_event_delete_events return apply_filters('em_event_delete_events', $result, $this, $events_array); } /** * Returns the days that match the recurrance array passed (unix timestamps) * @param array $recurrence * @return array */ function get_recurrence_days(){ //get timestampes for start and end dates, both at 12AM $start_date = $this->start()->copy()->setTime(0,0,0)->getTimestamp(); $end_date = $this->end()->copy()->setTime(0,0,0)->getTimestamp(); $weekdays = explode(",", $this->recurrence_byday); //what days of the week (or if monthly, one value at index 0) $matching_days = array(); //the days we'll be returning in timestamps //generate matching dates based on frequency type switch ( $this->recurrence_freq ){ /* @var EM_DateTime $current_date */ case 'daily': //If daily, it's simple. Get start date, add interval timestamps to that and create matching day for each interval until end date. $current_date = $this->start()->copy()->setTime(0,0,0); while( $current_date->getTimestamp() <= $end_date ){ $matching_days[] = $current_date->getTimestamp(); $current_date->add('P'. $this->recurrence_interval .'D'); } break; case 'weekly': //sort out week one, get starting days and then days that match time span of event (i.e. remove past events in week 1) $current_date = $this->start()->copy()->setTime(0,0,0); $start_of_week = get_option('start_of_week'); //Start of week depends on WordPress //then get the timestamps of weekdays during this first week, regardless if within event range $start_weekday_dates = array(); //Days in week 1 where there would events, regardless of event date range for($i = 0; $i < 7; $i++){ if( in_array( $current_date->format('w'), $weekdays) ){ $start_weekday_dates[] = $current_date->getTimestamp(); //it's in our starting week day, so add it } $current_date->add('P1D'); //add a day } //for each day of eventful days in week 1, add 7 days * weekly intervals foreach ($start_weekday_dates as $weekday_date){ //Loop weeks by interval until we reach or surpass end date $current_date->setTimestamp($weekday_date); while($current_date->getTimestamp() <= $end_date){ if( $current_date->getTimestamp() >= $start_date && $current_date->getTimestamp() <= $end_date ){ $matching_days[] = $current_date->getTimestamp(); } $current_date->add('P'. ($this->recurrence_interval * 7 ) .'D'); } }//done! break; case 'monthly': //loop months starting this month by intervals $current_date = $this->start()->copy(); $current_date->modify($current_date->format('Y-m-01 00:00:00')); //Start date on first day of month, done this way to avoid 'first day of' issues in PHP < 5.6 while( $current_date->getTimestamp() <= $this->end()->getTimestamp() ){ $last_day_of_month = $current_date->format('t'); //Now find which day we're talking about $current_week_day = $current_date->format('w'); $matching_month_days = array(); //Loop through days of this years month and save matching days to temp array for($day = 1; $day <= $last_day_of_month; $day++){ if((int) $current_week_day == $this->recurrence_byday){ $matching_month_days[] = $day; } $current_week_day = ($current_week_day < 6) ? $current_week_day+1 : 0; } //Now grab from the array the x day of the month $matching_day = false; if( $this->recurrence_byweekno > 0 ){ //date might not exist (e.g. fifth Sunday of a month) so only add if it exists if( !empty($matching_month_days[$this->recurrence_byweekno-1]) ){ $matching_day = $matching_month_days[$this->recurrence_byweekno-1]; } }else{ //last day of month, so we pop the last matching day $matching_day = array_pop($matching_month_days); } //if we have a matching day, get the timestamp, make sure it's within our start/end dates for the event, and add to array if it is if( !empty($matching_day) ){ $matching_date = $current_date->setDate( $current_date->format('Y'), $current_date->format('m'), $matching_day )->getTimestamp(); if($matching_date >= $start_date && $matching_date <= $end_date){ $matching_days[] = $matching_date; } } //add the monthly interval to the current date, but set to 1st of current month first so we don't jump months where $current_date is 31st and next month there's no 31st (so a month is skipped) $current_date->modify($current_date->format('Y-m-01')); //done this way to avoid 'first day of ' PHP < 5.6 issues $current_date->add('P'.$this->recurrence_interval.'M'); } break; case 'yearly': //Yearly is easy, we get the start date as a cloned EM_DateTime and keep adding a year until it surpasses the end EM_DateTime value. $EM_DateTime = $this->start()->copy(); while( $EM_DateTime <= $this->end() ){ $matching_days[] = $EM_DateTime->getTimestamp(); $EM_DateTime->add('P'.absint($this->recurrence_interval).'Y'); } break; } sort($matching_days); return apply_filters('em_events_get_recurrence_days', $matching_days, $this); } /** * If event is recurring, set recurrences to same status as template * @param $status */ function set_status_events($status){ //give sub events same status global $wpdb; //get post and event ids of recurrences $post_ids = $event_ids = array(); $events_array = EM_Events::get( array('recurrence'=>$this->event_id, 'scope'=>'all', 'status'=>false, 'array'=>true) ); //only get recurrences that aren't trashed or drafted foreach( $events_array as $event_array ){ $post_ids[] = absint($event_array['post_id']); $event_ids[] = absint($event_array['event_id']); } if( !empty($post_ids) ){ //decide on what status to set and update wp_posts in the process if($status === null){ $set_status='NULL'; //draft post $post_status = 'draft'; //set post status in this instance }elseif( $status == -1 ){ //trashed post $set_status = -1; $post_status = 'trash'; //set post status in this instance }else{ $set_status = $status ? 1:0; //published or pending post $post_status = $set_status ? 'publish':'pending'; } if( EM_MS_GLOBAL ) switch_to_blog( $this->blog_id ); $result = $wpdb->query( $wpdb->prepare("UPDATE ".$wpdb->posts." SET post_status=%s WHERE ID IN (". implode(',', $post_ids) .')', array($post_status)) ); restore_current_blog(); $result = $result && $wpdb->query( $wpdb->prepare("UPDATE ".EM_EVENTS_TABLE." SET event_status=%s WHERE event_id IN (". implode(',', $event_ids) .')', array($set_status)) ); } return apply_filters('em_event_set_status_events', $result !== false, $status, $this, $event_ids, $post_ids); } /** * Returns a string representation of this recurrence. Will return false if not a recurrence * @return string */ function get_recurrence_description() { $EM_Event_Recurring = $this->get_event_recurrence(); $recurrence = $this->to_array(); $weekdays_name = array( translate('Sunday'),translate('Monday'),translate('Tuesday'),translate('Wednesday'),translate('Thursday'),translate('Friday'),translate('Saturday')); $monthweek_name = array('1' => __('the first %s of the month', 'events-manager'),'2' => __('the second %s of the month', 'events-manager'), '3' => __('the third %s of the month', 'events-manager'), '4' => __('the fourth %s of the month', 'events-manager'), '5' => __('the fifth %s of the month', 'events-manager'), '-1' => __('the last %s of the month', 'events-manager')); $output = sprintf (__('From %1$s to %2$s', 'events-manager'), $EM_Event_Recurring->event_start_date, $EM_Event_Recurring->event_end_date).", "; if ($EM_Event_Recurring->recurrence_freq == 'daily') { $freq_desc =__('everyday', 'events-manager'); if ($EM_Event_Recurring->recurrence_interval > 1 ) { $freq_desc = sprintf (__("every %s days", 'events-manager'), $EM_Event_Recurring->recurrence_interval); } }elseif ($EM_Event_Recurring->recurrence_freq == 'weekly') { $weekday_array = explode(",", $EM_Event_Recurring->recurrence_byday); $natural_days = array(); foreach($weekday_array as $day){ array_push($natural_days, $weekdays_name[$day]); } $output .= implode(", ", $natural_days); $freq_desc = " " . __("every week", 'events-manager'); if ($EM_Event_Recurring->recurrence_interval > 1 ) { $freq_desc = " ".sprintf (__("every %s weeks", 'events-manager'), $EM_Event_Recurring->recurrence_interval); } }elseif ($EM_Event_Recurring->recurrence_freq == 'monthly') { $weekday_array = explode(",", $EM_Event_Recurring->recurrence_byday); $natural_days = array(); foreach($weekday_array as $day){ if( is_numeric($day) ){ array_push($natural_days, $weekdays_name[$day]); } } $freq_desc = sprintf (($monthweek_name[$EM_Event_Recurring->recurrence_byweekno]), implode(" and ", $natural_days)); if ($EM_Event_Recurring->recurrence_interval > 1 ) { $freq_desc .= ", ".sprintf (__("every %s months",'events-manager'), $EM_Event_Recurring->recurrence_interval); } }elseif ($EM_Event_Recurring->recurrence_freq == 'yearly') { $freq_desc = __("every year", 'events-manager'); if ($EM_Event_Recurring->recurrence_interval > 1 ) { $freq_desc .= sprintf (__("every %s years",'events-manager'), $EM_Event_Recurring->recurrence_interval); } }else{ $freq_desc = "[ERROR: corrupted database record]"; } $output .= $freq_desc; return $output; } /********************************************************** * UTILITIES ***********************************************************/ function to_array( $db = false ){ $event_array = parent::to_array($db); //we reset start/end datetimes here, based on the EM_DateTime objects if they are valid $event_array['event_start'] = $this->start()->valid ? $this->start(true)->format('Y-m-d H:i:s') : null; $event_array['event_end'] = $this->end()->valid ? $this->end(true)->format('Y-m-d H:i:s') : null; return apply_filters('em_event_to_array', $event_array, $this); } /** * Can the user manage this? */ function can_manage( $owner_capability = false, $admin_capability = false, $user_to_check = false ){ if( ($this->just_added_event || $this->event_id == '') && !is_user_logged_in() && get_option('dbem_events_anonymous_submissions') ){ $user_to_check = get_option('dbem_events_anonymous_user'); } return apply_filters('em_event_can_manage', parent::can_manage($owner_capability, $admin_capability, $user_to_check), $this, $owner_capability, $admin_capability, $user_to_check); } /** * Outputs a JSON-encodable associative array of data to output to REST or other remote operations * @return array */ function to_api(){ $event = array ( 'name' => $this->event_name, 'id' => $this->event_id, 'post_id' => $this->post_id, 'parent' => $this->event_parent, 'owner' => $this->event_owner, // overwritten further down 'blog_id' => $this->blog_id, 'group_id' => $this->group_id, 'slug' => $this->event_slug, 'status' => $this->event_private, 'content' => $this->post_content, 'bookings' => array ( 'end_date' => $this->event_rsvp_date, 'end_time' => $this->event_rsvp_time, 'rsvp_spaces' => $this->event_rsvp_spaces, 'spaces' => $this->event_spaces, ), 'when' => array( 'all_day' => $this->event_all_day, 'start' => $this->event_start, 'start_date' => $this->event_start_date, 'start_time' => $this->event_start_time, 'end' => $this->event_end, 'end_date' => $this->event_end_date, 'end_time' => $this->event_end_time, 'timezone' => $this->event_timezone, ), 'location' => false, 'recurrence' => false, 'language' => $this->event_language, 'translation' => $this->event_translation, ); if( $this->event_owner ){ // anonymous $event['owner'] = array( 'guest' => true, 'email' => $this->get_contact()->user_email, 'name' => $this->get_contact()->get_name(), ); }else{ // user $event['owner'] = array( 'guest' => false, 'email' => $this->get_contact()->user_email, 'name' => $this->get_contact()->get_name(), ); } if( $this->recurrence_id ){ $event['recurrence_id'] = $this->recurrence_id; } if( $this->recurrence ){ $event['recurrence'] = array ( 'interval' => $this->recurrence_interval, 'freq' => $this->recurrence_freq, 'days' => $this->recurrence_days, 'byday' => $this->recurrence_byday, 'byweekno' => $this->recurrence_byweekno, 'rsvp_days' => $this->recurrence_rsvp_days, ); } if( $this->has_location() ) { $EM_Location = $this->get_location(); $event['location'] = $EM_Location->to_api(); }elseif( $this->has_event_location() ){ $event['location_type'] = $this->event_location_type; $event['location'] = $this->get_event_location()->to_api(); } return $event; } public static function get_active_statuses(){ if( !empty(static::$active_statuses) ) { return static::$active_statuses; } $statuses = array( 0 => __('Cancelled', 'events-manager'), 1 => __('Active', 'events-manager') ); static::$active_statuses = apply_filters('event_get_active_statuses', $statuses); return static::$active_statuses; } } //TODO placeholder targets filtering could be streamlined better /** * This is a temporary filter function which mimicks the old filters in the old 2.x placeholders function * @param string $result * @param EM_Event $event * @param string $placeholder * @param string $target * @return mixed */ function em_event_output_placeholder($result,$event,$placeholder,$target='html'){ if( $target == 'raw' ) return $result; if( in_array($placeholder, array("#_EXCERPT",'#_EVENTEXCERPT','#_EVENTEXCERPTCUT', "#_LOCATIONEXCERPT")) && $target == 'html' ){ $result = apply_filters('dbem_notes_excerpt', $result); }elseif( $placeholder == '#_CONTACTEMAIL' && $target == 'html' ){ $result = em_ascii_encode($event->get_contact()->user_email); }elseif( in_array($placeholder, array('#_EVENTNOTES','#_NOTES','#_DESCRIPTION','#_LOCATIONNOTES','#_CATEGORYNOTES','#_CATEGORYDESCRIPTION')) ){ if($target == 'rss'){ $result = apply_filters('dbem_notes_rss', $result); $result = apply_filters('the_content_rss', $result); }elseif($target == 'map'){ $result = apply_filters('dbem_notes_map', $result); }elseif($target == 'ical'){ $result = apply_filters('dbem_notes_ical', $result); }elseif ($target == "email"){ $result = apply_filters('dbem_notes_email', $result); }else{ //html $result = apply_filters('dbem_notes', $result); } }elseif( in_array($placeholder, array("#_NAME",'#_LOCATION','#_TOWN','#_ADDRESS','#_LOCATIONNAME',"#_EVENTNAME","#_LOCATIONNAME",'#_CATEGORY')) ){ if ($target == "rss"){ $result = apply_filters('dbem_general_rss', $result); }elseif ($target == "ical"){ $result = apply_filters('dbem_general_ical', $result); }elseif ($target == "email"){ $result = apply_filters('dbem_general_email', $result); }else{ //html $result = apply_filters('dbem_general', $result); } } return $result; } add_filter('em_category_output_placeholder','em_event_output_placeholder',1,4); add_filter('em_event_output_placeholder','em_event_output_placeholder',1,4); add_filter('em_location_output_placeholder','em_event_output_placeholder',1,4); // FILTERS // filters for general events field (corresponding to those of "the _title") add_filter('dbem_general', 'wptexturize'); add_filter('dbem_general', 'convert_chars'); add_filter('dbem_general', 'trim'); // filters for the notes field in html (corresponding to those of "the _content") add_filter('dbem_notes', 'wptexturize'); add_filter('dbem_notes', 'convert_smilies'); add_filter('dbem_notes', 'convert_chars'); add_filter('dbem_notes', 'wpautop'); add_filter('dbem_notes', 'prepend_attachment'); add_filter('dbem_notes', 'do_shortcode'); // filters for the notes field in html (corresponding to those of "the _content") add_filter('dbem_notes_excerpt', 'wptexturize'); add_filter('dbem_notes_excerpt', 'convert_smilies'); add_filter('dbem_notes_excerpt', 'convert_chars'); add_filter('dbem_notes_excerpt', 'wpautop'); add_filter('dbem_notes_excerpt', 'prepend_attachment'); add_filter('dbem_notes_excerpt', 'do_shortcode'); // RSS content filter add_filter('dbem_notes_rss', 'convert_chars', 8); add_filter('dbem_general_rss', 'esc_html', 8); // Notes map filters add_filter('dbem_notes_map', 'convert_chars', 8); add_filter('dbem_notes_map', 'js_escape'); //embeds support if using placeholders if ( is_object($GLOBALS['wp_embed']) ){ add_filter( 'dbem_notes', array( $GLOBALS['wp_embed'], 'run_shortcode' ), 8 ); add_filter( 'dbem_notes', array( $GLOBALS['wp_embed'], 'autoembed' ), 8 ); } // booking form notices, overridable to inject other content (e.g. waiting list) function em_booking_form_status_disabled(){ echo '

'. get_option('dbem_bookings_form_msg_disabled') .'

'; } add_action('em_booking_form_status_disabled', 'em_booking_form_status_disabled'); function em_booking_form_status_full(){ echo '

'. get_option('dbem_bookings_form_msg_full') .'

'; } add_action('em_booking_form_status_full', 'em_booking_form_status_full'); function em_booking_form_status_closed(){ echo '

'. get_option('dbem_bookings_form_msg_closed') .'

'; } add_action('em_booking_form_status_closed', 'em_booking_form_status_closed'); function em_booking_form_status_cancelled(){ echo '

'. get_option('dbem_bookings_form_msg_cancelled') .'

'; } add_action('em_booking_form_status_cancelled', 'em_booking_form_status_cancelled'); function em_booking_form_status_already_booked(){ echo get_option('dbem_bookings_form_msg_attending'); echo ''. get_option('dbem_bookings_form_msg_bookings_link') .''; } add_action('em_booking_form_status_already_booked', 'em_booking_form_status_already_booked'); /** * This function replaces the default gallery shortcode, so it can check if this is a recurring event recurrence and pass on the parent post id as the default post. * @param array $attr */ function em_event_gallery_override( $attr = array() ){ global $post; if( !empty($post->post_type) && $post->post_type == EM_POST_TYPE_EVENT && empty($attr['id']) && empty($attr['ids']) ){ //no id specified, so check if it's recurring and override id with recurrence template post id $EM_Event = em_get_event($post->ID, 'post_id'); if( $EM_Event->is_recurrence() ){ $attr['id'] = $EM_Event->get_event_recurrence()->post_id; } } return gallery_shortcode($attr); } function em_event_gallery_override_init(){ remove_shortcode('gallery'); add_shortcode('gallery', 'em_event_gallery_override'); } add_action('init','em_event_gallery_override_init', 1000); //so that plugins like JetPack don't think we're overriding gallery, we're not i swear! ?>