diff --git a/public_html/wp-content/plugins/camptix/camptix.php b/public_html/wp-content/plugins/camptix/camptix.php
index 7209b9a39..316e78f5b 100644
--- a/public_html/wp-content/plugins/camptix/camptix.php
+++ b/public_html/wp-content/plugins/camptix/camptix.php
@@ -115,6 +115,11 @@ function init() {
// Our main shortcode
add_shortcode( 'camptix', array( $this, 'shortcode_callback' ) );
+ // Prevent shortcode removal, slug change and page removal when tickets have been sold
+ add_action( 'wp_insert_post_empty_content', array( $this, 'maybe_prevent_tickets_page_update' ), 10, 2 );
+ add_action( 'admin_notices', array( $this, 'show_tickets_page_update_warning' ) );
+ add_action( 'admin_footer', array( $this, 'show_tickets_page_update_warning_block_editor' ) );
+
// Hack to avoid object caching, see revenue report.
add_filter( 'get_post_metadata', array( $this, 'get_post_metadata' ), 10, 4 );
@@ -5395,6 +5400,198 @@ function shortcode_callback( $atts ) {
return $this->shortcode_contents;
}
+ /**
+ * Detect condition where tickets page updates might break things.
+ *
+ * @param integer $post_id Post ID to test.
+ *
+ * @return boolean True if updating page can break things.
+ */
+ public function should_prevent_tickets_page_update( $post_id ) {
+ if ( wp_is_post_revision( $post_id ) ) {
+ return false;
+ }
+
+ if ( 'page' !== get_post_type( $post_id ) ) {
+ return false;
+ }
+
+ // Get the post data as it is, before update.
+ $post_before = get_post( $post_id );
+
+ // Allow save if not tickets page.
+ if ( ! has_shortcode( $post_before->post_content, 'camptix' ) ) {
+ return false;
+ }
+
+ // Allow save until the page has been published.
+ if ( 'publish' !== $post_before->post_status ) {
+ return false;
+ }
+
+ // Allow save if no tickets bought yet.
+ if ( ! wp_count_posts( 'tix_attendee' )->publish ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * On tickets page update, check if updates are something that can break things after tickets page
+ * have been published and tickets have been sold. Camptix send ticket emails with links to the
+ * tickets page, and certain changes to the page do break those links. By not allowing certain updates,
+ * we try to mitigate the risk of organisers doing odd stuff after ticket sales has opened.
+ *
+ * Hooked filter `wp_insert_post_empty_content` only short circuits the save, so we need to handle
+ * sending meaningful error message ourselves. Otherwise the post wouldn't just update and user would
+ * get confused with not seeing the applied changes.
+ *
+ * @param boolean $maybe_empty Whether the post should be considered "empty".
+ * @param array $postarr Array of post data.
+ *
+ * @return boolean Whether the post should be considered "empty". When update should be prevented, send error message.
+ */
+ public function maybe_prevent_tickets_page_update( $maybe_empty, $postarr ) {
+ if ( ! $this->should_prevent_tickets_page_update( $postarr['ID'] ) ) {
+ return $maybe_empty;
+ }
+
+ // Cannot remove the camptix shortcode anymore.
+ if ( ! has_shortcode( $postarr['post_content'], 'camptix' ) ) {
+ $maybe_empty = true;
+ $message =
+ __( 'You cannot remove the camptix
shortcode from the page because tickets have been sold.', 'wordcamporg' ) . ' ' .
+ __( 'Doing that would break the links on ticket emails sent to attendees.', 'wordcamporg' );
+ }
+
+ // Cannot change the visibility of the page anymore.
+ if ( 'publish' !== $postarr['post_status'] || ! empty( $postarr['post_password'] ) ) {
+ $maybe_empty = true;
+ $message =
+ __( 'You cannot unpublish or make the page private because tickets have been sold.', 'wordcamporg' ) . ' ' .
+ __( 'Doing that would break the links on ticket emails sent to attendees.', 'wordcamporg' );
+ }
+
+ // Cannot change the slug anymore.
+ if ( $postarr['post_name'] !== $post_before->post_name ) {
+ $maybe_empty = true;
+ $message =
+ __( 'You cannot change the page slug because tickets have been sold.', 'wordcamporg' ) . ' ' .
+ __( 'Doing that would break the links on ticket emails sent to attendees.', 'wordcamporg' );
+ }
+
+ // Should update be prevented?
+ if ( $maybe_empty ) {
+ if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
+ /**
+ * If page should not be updated and save request comes from REST API (eg. block editor), send error message
+ * and status code to prevent the editor showing success message.
+ */
+ add_filter( 'rest_post_dispatch', function( $response ) use ( $message ) {
+ $response->set_data( array(
+ 'code' => 'prevented_tickets_page_breakage',
+ 'message' => $message,
+ 'data' => array(
+ 'status' => 400,
+ ),
+ ) );
+
+ return $response;
+ } );
+ } else {
+ // Save request is coming from classic editor or quick edit, send error message.
+ wp_die( $message );
+ }
+ }
+
+ return $maybe_empty;
+ }
+
+ /**
+ * Get the message shown in editor views.
+ * NB! Block editor notices do not support HTML and all tags will be removed.
+ *
+ * @return string Message content.
+ */
+ public function get_prevent_tickets_page_update_warning_message() {
+ return __( 'You cannot remove camptix
shortcode, unpublish the page, make it private or change the slug because tickets have been sold.', 'wordcamporg' ) . ' ' .
+ __( 'Doing that would break the links on ticket emails sent to attendees.', 'wordcamporg' );
+ }
+
+ /**
+ * Maybe show the warning message on block editor when editing tickets page.
+ */
+ public function show_tickets_page_update_warning_block_editor() {
+ $screen = get_current_screen();
+
+ if ( ! $screen->is_block_editor() ) {
+ return;
+ }
+
+ if ( 'post' !== $screen->base ) {
+ return;
+ }
+
+ if ( ! isset( $_GET['post'] ) ) {
+ return;
+ }
+
+ /**
+ * Check that page meets conditions when update should be prevented.
+ * This check takes also care of checking that the page is actually tickets page.
+ */
+ if ( ! $this->should_prevent_tickets_page_update( $_GET['post'] ) ) {
+ return;
+ }
+
+ $message = $this->get_prevent_tickets_page_update_warning_message(); ?>
+
+
+ base ) {
+ return;
+ }
+
+ if ( ! isset( $_GET['post'] ) ) {
+ return;
+ }
+
+ /**
+ * Check that page meets conditions when update should be prevented.
+ * This check takes also care of checking that the page is actually tickets page.
+ */
+ if ( ! $this->should_prevent_tickets_page_update( $_GET['post'] ) ) {
+ return;
+ }
+
+ $class = 'notice notice-warning';
+ $message = $this->get_prevent_tickets_page_update_warning_message();
+
+ if ( ! current_user_can( 'manage_options' ) ) {
+ $message = wp_strip_all_tags( $message );
+ }
+
+ printf( '
%2$s