Series): list of series, "edit this and future", delete, and the uninstall choice. * Flow: form (nonce field) -> admin-post.php -> recurkit_handle_*() -> redirect with a status flag -> notice. * * @package recurkit */ defined( 'ABSPATH' ) || exit; /** Capability TEC gives editors and administrators for other people's events. */ const RECURKIT_CAP = 'edit_others_tribe_events'; /** Where the Pro add-on is sold. Shown as one link on the Series screen. */ const RECURKIT_PRO_URL = 'https://brightlabs.app/apps/recurkit/'; /** * Admin notice for a status flag. Unknown flags print nothing. * * @param string $status Status flag from the redirect. */ function recurkit_render_status_notice( string $status ): void { $notices = array( 'updated' => array( 'notice-success', __( 'The occurrences were updated.', 'recurkit' ) ), 'deleted' => array( 'notice-success', __( 'The series was deleted.', 'recurkit' ) ), 'saved' => array( 'notice-success', __( 'Settings saved.', 'recurkit' ) ), 'unchanged' => array( 'notice-info', __( 'Nothing to change: the form matched the chosen occurrence.', 'recurkit' ) ), 'invalid' => array( 'notice-error', __( 'Nothing was changed: check the event, the times and the venue.', 'recurkit' ) ), ); if ( ! isset( $notices[ $status ] ) ) { return; } printf( '

%2$s

', esc_attr( $notices[ $status ][0] ), esc_html( $notices[ $status ][1] ) ); } /** Renders the Series screen. */ function recurkit_render_admin_page(): void { if ( ! current_user_can( RECURKIT_CAP ) ) { wp_die( esc_html__( 'You do not have permission to view this page.', 'recurkit' ), '', array( 'response' => 403 ) ); } // Display-only values: the status is checked against an allowlist, the event id is checked below. Nothing changes. $status = isset( $_GET['recurkit_status'] ) ? sanitize_key( wp_unslash( $_GET['recurkit_status'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $event = isset( $_GET['event'] ) ? absint( wp_unslash( $_GET['event'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>

= $today ) { return (int) $id; } } return (int) end( $ids ); } /** * "Edit this and future" form, plus a link to edit each occurrence on its own. * * @param int $event Occurrence to start from. */ function recurkit_render_edit_form( int $event ): void { $ids = recurkit_series_event_ids( (string) get_post_meta( $event, '_recurkit_series', true ) ); $post = get_post( $event ); $start = (string) get_post_meta( $event, '_EventStartDate', true ); $end = (string) get_post_meta( $event, '_EventEndDate', true ); $venue = (int) get_post_meta( $event, '_EventVenueID', true ); $venues = get_posts( array( 'post_type' => 'tribe_venue', 'post_status' => 'publish', 'numberposts' => 200, 'orderby' => 'title', 'order' => 'ASC', ) ); ?>

403 ) ); } check_admin_referer( 'recurkit_update', 'recurkit_nonce' ); $event = isset( $post['recurkit_event'] ) ? absint( $post['recurkit_event'] ) : 0; if ( ! $event || '' === (string) get_post_meta( $event, '_recurkit_series', true ) ) { return 'invalid'; } foreach ( recurkit_series_event_ids( (string) get_post_meta( $event, '_recurkit_series', true ) ) as $id ) { if ( ! current_user_can( 'edit_post', $id ) ) { wp_die( esc_html__( 'You do not have permission to change these events.', 'recurkit' ), '', array( 'response' => 403 ) ); } } // Only fields the user changed against the picked occurrence are applied, so later occurrences edited on their own // keep their own text, venue and times. $picked = get_post( $event ); $fields = array(); if ( isset( $post['recurkit_title'] ) ) { $title = sanitize_text_field( wp_unslash( $post['recurkit_title'] ) ); if ( $title !== $picked->post_title ) { $fields['title'] = $title; } } if ( isset( $post['recurkit_content'] ) ) { $raw = (string) wp_unslash( $post['recurkit_content'] ); if ( $raw !== $picked->post_content ) { $fields['content'] = current_user_can( 'unfiltered_html' ) ? $raw : wp_kses_post( $raw ); } } $venue = isset( $post['recurkit_venue'] ) ? absint( $post['recurkit_venue'] ) : 0; if ( $venue === (int) get_post_meta( $event, '_EventVenueID', true ) ) { $venue = 0; } if ( $venue ) { if ( 'tribe_venue' !== get_post_type( $venue ) ) { return 'invalid'; } $fields['venue'] = $venue; } $start = isset( $post['recurkit_start_time'] ) ? sanitize_text_field( wp_unslash( $post['recurkit_start_time'] ) ) : ''; $end = isset( $post['recurkit_end_time'] ) ? sanitize_text_field( wp_unslash( $post['recurkit_end_time'] ) ) : ''; $was_start = substr( (string) get_post_meta( $event, '_EventStartDate', true ), 11, 5 ); $was_end = substr( (string) get_post_meta( $event, '_EventEndDate', true ), 11, 5 ); if ( ( '' !== $start || '' !== $end ) && ( $start !== $was_start || $end !== $was_end ) ) { if ( ! recurkit_is_hm( $start ) || ! recurkit_is_hm( $end ) ) { return 'invalid'; } $fields['start_time'] = $start; $fields['end_time'] = $end; } if ( ! $fields ) { return 'unchanged'; } return recurkit_update_from( $event, $fields ) ? 'updated' : 'invalid'; } /** * Deletes a series. Order: capability, nonce, sanitize, per-event rights, delete. * * @param array $post Raw POST data. * @return string Status flag. */ function recurkit_process_delete( array $post ): string { if ( ! current_user_can( RECURKIT_CAP ) ) { wp_die( esc_html__( 'You do not have permission to delete events.', 'recurkit' ), '', array( 'response' => 403 ) ); } check_admin_referer( 'recurkit_delete', 'recurkit_nonce' ); $series = isset( $post['recurkit_series'] ) ? sanitize_text_field( wp_unslash( $post['recurkit_series'] ) ) : ''; if ( ! preg_match( '/^[0-9a-f-]{36}$/', $series ) ) { return 'invalid'; } $ids = recurkit_series_event_ids( $series ); foreach ( $ids as $id ) { if ( ! current_user_can( 'delete_post', $id ) ) { wp_die( esc_html__( 'You do not have permission to delete these events.', 'recurkit' ), '', array( 'response' => 403 ) ); } } return ( $ids && recurkit_delete_series( $series ) ) ? 'deleted' : 'invalid'; } /** * Stores the uninstall choice. * * @param array $post Raw POST data. * @return string Status flag. */ function recurkit_process_settings( array $post ): string { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have permission to change these settings.', 'recurkit' ), '', array( 'response' => 403 ) ); } check_admin_referer( 'recurkit_settings', 'recurkit_nonce' ); $mode = isset( $post['recurkit_uninstall'] ) ? sanitize_key( wp_unslash( $post['recurkit_uninstall'] ) ) : ''; recurkit_set_uninstall_mode( 'delete' === $mode ? 'delete' : 'keep' ); return 'saved'; } /** * Sends the browser back to the Series screen with a status flag. * * @param string $status Status flag. */ function recurkit_back( string $status ): void { wp_safe_redirect( add_query_arg( 'recurkit_status', $status, admin_url( 'edit.php?post_type=tribe_events&page=recurkit-series' ) ) ); exit; } /** The admin-post.php entry point for "edit this and future". */ function recurkit_handle_update(): void { recurkit_back( recurkit_process_update( $_POST ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified inside. } add_action( 'admin_post_recurkit_update', 'recurkit_handle_update' ); /** The admin-post.php entry point for deleting a series. */ function recurkit_handle_delete(): void { recurkit_back( recurkit_process_delete( $_POST ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified inside. } add_action( 'admin_post_recurkit_delete', 'recurkit_handle_delete' ); /** The admin-post.php entry point for the uninstall choice. */ function recurkit_handle_settings(): void { recurkit_back( recurkit_process_settings( $_POST ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified inside. } add_action( 'admin_post_recurkit_settings', 'recurkit_handle_settings' );