File: /var/www/html/triad-infosec/wp-content/plugins/events-calendar-pro/src/Tribe/Recurrence/Meta.php
<?php
/**
* Tribe__Events__Pro__Recurrence__Meta
*
* WordPress hooks and filters controlling event recurrence.
*/
class Tribe__Events__Pro__Recurrence__Meta {
const UPDATE_TYPE_ALL = 1;
const UPDATE_TYPE_FUTURE = 2;
const UPDATE_TYPE_SINGLE = 3;
/** @var Tribe__Events__Pro__Recurrence__Scheduler */
public static $scheduler = null;
/**
* @var Tribe__Events__Pro__Recurrence__Children_Events
*/
protected static $children;
/**
* Initializes the Recurrence Meta class and sub-classes and hooks on the filters required for it to work.
*
* @since 3.1
*
* @param bool $all Whether to init all the functions and hook all the filters for the class or just the ones
* common to all the Recurrence Instance Engines.
*/
public static function init( $all = true ) {
$actions = [
[ 'tribe_events_date_display', [ __CLASS__, 'loadRecurrenceData' ], 10, 1 ],
[ 'pre_get_comments', [ __CLASS__, 'set_post_id_for_recurring_event_comment_queries' ], 10, 1 ],
[ 'comment_post_redirect', [ __CLASS__, 'fix_redirect_after_comment_is_posted' ], 10, 2 ],
[ 'wp_update_comment_count', [ __CLASS__, 'update_comment_counts_on_child_events' ], 10, 3 ],
[
'manage_' . Tribe__Events__Main::POSTTYPE . '_posts_custom_column',
[ __CLASS__, 'populate_custom_list_table_columns' ],
10,
2,
],
[ 'wp_before_admin_bar_render', [ __CLASS__, 'admin_bar_render' ], 10, 1 ],
[ 'tribe_community_events_enqueue_resources', [ __CLASS__, 'enqueue_recurrence_data' ], 10, 1 ],
[
'tribe_events_community_form_before_template',
[ __CLASS__, 'output_recurrence_json_data' ],
10,
1,
],
];
if ( $all ) {
array_push( $actions,
[ 'tribe_events_update_meta', [ __CLASS__, 'updateRecurrenceMeta' ], 20, 2 ],
[ 'before_delete_post', [ __CLASS__, 'handle_delete_request' ], 10, 1 ],
[ 'wp_trash_post', [ __CLASS__, 'handle_trash_request' ], 10, 1 ],
[ 'untrashed_post', [ __CLASS__, 'handle_untrash_request' ], 10, 1 ],
[ 'admin_notices', [ __CLASS__, 'showRecurrenceErrorFlash' ], 10, 1 ],
[ 'tribe_recurring_event_error', [ __CLASS__, 'setupRecurrenceErrorMsg' ], 10, 2 ],
[ 'admin_action_tribe_split', [ __CLASS__, 'handle_split_request' ], 10, 1 ],
[ 'load-edit.php', [ __CLASS__, 'combineRecurringRequestIds' ], 10, 1 ],
[ 'updated_post_meta', [ __CLASS__, 'update_child_thumbnails' ], 4, 40 ],
[ 'added_post_meta', [ __CLASS__, 'update_child_thumbnails' ], 4, 40 ],
[ 'deleted_post_meta', [ __CLASS__, 'remove_child_thumbnails' ], 4, 40 ], [
'update_option_' . Tribe__Main::OPTIONNAME,
[
Tribe__Events__Pro__Recurrence__Old_Events_Cleaner::instance(),
'clean_up_old_recurring_events',
],
10,
2,
] );
}
foreach ( $actions as $action ) {
list( $tag, $callback, $priority, $args ) = $action;
if ( has_action( $tag, $callback ) ) {
continue;
}
add_action( $tag, $callback, $priority, $args );
}
$filters = [
[ 'get_edit_post_link', [ __CLASS__, 'filter_edit_post_link' ], 10, 3 ],
[ 'preprocess_comment', [ __CLASS__, 'set_parent_for_recurring_event_comments' ], 10, 1 ],
[ 'comments_array', [ __CLASS__, 'set_comments_array_on_child_events' ], 10, 2 ],
[
'manage_' . Tribe__Events__Main::POSTTYPE . '_posts_columns',
[ __CLASS__, 'list_table_column_headers' ],
10,
1,
],
[ 'post_class', [ __CLASS__, 'add_recurring_event_post_classes' ], 10, 3 ],
[ 'post_row_actions', [ __CLASS__, 'edit_post_row_actions' ], 10, 2 ],
[ 'tec_general_settings_viewing_section', [ __CLASS__, 'inject_settings' ], 10, 1 ],
[
'tribe_events_pro_output_recurrence_data',
[ __CLASS__, 'maybe_fix_datepicker_output' ],
10,
2,
],
];
if ( $all ) {
$filters[] = [ 'posts_request', [ 'Tribe__Events__Pro__Recurrence__Queries', 'collapse_sql' ], 10, 2 ];
}
foreach ( $filters as $filter ) {
list( $tag, $callback, $priority, $args ) = $filter;
if ( has_filter( $tag, $callback ) ) {
continue;
}
add_filter( $tag, $callback, $priority, $args );
}
if ( is_admin() ) {
if ( ! has_filter( 'tribe_events_pro_localize_script', [
Tribe__Events__Pro__Recurrence__Scripts::instance(),
'localize',
] ) ) {
add_filter( 'tribe_events_pro_localize_script', [
Tribe__Events__Pro__Recurrence__Scripts::instance(),
'localize',
], 10, 3 );
}
}
tribe_asset(
Tribe__Events__Pro__Main::instance(),
Tribe__Events__Main::POSTTYPE . '-recurrence',
'events-recurrence.css',
[ 'tribe-select2-css', 'tribe-common-admin' ],
null
);
/**
* Register Notices.
*/
if ( $all ) {
self::reset_scheduler();
Tribe__Admin__Notices::instance()->register( 'editing-all-recurrences', [
__CLASS__,
'render_notice_editing_all_recurrences',
], 'type=success' );
Tribe__Admin__Notices::instance()->register( 'created-recurrences', [
__CLASS__,
'render_notice_created_recurrences',
], 'type=success' );
}
}
/**
* Displays a message informing the user she is editing all of the recurrences in the series.
*/
public static function render_notice_editing_all_recurrences() {
if ( ! Tribe__Admin__Helpers::instance()->is_post_type_screen( Tribe__Events__Main::POSTTYPE ) ) {
return false;
}
if ( empty( $_REQUEST['post'] ) || ! tribe_is_recurring_event( $_REQUEST['post'] ) ) {
return false;
}
$message = '<p>' . esc_html__( 'You are currently editing all events in a recurring series.', 'tribe-events-calendar-pro' ) . '</p>';
return Tribe__Admin__Notices::instance()->render( 'editing-all-recurrences', $message );
}
public static function render_notice_created_recurrences() {
if ( ! Tribe__Admin__Helpers::instance()->is_post_type_screen( Tribe__Events__Main::POSTTYPE ) ) {
return false;
}
if ( empty( $_REQUEST['post'] ) || ! tribe_is_recurring_event( $_REQUEST['post'] ) ) {
return false;
}
$pending = get_post_meta( get_the_ID(), '_EventNextPendingRecurrence', true );
if ( ! $pending ) {
return false;
}
$start_dates = tribe_get_recurrence_start_dates( get_the_ID() );
$count = count( $start_dates );
$last = end( $start_dates );
$pending_message = __( '%d instances of this event have been created through %s. <a href="%s" target="_blank">Learn more.</a>', 'tribe-events-calendar-pro' );
$pending_message = '<p>' . sprintf( $pending_message, $count, date_i18n( tribe_get_date_format( true ), strtotime( $last ) ), 'https://evnt.is/lq' ) . '</p>';
return Tribe__Admin__Notices::instance()->render( 'created-recurrences', $pending_message );
}
public static function filter_edit_post_link( $url, $post_id, $context ) {
if ( ! empty( $post_id ) && tribe_is_recurring_event( $post_id ) && $parent = wp_get_post_parent_id( $post_id ) ) {
return get_edit_post_link( $parent, $context );
}
return $url;
}
/**
* Checks to fix all the required datepicker dates to the correct format.
*
* @param array $recurrence The Recurrence meta rules.
*
* @return array Recurrence Meta after maybe fixing the data.
*/
public static function maybe_fix_datepicker_output( $recurrence, $post_id ) {
if ( empty( $recurrence['exclusions'] ) ) {
return $recurrence;
}
// Fetch the datepicker current format.
$datepicker_format = Tribe__Date_Utils::datepicker_formats( tribe_get_option( 'datepickerFormat' ) );
foreach ( $recurrence['exclusions'] as &$exclusion ) {
// Only do something if the exclusion is custom+date.
if ( empty( $exclusion['custom'] ) || 'Date' !== $exclusion['custom']['type'] ) {
continue;
}
// Actually do the conversion of output.
if ( ! empty( $exclusion['custom']['date']['date'] ) ) {
$formatted = date( $datepicker_format, strtotime( $exclusion['custom']['date']['date'] ) );
$exclusion['custom']['date']['date'] = $formatted;
// set the end too to stick with new format
$exclusion['end'] = $formatted;
} else {
if ( isset( $exclusion['end'] ) ) {
$exclusion['end'] = date( $datepicker_format, strtotime( $exclusion['end'] ) );
}
}
// If there's no value, clean the output of the custom date.
if ( isset( $exclusion['custom']['date']['date'] ) && ! $exclusion['custom']['date']['date'] ) {
$exclusion = Tribe__Utils__Array::set( $exclusion, [ 'custom', 'date', 'date' ], '' );
}
}
return $recurrence;
}
/**
* Change the link for a recurring event to edit its series.
*
* @return void
*/
public static function admin_bar_render() {
/** @var WP_Admin_Bar $wp_admin_bar */
global $post, $wp_admin_bar;
if ( ! $post instanceof WP_Post ) {
return;
}
if ( is_admin() || ! tribe_is_recurring_event( $post ) ) {
return;
}
if ( get_query_var( 'eventDisplay' ) == 'all' ) {
return;
}
$menu_parent = $wp_admin_bar->get_node( 'edit' );
if ( ! $menu_parent ) {
return;
}
// We need to make sure we're editing the correct post (in cases where we show events on venue pages, etc), and the user can do so.
if ( get_edit_post_link( $post->ID ) === $menu_parent->href && current_user_can( 'edit_post', $post->ID ) ) {
$wp_admin_bar->add_node( [
'id' => 'edit-series',
'title' => __( 'Edit Series', 'tribe-events-calendar-pro' ),
'parent' => 'edit',
'href' => $menu_parent->href,
] );
$wp_admin_bar->add_node( [
'id' => 'split-single',
'title' => __( 'Break from Series', 'tribe-events-calendar-pro' ),
'parent' => 'edit',
'href' => esc_url( wp_nonce_url( self::get_split_series_url( $post->ID, false, false ), 'tribe_split_' . $post->ID ) ),
'meta' => [
'class' => 'tribe-split-single',
],
] );
if ( ! empty( $post->post_parent ) ) {
$wp_admin_bar->add_node( [
'id' => 'split-series',
'title' => __( 'Edit Future Events', 'tribe-events-calendar-pro' ),
'parent' => 'edit',
'href' => esc_url( wp_nonce_url( self::get_split_series_url( $post->ID, false, true ), 'tribe_split_' . $post->ID ) ),
'meta' => [
'class' => 'tribe-split-all',
],
] );
}
}
}
public static function list_table_column_headers( $columns ) {
$columns['recurring'] = __( 'Recurring', 'tribe-events-calendar-pro' );
return $columns;
}
public static function populate_custom_list_table_columns( $column, $post_id ) {
if ( $column == 'recurring' ) {
if ( tribe_is_recurring_event( $post_id ) ) {
echo esc_html__( 'Yes', 'tribe-events-calendar-pro' );
} else {
echo esc_html__( '—', 'tribe-events-calendar-pro' );
}
}
}
public static function add_recurring_event_post_classes( $classes, $class, $post_id ) {
if ( get_post_type( $post_id ) == Tribe__Events__Main::POSTTYPE && tribe_is_recurring_event( $post_id ) ) {
$classes[] = 'tribe-recurring-event';
$post = get_post( $post_id );
if ( empty( $post->post_parent ) ) {
$classes[] = 'tribe-recurring-event-parent';
} else {
$classes[] = 'tribe-recurring-event-child';
}
}
return $classes;
}
public static function edit_post_row_actions( $actions, $post ) {
if ( tribe_is_recurring_event( $post ) ) {
unset( $actions['inline hide-if-no-js'] );
$post_type_object = get_post_type_object( Tribe__Events__Main::POSTTYPE );
$is_first_in_series = empty( $post->post_parent );
$first_id_in_series = $post->post_parent ? $post->post_parent : $post->ID;
if ( isset( $actions['edit'] ) && 'trash' != $post->post_status ) {
if ( current_user_can( 'edit_post', $post->ID ) ) {
$split_actions = [];
$split_actions['split'] = sprintf( '<a href="%s" class="tribe-split tribe-split-single" title="%s">%s</a>',
esc_url( wp_nonce_url( self::get_split_series_url( $post->ID, false, false ), 'tribe_split_' . $post->ID ) ),
esc_attr( __( 'Break this event out of its series and edit it independently', 'tribe-events-calendar-pro' ) ),
__( 'Edit Single', 'tribe-events-calendar-pro' )
);
if ( ! $is_first_in_series ) {
$split_actions['split_all'] = sprintf( '<a href="%s" class="tribe-split tribe-split-all" title="%s">%s</a>',
esc_url( wp_nonce_url( self::get_split_series_url( $post->ID, false, true ), 'tribe_split_' . $post->ID ) ),
esc_attr( __( 'Split the series in two at this point, creating a new series out of this and all subsequent events', 'tribe-events-calendar-pro' ) ),
__( 'Edit Upcoming', 'tribe-events-calendar-pro' )
);
}
$actions = Tribe__Main::array_insert_after_key( 'edit', $actions, $split_actions );
}
if ( current_user_can( 'edit_post', $first_id_in_series ) ) {
$edit_series_url = get_edit_post_link( $first_id_in_series, 'display' );
$actions['edit'] = sprintf(
'<a href="%s" title="%s">%s</a>',
esc_url( $edit_series_url ),
esc_attr( __( 'Edit all events in this series', 'tribe-events-calendar-pro' ) ),
__( 'Edit All', 'tribe-events-calendar-pro' )
);
}
}
if ( $is_first_in_series ) {
if ( ! empty( $actions['trash'] ) ) {
$actions['trash'] = "<a class='submitdelete' title='" . esc_attr( __( 'Move all events in this series to the Trash', 'tribe-events-calendar-pro' ) ) . "' href='" . esc_url( get_delete_post_link( $post->ID ) ) . "'>" . esc_html__( 'Trash Series', 'tribe-events-calendar-pro' ) . '</a>';
}
if ( ! empty( $actions['delete'] ) ) {
$actions['delete'] = "<a class='submitdelete' title='" . esc_attr( __( 'Delete all events in this series permanently',
'tribe-events-calendar-pro' ) ) . "' href='" . esc_url( get_delete_post_link( $post->ID, '', true ) ) . "'>" . esc_html__( 'Delete Series Permanently',
'tribe-events-calendar-pro' ) . '</a>';
}
}
if ( ! empty( $actions['untrash'] ) ) { // if the whole series is in the trash, restore the whole series together
$first_event = get_post( $first_id_in_series );
if ( isset( $first_event->post_status ) && $first_event->post_status == 'trash' ) {
$actions['untrash'] = "<a title='" . esc_attr( __( 'Restore all events in this series from the Trash',
'tribe-events-calendar-pro' ) ) . "' href='" . esc_url( wp_nonce_url( admin_url( sprintf( $post_type_object->_edit_link . '&action=untrash',
$first_id_in_series ) ), 'untrash-post_' . $first_id_in_series ) ) . "'>" . esc_html__( 'Restore Series', 'tribe-events-calendar-pro' ) . '</a>';
}
}
}
return $actions;
}
private static function get_split_series_url( $id, $context = 'display', $all = false ) {
if ( ! $post = get_post( $id ) ) {
return;
}
if ( 'revision' === $post->post_type ) {
return;
}
if ( ! current_user_can( 'edit_post', $post->ID ) ) {
return;
}
$post_type_object = get_post_type_object( $post->post_type );
if ( ! $post_type_object ) {
return;
}
$args = [ 'action' => 'tribe_split' ];
if ( $all ) {
$args['split_all'] = 1;
}
$url = admin_url( sprintf( $post_type_object->_edit_link, $post->ID ) );
$url = add_query_arg( $args, $url );
if ( $context == 'display' ) {
$url = esc_url( $url );
}
return apply_filters( 'tribe_events_get_split_series_link', $url, $post->ID, $context, $all );
}
public static function handle_split_request() {
// TODO: would be nice to have a way to add it back into the series (i.e., an undo).
$post_id = isset( $_REQUEST['post'] ) ? $_REQUEST['post'] : 0;
check_admin_referer( 'tribe_split_' . $post_id );
if ( ! current_user_can( 'edit_post', $post_id ) ) {
wp_die( 'You do not have sufficient capabilities to edit this event', 'tribe-events-calendar-pro' );
exit();
}
$splitter = new Tribe__Events__Pro__Recurrence__Series_Splitter();
if ( ! empty( $_REQUEST['split_all'] ) ) {
$splitter->break_remaining_events_from_series( $post_id );
} else {
$splitter->break_single_event_from_series( $post_id );
}
// TODO: show a message?
$edit_url = get_edit_post_link( $post_id, false );
wp_redirect( $edit_url );
exit();
}
public static function handle_trash_request( $post_id ) {
if ( tribe_is_recurring_event( $post_id ) && ! wp_get_post_parent_id( $post_id ) ) {
self::children()->trash_all( $post_id );
}
}
public static function handle_untrash_request( $post_id ) {
if ( tribe_is_recurring_event( $post_id ) && ! wp_get_post_parent_id( $post_id ) ) {
self::children()->untrash_all( $post_id );
}
}
public static function handle_delete_request( $post_id ) {
if ( tribe_is_recurring_event( $post_id ) ) {
$parent = wp_get_post_parent_id( $post_id );
$parent_exists = ! is_null( get_post( $parent ) );
if ( empty( $parent ) ) {
self::children()->permanently_delete_all( $post_id );
} elseif ( $parent_exists ) {
$recurrence_meta = get_post_meta( $parent, '_EventRecurrence', true );
/**
* Controls whether exclusions should be generated for deleted child events.
*
* @param bool $should_generate_exclusion
* @param int $post_id
* @param int $parent
*/
if ( apply_filters( 'tribe_events_pro_should_generate_exclusion_for_deleted_event', true, $post_id, $parent ) ) {
$recurrence_meta = self::add_date_exclusion_to_recurrence(
$recurrence_meta,
get_post_meta( $post_id, '_EventStartDate', true )
);
}
update_post_meta( $parent, '_EventRecurrence', $recurrence_meta );
}
}
}
/**
* Given a date, add a date exclusion to the recurrence meta array
*
* @param array|mixed $recurrence_meta Recurrence meta array that holds recurrence rules/exclusions.
* @param string $date Date to add to the exclusions array in the `Y-m-d` format.
*
* @return array|mixed The modified recurrence meta array, or the input value if the input value was not an array
*/
public static function add_date_exclusion_to_recurrence( $recurrence_meta, $date ) {
if ( ! is_array( $recurrence_meta ) ) {
$recurrence_meta = [];
}
if ( empty( $recurrence_meta['exclusions'] ) || ! is_array( $recurrence_meta['exclusions'] ) ) {
$recurrence_meta['exclusions'] = [];
}
// Only array entries are valid.
$recurrence_meta['exclusions'] = array_filter( $recurrence_meta['exclusions'], 'is_array' );
$recurrence_meta['exclusions'][] = [
'type' => 'Custom',
'custom' => [
'type' => 'Date',
'date' => [
'date' => $date,
],
],
];
return $recurrence_meta;
}
/**
* Comments on recurring events should be kept with the parent event
*
* @param array $comment_data
*
* @return array
*/
public static function set_parent_for_recurring_event_comments( $comment_data ) {
if ( isset( $comment_data['comment_post_ID'] ) && tribe_is_recurring_event( $comment_data['comment_post_ID'] ) ) {
$event = get_post( $comment_data['comment_post_ID'] );
if ( ! empty( $event->post_parent ) ) {
$comment_data['comment_post_ID'] = $event->post_parent;
}
}
return $comment_data;
}
/**
* When displaying comments on a recurring event, get them from the parent
*
* @param WP_Comment_Query $query
*
* @return void
*/
public static function set_post_id_for_recurring_event_comment_queries( $query ) {
if ( ! empty( $query->query_vars['post_id'] ) && tribe_is_recurring_event( $query->query_vars['post_id'] ) ) {
$event = get_post( $query->query_vars['post_id'] );
if ( ! empty( $event->post_parent ) ) {
$query->query_vars['post_id'] = $event->post_parent;
}
}
}
public static function fix_redirect_after_comment_is_posted( $location, $comment ) {
if ( tribe_is_recurring_event( $comment->comment_post_ID ) ) {
if ( isset( $_REQUEST['comment_post_ID'] ) && $_REQUEST['comment_post_ID'] != $comment->comment_post_ID ) {
$child = get_post( $_REQUEST['comment_post_ID'] );
if ( $child->post_parent == $comment->comment_post_ID ) {
$location = str_replace( get_permalink( $comment->comment_post_ID ), get_permalink( $child->ID ), $location );
}
}
}
return $location;
}
public static function update_comment_counts_on_child_events( $parent_id, $new_count, $old_count ) {
if ( tribe_is_recurring_event( $parent_id ) ) {
$event = get_post( $parent_id );
if ( ! empty( $event->post_parent ) ) {
return; // no idea how we got here, but don't update anything
}
/** @var wpdb $wpdb */
global $wpdb;
$wpdb->update(
$wpdb->posts,
[ 'comment_count' => $new_count ],
[
'post_parent' => $parent_id,
'post_type' => Tribe__Events__Main::POSTTYPE,
]
);
$child_ids = self::children()->get_ids( $parent_id );
foreach ( $child_ids as $child ) {
clean_post_cache( $child );
}
}
}
public static function set_comments_array_on_child_events( $comments, $post_id ) {
if ( empty( $comments ) && tribe_is_recurring_event( $post_id ) ) {
$event = get_post( $post_id );
if ( ! empty( $event->post_parent ) ) {
/** @var wpdb $wpdb */
global $wpdb, $user_ID;
$commenter = wp_get_current_commenter();
$comment_author = $commenter['comment_author']; // Escaped by sanitize_comment_cookies()
$comment_author_email = $commenter['comment_author_email']; // Escaped by sanitize_comment_cookies()
if ( $user_ID ) {
$comments = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM $wpdb->comments WHERE comment_post_ID = %d AND (comment_approved = '1' OR ( user_id = %d AND comment_approved = '0' ) ) ORDER BY comment_date_gmt",
$event->post_parent, $user_ID
)
);
} elseif ( empty( $comment_author ) ) {
$comments = get_comments( [
'post_id' => $event->post_parent,
'status' => 'approve',
'order' => 'ASC',
] );
} else {
$comments = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM $wpdb->comments WHERE comment_post_ID = %d AND ( comment_approved = '1' OR ( comment_author = %s AND comment_author_email = %s AND comment_approved = '0' ) ) ORDER BY comment_date_gmt",
$event->post_parent,
wp_specialchars_decode( $comment_author, ENT_QUOTES ),
$comment_author_email
)
);
}
}
}
return $comments;
}
/**
* Update event recurrence when a recurring event is saved.
*
* @param integer $event_id Id of the event to update.
* @param array $data Data defining the recurrence of this event.
*
* @return bool `true` on success, `false` on failure.
*/
public static function updateRecurrenceMeta( $event_id, $data ) {
if ( ! isset( $data['recurrence'] ) ) {
return false;
}
// do not update recurrence meta on preview
if ( isset( $data['wp-preview'] ) && $data['wp-preview'] === 'dopreview' ) {
return false;
}
$meta_builder = new Tribe__Events__Pro__Recurrence__Meta_Builder( $event_id, $data );
$recurrence_meta = $meta_builder->build_meta();
/**
* Filter the `_EventRecurrence` meta value before it's written to the database.
*
* @since 6.0.0
*
* @param array<string,mixed> $recurrence_meta The `_EventRecurrence` meta value.
* @param int $post_id The Event post ID.
*/
$recurrence_meta = apply_filters( 'tec_events_pro_recurrence_meta_update', $recurrence_meta, $event_id );
$updated = update_post_meta( $event_id, '_EventRecurrence', $recurrence_meta );
$events_saver = new Tribe__Events__Pro__Recurrence__Events_Saver( $event_id, $updated );
return $events_saver->save_events();
}//end updateRecurrenceMeta
/**
* Displays the events recurrence form on the event editor screen.
*
* @param integer $post_id ID of the current event.
*
* @return void
*/
public static function loadRecurrenceData( $post_id ) {
$post = get_post( $post_id );
if ( ! empty( $post->post_parent ) ) {
return; // don't show recurrence fields for instances of a recurring event
}
/**
* Control if the recurrence meta is displayed.
*
* @since 4.4.35
*
* @param bool $display By default is true.
* @param int $post_id The ID of the post where the meta box is being included.
*/
$show_recurrence_meta = apply_filters( 'tribe_events_pro_show_recurrence_meta_box', true, $post_id );
if ( ! empty( $post->post_parent ) || ! $show_recurrence_meta ) {
include Tribe__Events__Pro__Main::instance()->pluginPath . 'src/admin-views/event-recurrence-blocks-message.php';
return; // don't show recurrence fields for instances of a recurring event
}
$recurrence = [];
if ( $post_id ) {
$recurrence = self::getRecurrenceMeta( $post_id );
}
self::enqueue_recurrence_data( $post_id );
$premium = Tribe__Events__Pro__Main::instance();
include Tribe__Events__Pro__Main::instance()->pluginPath . 'src/admin-views/event-recurrence.php';
}
/**
* Localizes recurrence JS data
*/
public static function enqueue_recurrence_data( $post_id = null ) {
wp_enqueue_style( Tribe__Events__Main::POSTTYPE . '-recurrence' );
if ( $post_id ) {
// Convert array to variables that can be used in the view.
$recurrence = self::getRecurrenceMeta( $post_id );
/**
* Creates a way to filter the output of recurrence meta depending on the ID
*
* @var $recurrence Meta Info
* @var $post_id the post ID
*/
$recurrence = apply_filters( 'tribe_events_pro_output_recurrence_data', $recurrence, $post_id );
wp_localize_script(
Tribe__Events__Main::POSTTYPE . '-premium-admin',
'tribe_events_pro_recurrence_data', $recurrence
);
}
wp_localize_script(
Tribe__Events__Main::POSTTYPE . '-premium-admin',
'tribe_events_pro_recurrence_strings',
apply_filters(
'tribe_events_pro_recurrence_strings',
[
'date' => self::date_strings(),
'recurrence' => Tribe__Events__Pro__Recurrence__Strings::recurrence_strings(),
'exclusion' => [],
]
)
);
}
/**
* Outputs recurrence data in JSON format when localize script won't work.
*
* @param int $post_id Post ID of event.
*/
public static function output_recurrence_json_data( $post_id, $recurrence = [] ) {
// Convert array to variables that can be used in the view.
$recurrence = self::getRecurrenceMeta( $post_id, $recurrence );
/**
* Creates a way to filter the output of recurrence meta depending on the ID.
*
* @var $recurrence Meta Info.
* @var $post_id The post ID.
*/
$recurrence = apply_filters( 'tribe_events_pro_output_recurrence_data', $recurrence, $post_id );
?>
<script>
var tribe_events_pro_recurrence_data = <?php echo json_encode( $recurrence ); ?>;
</script>
<?php
}
public static function filter_passthrough( $data ) {
return $data;
}
/**
* Setup an error message if there is a problem with a given recurrence.
* The message is saved in an option to survive the page load and is deleted after being displayed.
*
* @param array $event_id The event object that is being saved.
* @param array $msg The message to display.
*
* @return void
*/
public static function setupRecurrenceErrorMsg( $event_id, $msg ) {
global $current_screen;
// only do this when editing events
if ( is_admin() && $current_screen->id == Tribe__Events__Main::POSTTYPE ) {
update_post_meta( $event_id, 'tribe_flash_message', $msg );
}
}
/**
* Display an error message if there is a problem with a given recurrence and clear it from the cache.
*
* @return void
*/
public static function showRecurrenceErrorFlash() {
global $post, $current_screen;
if ( $current_screen->base == 'post' && $current_screen->post_type == Tribe__Events__Main::POSTTYPE ) {
$msg = get_post_meta( $post->ID, 'tribe_flash_message', true );
if ( $msg ) {
echo '<div class="error"><p>Recurrence not saved: ' . $msg . '</p></div>';
delete_post_meta( $post->ID, 'tribe_flash_message' );
}
}
}
/**
* Convenience method for turning event meta into keys available to turn into PHP variables.
*
* @param integer $post_id ID of the event being updated.
* @param array $recurrence_data The actual recurrence data.
*
* @return array
*/
public static function getRecurrenceMeta( $post_id, $recurrence_data = null ) {
if ( ! $recurrence_data ) {
$recurrence_data = get_post_meta( $post_id, '_EventRecurrence', true );
}
// Update the recurrence data if needed (useful for rules created pre-4.4)
$rules_updater = new Tribe__Events__Pro__Recurrence__Rule_Updater( $post_id, $recurrence_data );
$recurrence_data = $rules_updater->update_if_required();
if ( is_array( $recurrence_data ) ) {
if (
isset( $recurrence_data['rules']['custom']['year']['month-number'] )
&& $recurrence_data['rules']['custom']['year']['month-number']
) {
$month_number = $recurrence_data['rules']['custom']['year']['month-number'];
$recurrence_data['rules']['custom']['year']['number'] = Tribe__Events__Pro__Recurrence__Series_Rules_Factory::intToOrdinal( $month_number );
unset( $recurrence_data['rules']['custom']['year']['month-number'] );
}
if (
isset( $recurrence_data['rules']['custom']['year']['month-day'] )
&& $recurrence_data['rules']['custom']['year']['month-day']
) {
$month_day = $recurrence_data['rules']['custom']['year']['month-day'];
$recurrence_data['rules']['custom']['year']['day'] = $month_day;
unset( $recurrence_data['rules']['custom']['year']['month-day'] );
}
// update legacy data
if ( $recurrence_data && ! isset( $recurrence_data['rules'] ) ) {
$recurrence_data = self::get_legacy_recurrence_meta( $post_id, $recurrence_data );
}
}
$recurrence_data = self::recurrenceMetaDefault( $recurrence_data );
/**
* Filter the `_EventRecurrence` meta value after it's read from the database.
*
* @since 6.0.0
*
* @param array<string,mixed> $recurrence_data The `_EventRecurrence` meta value.
* @param int $post_id The Event post ID.
*/
$recurrence_data = apply_filters( 'tec_events_pro_recurrence_meta_get', $recurrence_data, $post_id );
return apply_filters( 'Tribe__Events__Pro__Recurrence_Meta_getRecurrenceMeta', $recurrence_data );
}
/**
* Convenience method for turning event meta into keys available to turn into PHP variables.
*
* @param integer $post_id ID of the event being updated.
* @param array $recurrence_data The actual recurrence data.
*
* @return array
*/
public static function get_legacy_recurrence_meta( $post_id, $recurrence_data = null ) {
if ( ! $recurrence_data ) {
$recurrence_data = get_post_meta( $post_id, '_EventRecurrence', true );
}
$utils = new Tribe__Events__Pro__Recurrence__Utils;
$record = [];
if ( $recurrence_data ) {
$record['EventStartDate'] = empty( $recurrence_data['EventStartDate'] ) ? tribe_get_start_date( $post_id ) : $recurrence_data['EventStartDate'];
$record['EventEndDate'] = empty( $recurrence_data['EventEndDate'] ) ? tribe_get_end_date( $post_id ) : $recurrence_data['EventEndDate'];
$record['type'] = empty( $recurrence_data['type'] ) ? null : $recurrence_data['type'];
$record['end-type'] = empty( $recurrence_data['end-type'] ) ? null : $recurrence_data['end-type'];
$record['end'] = empty( $recurrence_data['end'] ) ? null : $recurrence_data['end'];
$record['end-count'] = empty( $recurrence_data['end-count'] ) ? null : $recurrence_data['end-count'];
$record['custom'] = [];
$record['custom']['type'] = empty( $recurrence_data['custom-type'] ) ? null : $recurrence_data['custom-type'];
$record['custom']['interval'] = empty( $recurrence_data['custom-interval'] ) ? null : $recurrence_data['custom-interval'];
$record['custom']['same-time'] = 'yes';
$record['custom']['day']['same-time'] = 'yes';
$record['custom']['week'] = [];
$record['custom']['week']['day'] = empty( $recurrence_data['custom-week-day'] ) ? null : $recurrence_data['custom-week-day'];
$record['custom']['week']['same-time'] = 'yes';
$record['custom']['month'] = [];
$record['custom']['month']['number'] = empty( $recurrence_data['custom-month-number'] ) ? null : $recurrence_data['custom-month-number'];
$record['custom']['month']['day'] = empty( $recurrence_data['custom-month-day'] ) ? null : $recurrence_data['custom-month-day'];
$record['custom']['month']['same-time'] = 'yes';
$record['custom']['year'] = [];
$record['custom']['year']['month'] = empty( $recurrence_data['custom-year-month'] ) ? null : $recurrence_data['custom-year-month'];
$record['custom']['year']['filter'] = empty( $recurrence_data['custom-year-filter'] ) ? null : $recurrence_data['custom-year-filter'];
$record['custom']['year']['number'] = empty( $recurrence_data['custom-year-month-number'] ) ? null : Tribe__Events__Pro__Recurrence__Series_Rules_Factory::intToOrdinal( $recurrence_data['custom-year-month-number'] );
$record['custom']['year']['day'] = empty( $recurrence_data['custom-year-month-day'] ) ? null : $recurrence_data['custom-year-month-day'];
$record['custom']['year']['same-time'] = 'yes';
$custom_type_key = null;
if (
'Custom' === $record['type']
&& ! empty( $record['custom']['type'] )
) {
$custom_type_key = $utils->to_key( $record['custom']['type'] );
}
if (
$custom_type_key
&& ! empty( $record['custom'][ $custom_type_key ] )
&& ! empty( $record['custom'][ $custom_type_key ]['same-time'] )
) {
$record['custom']['same-time'] = $record['custom'][ $custom_type_key ]['same-time'];
}
$recurrence_meta['rules'][] = $record;
if ( ! empty( $recurrence_data['excluded-dates'] ) ) {
foreach ( (array) $recurrence_data['excluded-dates'] as $date ) {
self::add_date_exclusion_to_recurrence( $recurrence_meta, $date );
}
}
if ( ! empty( $recurrence_data['recurrence-description'] ) ) {
$recurrence_meta['description'] = $recurrence_data['recurrence-description'];
}
}
return apply_filters( 'tribe_pro_legacy_recurrence_meta', $recurrence_meta );
}
/**
* Clean up meta array by providing defaults.
*
* @param array $meta Meta array to clean up.
*
* @return array of $meta Merged with defaults.
*/
protected static function recurrenceMetaDefault( $meta = [] ) {
$default_meta = [
'rules' => [],
'exclusions' => [],
];
if ( $meta ) {
if ( isset( $meta['rules'] ) ) {
return $meta;
} else {
$default_meta['rules'][] = $meta;
}
}
return $default_meta;
}
/**
* Recurrence validation method. This is checked after saving an event, but before splitting a series out into multiple occurrences
*
* @param int $event_id The event object that is being saved.
* @param array $recurrence_meta Recurrence information for this event.
*
* @return bool
*/
public static function isRecurrenceValid( $event_id, $recurrence_meta ) {
$valid = true;
$errorMsg = '';
if ( isset( $recurrence_meta['type'] ) && 'Custom' === $recurrence_meta['type'] ) {
if ( ! isset( $recurrence_meta['custom']['type'] ) ) {
$valid = false;
$errorMsg = __( 'Custom recurrences must have a type selected.', 'tribe-events-calendar-pro' );
} elseif (
! isset( $recurrence_meta['custom']['start-time'] )
&& ! isset( $recurrence_meta['custom']['day'] )
&& ! isset( $recurrence_meta['custom']['week'] )
&& ! isset( $recurrence_meta['custom']['month'] )
&& ! isset( $recurrence_meta['custom']['year'] )
) {
$valid = false;
$errorMsg = __( 'Custom recurrences must have all data present.', 'tribe-events-calendar-pro' );
} elseif (
'Monthly' === $recurrence_meta['custom']['type']
&& (
empty( $recurrence_meta['custom']['month']['day'] )
|| empty( $recurrence_meta['custom']['month']['number'] )
|| '-' === $recurrence_meta['custom']['month']['day']
|| '' === $recurrence_meta['custom']['month']['number']
)
) {
$valid = false;
$errorMsg = __( 'Monthly custom recurrences cannot have a dash set as the day to occur on.', 'tribe-events-calendar-pro' );
} elseif (
'Yearly' === $recurrence_meta['custom']['type']
&& (
empty( $recurrence_meta['custom']['year']['day'] )
|| '-' === $recurrence_meta['custom']['year']['day']
)
) {
$valid = false;
$errorMsg = __( 'Yearly custom recurrences cannot have a dash set as the day to occur on.', 'tribe-events-calendar-pro' );
}
}
if ( ! $valid ) {
do_action( 'tribe_recurring_event_error', $event_id, $errorMsg );
}
return $valid;
}
/**
* Fetches child event IDs.
*
* @param int
* $post_id Post ID
* @param array $args Array of arguments for get_posts.
*
* @deprecated 4.0.1
*/
public static function get_child_event_ids( $post_id, $args = [] ) {
_deprecated_function( __METHOD__, '4.0.1', 'Tribe__Events__Pro__Recurrence__Meta::children()->get_ids()' );
return self::children()->get_ids( $post_id, $args );
}
public static function get_events_by_slug( $slug ) {
$cache = new Tribe__Cache();
$all_ids = $cache->get( 'events_by_slug_' . $slug, 'save_post' );
if ( is_array( $all_ids ) ) {
return $all_ids;
}
/** @var wpdb $wpdb */
global $wpdb;
$parent_sql = "SELECT ID FROM {$wpdb->posts} WHERE post_name=%s AND post_type=%s";
$parent_sql = $wpdb->prepare( $parent_sql, $slug, Tribe__Events__Main::POSTTYPE );
$parent_id = $wpdb->get_var( $parent_sql );
if ( empty( $parent_id ) ) {
return [];
}
$children_sql = "SELECT ID FROM {$wpdb->posts} WHERE ID=%d OR post_parent=%d AND post_type=%s";
$children_sql = $wpdb->prepare( $children_sql, $parent_id, $parent_id, Tribe__Events__Main::POSTTYPE );
$all_ids = $wpdb->get_col( $children_sql );
if ( empty( $all_ids ) ) {
return [];
}
$cache->set( 'events_by_slug_' . $slug, $all_ids, Tribe__Cache::NO_EXPIRATION, 'save_post' );
return $all_ids;
}
/**
* Get the start dates of all instances of the event in ascending order.
*
* @param int $request_post The post ID of the requested post, this might not be the Series parent, but the ID
* of any post in the Series.
*
* @return array Start times, as Y-m-d H:i:s
*/
public static function get_start_dates( $request_post ) {
if ( empty( $request_post ) ) {
return [];
}
// We get the parent first to make sure we leverage the cache correctly, that is set for the series parent.
/** @var wpdb $wpdb */
global $wpdb;
$ancestors = get_post_ancestors( $request_post );
$post_id = empty( $ancestors ) ? $request_post : end( $ancestors );
$cache = tribe( 'cache' );
$dates = $cache->get( 'event_dates_' . $post_id, 'save_post' );
if ( is_array( $dates ) ) {
return $dates;
}
/**
* This filter allows overriding the legacy query to fetch occurrence start dates.
*
* @since 6.3.0
*
* @param null $results Fetch occurrence start dates to override typical query, or null to continue.
* @param int $post_id The post ID for this recurring event.
*/
$results = apply_filters( 'tec_events_pro_recurrence_get_start_dates', null, (int) $post_id );
if ( $results === null ) {
$sql = "
SELECT meta_value
FROM {$wpdb->postmeta} m
INNER JOIN {$wpdb->posts} p ON p.ID=m.post_id
AND ( p.post_parent=%d OR p.ID=%d )
WHERE meta_key = '_EventStartDate'
AND post_type = %s
AND post_status NOT IN ( 'inherit', 'auto-draft', 'trash' )
ORDER BY meta_value ASC
";
$sql = $wpdb->prepare( $sql, $post_id, $post_id, Tribe__Events__Main::POSTTYPE );
$results = $wpdb->get_col( $sql );
}
$cache->set( 'event_dates_' . $post_id, $results, Tribe__Cache::NO_EXPIRATION, 'save_post' );
return $results;
}
/**
* Deletes events when a change in recurrence pattern renders them obsolete.
*
* This should not be used when removing individual instances from an otherwise unchanged
* pattern - wp_delete_post() can be used directly to facilitate that.
*
* This method takes care of temporarily unhooking our 'before_delete_post' callback
* to avoid the deleted instance being added to the exclusion list.
*
* @param string $instance_id The ID of the event to delete.
* @param string $start_date The start date of the event to delete.
*/
public static function delete_unexcluded_event( $instance_id, $start_date ) {
do_action( 'tribe_events_deleting_child_post', $instance_id, $start_date );
remove_action( 'before_delete_post', [ __CLASS__, 'handle_delete_request' ] );
wp_delete_post( $instance_id, true );
add_action( 'before_delete_post', [ __CLASS__, 'handle_delete_request' ] );
}
public static function save_pending_events( $event_id ) {
if ( wp_get_post_parent_id( $event_id ) != 0 ) {
return;
}
$next_pending = get_post_meta( $event_id, '_EventNextPendingRecurrence', true );
if ( empty( $next_pending ) ) {
return;
}
$latest_date = strtotime( self::$scheduler->get_latest_date() );
$recurrences = self::get_recurrence_for_event( $event_id );
$exclusions = self::get_excluded_dates( $recurrences, strtotime( $next_pending ), $latest_date );
$event_timezone_string = Tribe__Events__Timezones::get_event_timezone_string( $event_id );
$exclusions_obj = Tribe__Events__Pro__Recurrence__Exclusions::instance( $event_timezone_string );
/** @var Tribe__Events__Pro__Recurrence $recurrence */
foreach ( $recurrences['rules'] as &$recurrence ) {
$recurrence->setMinDate( strtotime( $next_pending ) );
$recurrence->setMaxDate( $latest_date );
$dates = (array) $recurrence->getDates();
if ( empty( $dates ) ) {
return; // nothing to add right now. try again later
}
$sequence = new Tribe__Events__Pro__Recurrence__Sequence( $dates, $event_id );
delete_post_meta( $event_id, '_EventNextPendingRecurrence' );
if ( $recurrence->constrainedByMaxDate() !== false ) {
update_post_meta( $event_id, '_EventNextPendingRecurrence',
date( Tribe__Events__Pro__Date_Series_Rules__Rules_Interface::DATE_FORMAT, $recurrence->constrainedByMaxDate() ) );
}
$to_create = $exclusions_obj->remove_exclusions( $sequence->get_sorted_sequence(), $exclusions );
foreach ( $to_create as $date_duration ) {
$instance = new Tribe__Events__Pro__Recurrence__Instance( $event_id, $date_duration, 0, $date_duration['sequence'] );
$instance->save();
}
}
}
/**
* Get Excluded Dates for New Events in Series.
*
* @since 4.4.20
*
* @param object $recurrences A recurrence object.
* @param int $earliest_date A unix timestamp.
* @param int $latest_date A unix timestamp.
*
* @return array
*/
private static function get_excluded_dates( $recurrences, $earliest_date, $latest_date ) {
if ( empty( $recurrences['exclusions'] ) ) {
return [];
}
// find days we should exclude
$exclusions = [];
foreach ( $recurrences['exclusions'] as &$recurrence ) {
if ( ! $recurrence ) {
continue;
}
$recurrence->setMinDate( $earliest_date );
$recurrence->setMaxDate( $latest_date );
$exclusions = array_merge( $exclusions, $recurrence->getDates() );
}
return tribe_array_unique( $exclusions );
}
public static function get_recurrence_for_event( $event_id ) {
$recurrence_meta = self::getRecurrenceMeta( $event_id );
$recurrences = [
'rules' => [],
'exclusions' => [],
];
// Used in generating all-day durations below.
$parent_start_date = strtotime( get_post_meta( $event_id, '_EventStartDate', true ) );
$parent_end_date = strtotime( get_post_meta( $event_id, '_EventEndDate', true ) );
if ( ! $recurrence_meta['rules'] ) {
$recurrences[] = new Tribe__Events__Pro__Null_Recurrence();
return $recurrences;
}
foreach ( [ 'rules', 'exclusions' ] as $rule_type ) {
foreach ( $recurrence_meta[ $rule_type ] as &$recurrence ) {
$rule = Tribe__Events__Pro__Recurrence__Series_Rules_Factory::instance()->build_from( $recurrence, $rule_type );
// recurrence meta entry might be malformed.
if ( is_wp_error( $rule ) ) {
// let's not process it and let's not try to fix it as it might be a third-party modification
tribe( 'logger' )->log_debug( "Broken recurrence data detected for event #{$event_id}", __CLASS__ );
continue;
}
$custom_type = 'none';
$start_time = null;
$duration = (int) get_post_meta( $event_id, '_EventDuration', true );
if ( isset( $recurrence['custom']['type'] ) ) {
$custom_type = Tribe__Events__Pro__Recurrence__Custom_Types::to_key( $recurrence['custom']['type'] );
}
if (
empty( $recurrence['custom'][ $custom_type ]['same-time'] )
&& isset( $recurrence['custom']['start-time'] )
&& isset( $recurrence['custom']['duration'] )
) {
$start_time = "{$recurrence['custom']['start-time']['hour']}:{$recurrence['custom']['start-time']['minute']}:00";
$start_time .= isset( $recurrence['custom']['start-time']['meridian'] ) ? " {$recurrence['custom']['start-time']['meridian']}" : '';
$duration = self::get_duration_in_seconds( $recurrence['custom']['duration'] );
} elseif (
isset( $recurrence['custom']['start-time'] )
&& ! is_array( $recurrence['custom']['start-time'] )
&& isset( $recurrence['custom']['end-time'] )
&& isset( $recurrence['custom']['end-day'] )
) {
$start_time_in_seconds = strtotime( $recurrence['custom']['start-time'] );
$start_time = date( 'H:i:s', $start_time_in_seconds );
$end_time_in_seconds = strtotime( $recurrence['custom']['end-time'] );
$end_time = date( 'H:i:s', $end_time_in_seconds );
$duration = $end_time_in_seconds - $start_time_in_seconds;
if ( is_numeric( $recurrence['custom']['end-day'] ) ) {
$duration += $recurrence['custom']['end-day'] * DAY_IN_SECONDS;
}
}
// Look out for instances inheriting the same time as the parent, where the parent is an all day event
if (
isset( $recurrence['custom']['same-time'] )
&& 'yes' === $recurrence['custom']['same-time']
&& tribe_event_is_all_day( $event_id )
) {
// In the current implementation, events are considered "all day"
// when their length is 1 second short of being 24hrs in duration.
// We need to factor this in as follows to preserve duration
// of multiday all-day events
$diff = $parent_end_date - $parent_start_date;
$num_days = absint( $diff / DAY_IN_SECONDS ) + 1;
$duration = ( $num_days * DAY_IN_SECONDS ) - 1;
}
$start = strtotime( get_post_meta( $event_id, '_EventStartDate', true ) . '+00:00' );
$is_after = false;
if ( isset( $recurrence['end-type'] ) ) {
switch ( $recurrence['end-type'] ) {
case 'On':
$end = strtotime( tribe_end_of_day( $recurrence['end'] ) );
break;
case 'Never':
$end = Tribe__Events__Pro__Recurrence::NO_END;
break;
case 'After':
default:
$end = $recurrence['end-count'] - 1; // subtract one because event is first occurrence
$is_after = true;
break;
}
} else {
$end = Tribe__Events__Pro__Recurrence::NO_END;
}
$recurrences[ $rule_type ][] = new Tribe__Events__Pro__Recurrence( $start, $end, $rule, $is_after, get_post( $event_id ), $start_time, $duration );
}
}
return $recurrences;
}
/**
* Returns the total number of seconds represented by an array of three integers
* (with the keys "days", "hours" and "seconds" - representing those units of time
* respectively).
*
* @param array $duration
*
* @return int
*/
public static function get_duration_in_seconds( array $duration ) {
$total = 0;
$expected = [
'days' => 0,
'hours' => 0,
'minutes' => 0
];
$duration = array_merge( $expected, $duration );
if ( is_numeric( $duration['days'] ) ) {
$total += absint( $duration['days'] ) * DAY_IN_SECONDS;
}
if ( is_numeric( $duration['hours'] ) ) {
$total += absint( $duration['hours'] ) * HOUR_IN_SECONDS;
}
if ( is_numeric( $duration['minutes'] ) ) {
$total += absint( $duration['minutes'] ) * MINUTE_IN_SECONDS;
}
return $total;
}
/**
* Decide which rule set to use for finding all the dates in an event series
*
* @todo fix this method!
* - currently returns an array rather than the type declared by the return doc tag
* - always returns an empty array, the result of build_from() isn't actually used
*
* @param array $postId The event to find the series for
*
* @return Tribe__Events__Pro__Date_Series_Rules__Rules_Interface
*/
public static function getSeriesRules( $postId ) {
$recurrence_meta = self::getRecurrenceMeta( $postId );
$rules = [];
foreach ( $recurrence_meta['rules'] as &$recurrence ) {
$rule = Tribe__Events__Pro__Recurrence__Series_Rules_Factory::instance()->build_from( $recurrence );
}//end foreach
return $rules;
}
/**
* Get the recurrence pattern in text format by post id.
*
* @param int $postId The post id.
*
* @return string The human readable string.
*/
public static function recurrenceToTextByPost( $postId = null ) {
if ( $postId == null ) {
global $post;
$postId = $post->ID;
}
$recurrence_rules = self::getRecurrenceMeta( $postId );
$start_date = Tribe__Events__Main::get_series_start_date( $postId );
$output_text = [];
// if there is a description override for recurrences, return that instead of building it dynamically
if ( ! empty( $recurrence_rules['description'] ) ) {
return $recurrence_rules['description'];
}
foreach ( $recurrence_rules['rules'] as $rule ) {
$output_text[] = '<p>' . self::recurrenceToText( $rule, $start_date, $postId ) . '</p>';
}
return implode( '', $output_text );
}
/**
* Build possible date-specific strings for recurrence
*
* @return array
*/
public static function date_strings() {
return [
'weekdays' => [
__( 'Monday' ),
__( 'Tuesday' ),
__( 'Wednesday' ),
__( 'Thursday' ),
__( 'Friday' ),
__( 'Saturday' ),
__( 'Sunday' ),
_x( 'day', 'placeholder used where the true day of the day is determined dynamically', 'tribe-events-calendar-pro' ),
],
'months' => [
__( 'January' ),
__( 'February' ),
__( 'March' ),
__( 'April' ),
__( 'May' ),
__( 'June' ),
__( 'July' ),
__( 'August' ),
__( 'September' ),
__( 'October' ),
__( 'November' ),
__( 'December' ),
],
'time_spans' => [
'day' => _x( 'day', 'Used when displaying the word "day" in "the last day" or "the first day"', 'tribe-events-calendar-pro' ),
'days' => _x( 'days', 'Used when displaying the word "days" in e.g. "every 3 days"', 'tribe-events-calendar-pro' ),
'week' => _x( 'week', 'Used when displaying the word "week" in "the last week" or "the first week"', 'tribe-events-calendar-pro' ),
'weeks' => _x( 'weeks', 'Used when displaying the word "weeks" in e.g. "every 3 weeks"', 'tribe-events-calendar-pro' ),
'month' => _x( 'month', 'Used when displaying the word "month" in e.g. "every month"', 'tribe-events-calendar-pro' ),
'months' => _x( 'months', 'Used when displaying the word "months" in e.g. "every 3 months"', 'tribe-events-calendar-pro' ),
'year' => _x( 'year', 'Used when displaying the word "year" in e.g. "every year"', 'tribe-events-calendar-pro' ),
'years' => _x( 'years', 'Used when displaying the word "years" in e.g. "every 2 years"', 'tribe-events-calendar-pro' ),
],
'collection_joiner' => _x( 'and', 'Joins the last item in a list of items (i.e. the "and" in Monday, Tuesday, and Wednesday)', 'tribe-events-calendar-pro' ),
'day_placeholder' => _x( '[day]', 'Placeholder text for a day of the week (or days of the week) before the user has selected any', 'tribe-events-calendar-pro' ),
'month_placeholder' => _x( '[month]', 'Placeholder text for a month (or months) before the user has selected any', 'tribe-events-calendar-pro' ),
'day_of_month' => _x( 'day %1$s', 'Describes a day of the month (e.g. "day 5" or "day 27")', 'tribe-events-calendar-pro' ),
'first_x' => _x( 'the first %1$s', 'Used when displaying: "the first Monday" or "the first day"', 'tribe-events-calendar-pro' ),
'second_x' => _x( 'the second %1$s', 'Used when displaying: "the second Monday" or "the second day"', 'tribe-events-calendar-pro' ),
'third_x' => _x( 'the third %1$s', 'Used when displaying: "the third Monday" or "the third day"', 'tribe-events-calendar-pro' ),
'fourth_x' => _x( 'the fourth %1$s', 'Used when displaying: "the fourth Monday" or "the fourth day"', 'tribe-events-calendar-pro' ),
'fifth_x' => _x( 'the fifth %1$s', 'Used when displaying: "the fifth Monday" or "the fifth day"', 'tribe-events-calendar-pro' ),
'last_x' => _x( 'the last %1$s', 'Used when displaying: "the last Monday" or "the last day"', 'tribe-events-calendar-pro' ),
'day' => _x( 'day', 'Used when displaying the word "day" in "the last day" or "the first day"', 'tribe-events-calendar-pro' ),
];
}
/**
* Convert the event recurrence meta into a human readable string
*
* @TODO: there's a great deal of duplication between this method and tribe_events_pro_admin.recurrence.update_rule_recurrence_text (events-recurrence.js)
* let's consider generating once (by JS?) and saving the result for re-use instead
*
* @param array $rule
* @param string $start_date
* @param int $event_id
*
* @return string human readable string
*/
public static function recurrenceToText( $rule, $start_date, $event_id ) {
$text = '';
$recurrence_strings = Tribe__Events__Pro__Recurrence__Strings::recurrence_strings();
$date_strings = self::date_strings();
$interval = 1;
$is_custom = false;
$same_time = true;
$same_day = true;
$year_filtered = false;
$rule['type'] = str_replace( ' ', '-', strtolower( $rule['type'] ) );
// The end-type may not be set in the cast of single-date recurrence rules, so default to an "on"-this-date type
$rule['end-type'] = isset( $rule['end-type'] ) ? str_replace( ' ', '-', strtolower( $rule['end-type'] ) ) : 'on';
$count = isset( $rule['end-count'] ) ? intval( $rule['end-count'] ) : 0;
$type = 'custom' == $rule['type'] ? strtolower( $rule['custom']['type'] ) : $rule['type'];
$start_date = strtotime( tribe_get_start_date( $event_id, true, Tribe__Date_Utils::DBDATETIMEFORMAT ) );
$end_date = strtotime( tribe_get_end_date( $event_id, true, Tribe__Date_Utils::DBDATETIMEFORMAT ) );
// if the type is "none", then there's no rules to parse
if ( 'none' === $rule['type'] ) {
return;
}
// If there isn't an end date, then let's assume this means never - unless it is a single date
// rule in which case 'never' doesn't make sense
if (
'on' === $rule['end-type']
&& empty( $rule['end'] )
&& 'date' !== $type
) {
$rule['end'] = 'never';
$rule['end-type'] = 'never';
}
// Gets the interval, defaulting to 1
$interval = intval( Tribe__Utils__Array::get( $rule, [ 'custom', 'interval' ], $interval ) );
$start_date = strtotime( tribe_get_start_date( $event_id, true, Tribe__Date_Utils::DBDATETIMEFORMAT ) );
$end_date = strtotime( tribe_get_end_date( $event_id, true, Tribe__Date_Utils::DBDATETIMEFORMAT ) );
// check if the recurring event time is different than the main event
if ( isset( $rule['custom'] ) && isset( $rule['same-time'] ) && 'no' === $rule['same-time'] ) {
$same_time = false;
$new_start_date = date( 'Y-m-d', $start_date );
// check if we have a legacy start format stored, 4.3 and older
if ( is_array( $rule['custom']['start-time'] ) ) {
$new_start_date .= ' ' . $rule['custom']['start-time']['hour'] . ':' . $rule['custom']['start-time']['minute'];
if ( isset( $rule['custom']['start-time']['meridian'] ) ) {
$new_start_date .= ' ' . $rule['custom']['start-time']['meridian'];
}
} else {
$new_start_date = ' ' . $rule['custom']['start-time'];
}
$start_date = strtotime( $new_start_date );
// legacy use of duration, 4.3 and older
if ( isset( $rule['custom']['duration'] ) ) {
try {
$end_date = new DateTime( '@' . $start_date );
$end_date->modify( '+' . absint( $rule['custom']['duration']['days'] ) . ' days' );
$end_date->modify( '+' . absint( $rule['custom']['duration']['hours'] ) . ' hours' );
$end_date->modify( '+' . absint( $rule['custom']['duration']['minutes'] ) . ' minutes' );
$end_date = $end_date->format( 'U' );
} catch ( Exception $e ) {
// $formatted_end will default to the "unspecified end date" text in this case
}
} else {
// @todo - this does not currently support end times on a different day
// events that span days are not currently functional, so, will follow up here when that is fixed
$end_date = strtotime( $start_date . ' ' . $rule['custom']['end-time'] );
}
}
$formatted_start = date( 'Y-m-d H:i:s', $start_date );
if (
isset( $rule['custom']['same-time'] )
&& 'no' === $rule['custom']['same-time']
) {
$start_time = date( get_option( 'time_format' ), strtotime( $rule['custom']['start-time'] ) );
} else {
$start_time = date( get_option( 'time_format' ), $start_date );
}
$days_of_week = null;
$month_names = null;
$month_day_description = null;
$day_number = date( 'j', $start_date );
$month_number = $day_number;
$key = $type;
if ( 'weekly' === $type ) {
$weekdays = [];
foreach ( $rule['custom']['week']['day'] as $day ) {
$weekdays[] = $date_strings['weekdays'][ $day - 1 ];
}
if ( empty( $weekdays ) ) {
$days_of_week = $date_strings['day_placeholder'];
} elseif ( 2 >= count( $weekdays ) ) {
$days_of_week = implode( " {$date_strings['collection_joiner']} ", $weekdays );
} else {
$days_of_week = implode( ', ', $weekdays );
$days_of_week = preg_replace( '/(.*),/', '$1 ' . $date_strings['collection_joiner'], $days_of_week );
}
} elseif ( 'monthly' === $type ) {
$same_day = empty( $rule['custom']['month']['same-day'] ) || 'yes' === $rule['custom']['month']['same-day'];
$month_number = Tribe__Utils__Array::get( $rule, [ 'custom', 'month', 'number' ], $month_number );
$month_day = Tribe__Utils__Array::get( $rule, [ 'custom', 'month', 'day' ], $day_number );
// need to check month number is a number
if ( ( $same_day && 1 == $interval ) || $month_number ) {
$key .= '-numeric';
} else {
$day_number = $month_number;
$month_day_description = '??????';
}
} elseif ( 'yearly' === $type ) {
// Select saves on a single field, and we have to explode it into an array for manipulation
if ( isset( $rule['custom']['year']['month'] ) ) {
if ( ! is_array( $rule['custom']['year']['month'] ) ) {
$rule['custom']['year']['month'] = (array) explode( ',', $rule['custom']['year']['month'] );
}
$rule['custom']['year']['month'] = array_map( 'intval', $rule['custom']['year']['month'] );
}
// this variable represents what relative day of the month
$month_number = isset( $rule['custom']['year']['number'] ) ? strtolower( $rule['custom']['year']['number'] ) : $month_number;
$same_day = ! isset( $rule['custom']['year']['filter'] ) || '1' === $rule['custom']['year']['filter'];
if ( ! empty( $rule['custom']['year']['month'] ) ) {
foreach ( $rule['custom']['year']['month'] as $month ) {
$months[] = $date_strings['months'][ $month - 1 ];
}
}
// build a string of month names
if ( ! $months ) {
$month_names = $date_strings['month_placeholder'];
} elseif ( 2 >= count( $months ) ) {
$month_names = implode( " {$date_strings['collection_joiner']} ", $months );
} else {
$month_names = implode( ', ', $months );
$month_names = preg_replace( '/(.*),/', '$1 ' . $date_strings['collection_joiner'], $month_names );
}
$year_day_of_month_numeric = false;
if ( $same_day && 1 == count( $months ) ) {
$start_month = date( 'n', $start_date );
if ( $start_month == $rule['custom']['year']['month'][0] ) {
$key .= '-numeric';
$year_day_of_month_numeric = true;
}
}
if ( is_numeric( $month_number ) || $year_day_of_month_numeric ) {
$day_number = $month_number;
} else {
$month_day = $rule['custom']['year']['day'];
$month_day_description = $date_strings[ $month_number . '_x' ];
if ( is_numeric( $month_day ) && $month_day > 0 ) {
$month_day = intval( $month_day );
$month_day_description = str_replace( '%1$s', $date_strings['weekdays'][ $month_day - 1 ], $month_day_description );
} elseif ( is_numeric( $month_day ) ) {
$month_day_description = str_replace( '%1$s', $date_strings['day'], $month_day_description );
} else {
$month_day_description = str_replace( '%1$s', $date_strings['day_placeholder'], $month_day_description );
}
}
}
$key .= '-' . $rule['end-type'];
$text = '';
/**
* Makes sure legacy Recurrence works as expected
* @see C#71907
*/
if ( ! empty( $recurrence_strings[ $key ] ) ) {
$text = $recurrence_strings[ $key ];
}
if ( empty( $text ) ) {
return;
}
// Handle different string conversions based on recurring end type.
if ( empty( $rule['end'] ) ) {
// If the events are single events, use the dates of those single instances.
if ( 'date' === $type && isset( $rule['custom']['date']['date'] ) ) {
$series_end = date_i18n( tribe_get_date_format( true ), strtotime( $rule['custom']['date']['date'] ) );
// Otherwise there's no end date specified.
} else {
$series_end = _x( 'an unspecified date', 'An unspecified end date', 'tribe-events-calendar-pro' );
}
} else {
$series_end = date_i18n( tribe_get_date_format( true ), strtotime( $rule['end'] ) );
}
$text = str_replace(
[
'[count]',
'[day_number]',
'[days_of_week]',
'[month_day_description]',
'[month_names]',
'[month_number]',
'[interval]',
'[series_end_date]',
'[start_date]',
'[start_time]',
'[single_date]',
// English-only simplification,
'1 day(s)',
'1 week(s)',
'1 month(s)',
'1 year(s)',
'day(s)',
'week(s)',
'month(s)',
'year(s)',
],
[
$count,
$day_number,
$days_of_week,
$month_day_description,
$month_names,
$month_number,
$interval,
$series_end,
$formatted_start,
$start_time,
$series_end,
// English-only simplification,
'day',
'week',
'month',
'year',
'days',
'weeks',
'months',
'years',
],
$text
);
return $text;
}
public static function init_non_core() {
}
/**
* Convert an array of day ids into a human readable string
*
* @param array $days The day ids
*
* @return The human readable string
*/
private static function daysToText( $days ) {
$day_words = [
__( 'Monday', 'tribe-events-calendar-pro' ),
__( 'Tuesday', 'tribe-events-calendar-pro' ),
__( 'Wednesday', 'tribe-events-calendar-pro' ),
__( 'Thursday', 'tribe-events-calendar-pro' ),
__( 'Friday', 'tribe-events-calendar-pro' ),
__( 'Saturday', 'tribe-events-calendar-pro' ),
__( 'Sunday', 'tribe-events-calendar-pro' ),
];
$count = count( $days );
$day_text = '';
for ( $i = 0; $i < $count; $i ++ ) {
if ( $count > 2 && $i == $count - 1 ) {
$day_text .= __( ', and', 'tribe-events-calendar-pro' ) . ' ';
} elseif ( $count == 2 && $i == $count - 1 ) {
$day_text .= ' ' . __( 'and', 'tribe-events-calendar-pro' ) . ' ';
} elseif ( $count > 2 && $i > 0 ) {
$day_text .= __( ',', 'tribe-events-calendar-pro' ) . ' ';
}
$day_text .= $day_words[ $days[ $i ] - 1 ] ? $day_words[ $days[ $i ] - 1 ] : 'day';
}
return $day_text;
}
/**
* Convert an array of month ids into a human readable string.
*
* @param array $months The month ids.
*
* @return $month_text The human readable string.
*/
private static function monthsToText( $months ) {
$month_words = [
__( 'January', 'tribe-events-calendar-pro' ),
__( 'February', 'tribe-events-calendar-pro' ),
__( 'March', 'tribe-events-calendar-pro' ),
__( 'April', 'tribe-events-calendar-pro' ),
__( 'May', 'tribe-events-calendar-pro' ),
__( 'June', 'tribe-events-calendar-pro' ),
__( 'July', 'tribe-events-calendar-pro' ),
__( 'August', 'tribe-events-calendar-pro' ),
__( 'September', 'tribe-events-calendar-pro' ),
__( 'October', 'tribe-events-calendar-pro' ),
__( 'November', 'tribe-events-calendar-pro' ),
__( 'December', 'tribe-events-calendar-pro' ),
];
$count = count( $months );
$month_text = '';
for ( $i = 0; $i < $count; $i ++ ) {
if ( $count > 2 && $i == $count - 1 ) {
$month_text .= __( ', and ', 'tribe-events-calendar-pro' );
} elseif ( $count == 2 && $i == $count - 1 ) {
$month_text .= __( ' and ', 'tribe-events-calendar-pro' );
} elseif ( $count > 2 && $i > 0 ) {
$month_text .= __( ', ', 'tribe-events-calendar-pro' );
}
$month_text .= $month_words[ $months[ $i ] - 1 ];
}
return $month_text;
}
/**
* Adds setting for hiding subsequent occurrences by default.
*
* @param array $fields The array of fields to inject the new field into.
* @param string $deprecated Deprecated parameter.
*
* @return array
*/
public static function inject_settings( $fields, $deprecated = null ) {
$fields = Tribe__Main::array_insert_before_key(
'enable_month_view_cache',
$fields,
[
'hideSubsequentRecurrencesDefault' => [
'type' => 'checkbox_bool',
'label' => __( 'Recurring event instances', 'tribe-events-calendar-pro' ),
'tooltip' => __( 'Show only the next instance of each recurring event (only affects list-style views).', 'tribe-events-calendar-pro' ),
'default' => false,
'validation_type' => 'boolean',
],
'userToggleSubsequentRecurrences' => [
'type' => 'checkbox_bool',
'label' => __( 'Front-end recurring event instances toggle', 'tribe-events-calendar-pro' ),
'tooltip' => __( 'Allow users to decide whether to show all instances of a recurring event on list-style views.', 'tribe-events-calendar-pro' ),
'default' => false,
'validation_type' => 'boolean',
],
'recurrenceMaxMonthsAfter' => [
'type' => 'text',
'size' => 'small',
'label' => __( 'Create recurring events in advance for', 'tribe-events-calendar-pro' ),
'tooltip' => __( 'Recurring events will be created this far in advance', 'tribe-events-calendar-pro' ),
'validation_type' => 'positive_int',
'default' => 24,
],
'recurrenceMaxMonthsBefore' => [
'type' => 'text',
'size' => 'small',
'label' => __( 'Clean up recurring events after', 'tribe-events-calendar-pro' ),
'tooltip' => __( 'Automatically remove recurring event instances older than this', 'tribe-events-calendar-pro' ),
'validation_type' => 'positive_int',
'default' => 24,
],
]
);
return $fields;
}
/**
* @param string $html
* @param Tribe__Field $field
*
* @return string
*/
public static function add_months_to_settings_field( $html, $field ) {
if ( in_array( $field->name, [ 'recurrenceMaxMonthsBefore', 'recurrenceMaxMonthsAfter' ] ) ) {
$html = __( ' months', 'tribe-events-calendar-pro' ) . $html;
}
return $html;
}
/**
* Combines the ['post'] piece of the $_REQUEST variable so it only has unique post ids.
*
*
* @return void
*/
public static function combineRecurringRequestIds() {
if ( isset( $_REQUEST['post_type'] ) && $_REQUEST['post_type'] == Tribe__Events__Main::POSTTYPE && ! empty( $_REQUEST['post'] ) && is_array( $_REQUEST['post'] ) ) {
$_REQUEST['post'] = array_unique( $_REQUEST['post'] );
}
}
/**
* @return void
*/
public static function reset_scheduler() {
if ( ! empty( self::$scheduler ) ) {
self::$scheduler->remove_hooks();
}
self::$scheduler = new Tribe__Events__Pro__Recurrence__Scheduler( tribe_get_option( 'recurrenceMaxMonthsBefore', 24 ), tribe_get_option( 'recurrenceMaxMonthsAfter', 24 ) );
self::$scheduler->add_hooks();
}
/**
* @return Tribe__Events__Pro__Recurrence__Scheduler
*/
public static function get_scheduler() {
if ( empty( self::$scheduler ) ) {
self::reset_scheduler();
}
return self::$scheduler;
}
/**
* Placed here for compatibility reasons. This can be removed
* when Events Calendar 3.2 or greater is released
*
* @todo Remove this method
*
* @param int $post_id
*
* @return string
* @see Tribe__Events__Main::get_series_start_date()
*/
private static function get_series_start_date( $post_id ) {
if ( method_exists( 'Tribe__Events__Main', 'get_series_start_date' ) ) {
return Tribe__Events__Main::get_series_start_date( $post_id );
}
$start_dates = tribe_get_recurrence_start_dates( $post_id );
return reset( $start_dates );
}
public static function update_child_thumbnails( $meta_id, $post_id, $meta_key, $meta_value ) {
static $recursing = false;
if ( $recursing || $meta_key != '_thumbnail_id' || ! tribe_is_recurring_event( $post_id ) ) {
return;
}
$recursing = true; // don't repeat this for child events
$children = self::children()->get_ids( $post_id );
foreach ( $children as $child_id ) {
update_post_meta( $child_id, $meta_key, $meta_value );
}
$recursing = false;
}
public static function remove_child_thumbnails( $meta_ids, $post_id, $meta_key, $meta_value ) {
static $recursing = false;
if ( $recursing || $meta_key != '_thumbnail_id' || ! tribe_is_recurring_event( $post_id ) ) {
return;
}
$recursing = true; // don't repeat this for child events
$children = self::children()->get_ids( $post_id );
foreach ( $children as $child_id ) {
delete_post_meta( $child_id, $meta_key, $meta_value );
}
$recursing = false;
}
/**
* Returns an instance of the Child Events class.
*
* @return Tribe__Events__Pro__Recurrence__Children_Events
*/
private static function children() {
if ( empty( self::$children ) ) {
self::$children = Tribe__Events__Pro__Recurrence__Children_Events::instance();
}
return self::$children;
}
/**
* Unhooks the class from all the actions and filters it did, or woruld, hook on
* in the `init` method.
*
* @since 4.7
*
* @param bool $only_core Whether to remove all actions and filters or only the core ones this specific recurring
* events implementation uses.
*/
public static function unhook( $only_core = true ) {
// Remove core filters first.
// Reset the scheduler, it will unhook it too.
self::reset_scheduler();
remove_action( 'tribe_events_update_meta', [ __CLASS__, 'updateRecurrenceMeta' ], 20 );
remove_action( 'wp_trash_post', [ __CLASS__, 'handle_trash_request' ] );
remove_action( 'untrashed_post', [ __CLASS__, 'handle_untrash_request' ] );
remove_action( 'admin_notices', [ __CLASS__, 'showRecurrenceErrorFlash' ] );
remove_action( 'tribe_recurring_event_error', [ __CLASS__, 'setupRecurrenceErrorMsg' ] );
remove_action( 'admin_action_tribe_split', [ __CLASS__, 'handle_split_request' ] );
remove_filter( 'posts_request', [ 'Tribe__Events__Pro__Recurrence__Queries', 'collapse_sql' ] );
// Unregister the notices.
Tribe__Admin__Notices::instance()->remove( 'editing-all-recurrences' );
Tribe__Admin__Notices::instance()->remove( 'created-recurrences' );
remove_action( 'load-edit.php', [ __CLASS__, 'combineRecurringRequestIds' ] );
remove_action( 'updated_post_meta', [ __CLASS__, 'update_child_thumbnails' ] );
remove_action( 'added_post_meta', [ __CLASS__, 'update_child_thumbnails' ] );
remove_action( 'deleted_post_meta', [ __CLASS__, 'remove_child_thumbnails' ] );
remove_action( 'update_option_' . Tribe__Main::OPTIONNAME, [
Tribe__Events__Pro__Recurrence__Old_Events_Cleaner::instance(),
'clean_up_old_recurring_events',
], 10 );
if ( $only_core ) {
return;
}
// Proceed to remove all filters, including the non-core ones.
remove_action( 'tribe_events_date_display', [ __CLASS__, 'loadRecurrenceData' ] );
remove_filter( 'get_edit_post_link', [ __CLASS__, 'filter_edit_post_link' ] );
remove_filter( 'preprocess_comment', [ __CLASS__, 'set_parent_for_recurring_event_comments' ] );
remove_action( 'pre_get_comments', [ __CLASS__, 'set_post_id_for_recurring_event_comment_queries' ] );
remove_action( 'comment_post_redirect', [ __CLASS__, 'fix_redirect_after_comment_is_posted' ] );
remove_action( 'wp_update_comment_count', [ __CLASS__, 'update_comment_counts_on_child_events' ] );
remove_filter( 'comments_array', [ __CLASS__, 'set_comments_array_on_child_events' ] );
remove_filter( 'manage_' . Tribe__Events__Main::POSTTYPE . '_posts_columns', [
__CLASS__,
'list_table_column_headers',
] );
remove_action( 'manage_' . Tribe__Events__Main::POSTTYPE . '_posts_custom_column', [
__CLASS__,
'populate_custom_list_table_columns',
], 10 );
remove_filter( 'post_class', [ __CLASS__, 'add_recurring_event_post_classes' ] );
remove_filter( 'post_row_actions', [ __CLASS__, 'edit_post_row_actions' ] );
remove_action( 'wp_before_admin_bar_render', [ __CLASS__, 'admin_bar_render' ] );
remove_filter( 'tec_general_settings_viewing_section', [ __CLASS__, 'inject_settings' ] );
remove_action( 'tribe_community_events_enqueue_resources', [ __CLASS__, 'enqueue_recurrence_data' ] );
remove_action( 'tribe_events_community_form_before_template', [ __CLASS__, 'output_recurrence_json_data' ] );
remove_filter( 'tribe_events_pro_output_recurrence_data', [ __CLASS__, 'maybe_fix_datepicker_output' ] );
remove_filter( 'tribe_events_pro_localize_script', [ Tribe__Events__Pro__Recurrence__Scripts::instance(), 'localize' ] );
}
}