From 3831459230fc9b5b30e364fb6cc4c24cae4001b1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 02:14:36 +0000 Subject: [PATCH 01/14] Initial plan From ecb07032f438772d8b5a227c4996708978afc16f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 02:19:15 +0000 Subject: [PATCH 02/14] Add validation for closing WordCamps - require Actual Attendees and End Date check Co-authored-by: dd32 <767313+dd32@users.noreply.github.com> --- .../wcpt/wcpt-wordcamp/wordcamp-admin.php | 136 +++++++++++++++++- 1 file changed, 135 insertions(+), 1 deletion(-) diff --git a/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php b/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php index 8624ee00f..144e17f9a 100644 --- a/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php +++ b/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php @@ -49,6 +49,16 @@ public function __construct() { 2 ); // after enforce_post_status. + add_filter( + 'wp_insert_post_data', + array( + $this, + 'require_complete_meta_to_close_wordcamp', + ), + 12, + 2 + ); // after require_complete_meta_to_publish_wordcamp. + // Filters - Subtype filtering on the WordCamp list table. add_filter( 'views_edit-wordcamp', array( $this, 'alter_views' ) ); add_action( 'parse_query', array( $this, 'filter_by_subtype' ) ); @@ -1107,10 +1117,86 @@ public function require_complete_meta_to_publish_wordcamp( $post_data, $post_dat return $post_data; } + /** + * Prevent WordCamps from being closed without required metadata + * + * @param array $post_data Sanitized post data. + * @param array $post_data_raw Raw post data. + * + * @return array + */ + public function require_complete_meta_to_close_wordcamp( $post_data, $post_data_raw ) { + if ( WCPT_POST_TYPE_ID != $post_data['post_type'] || empty( $post_data_raw['ID'] ) ) { + return $post_data; + } + + // Only apply validation when trying to transition to closed status. + if ( 'wcpt-closed' != $post_data['post_status'] ) { + return $post_data; + } + + $post = get_post( $post_data_raw['ID'] ); + if ( ! $post ) { + return $post_data; + } + + // Get the old status to check if this is an actual transition. + $old_status = $post->post_status; + if ( 'wcpt-closed' === $old_status ) { + // Already closed, allow saving other changes. + return $post_data; + } + + $required_closed_fields = $this->get_required_fields( 'closed', $post_data_raw['ID'] ); + $missing_fields = array(); + + foreach ( $required_closed_fields as $field ) { + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce check would have done in `metabox_save`. + $value = $_POST[ wcpt_key_to_str( $field, 'wcpt_' ) ] ?? ''; + + if ( empty( $value ) || 'null' == $value ) { + $missing_fields[] = $field; + } + } + + // Check if End Date has passed. + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce check would have done in `metabox_save`. + $end_date_value = $_POST['wcpt_end_date_yyyy_mm_dd'] ?? ''; + if ( empty( $end_date_value ) ) { + $end_date_value = get_post_meta( $post_data_raw['ID'], 'End Date (YYYY-mm-dd)', true ); + } + + $end_date_passed = true; + if ( ! empty( $end_date_value ) ) { + $end_date_at_midnight = strtotime( '23:59', $end_date_value ); + if ( $end_date_at_midnight > time() ) { + $end_date_passed = false; + } + } + + // If there are validation errors, prevent the status change. + if ( ! empty( $missing_fields ) || ! $end_date_passed ) { + $post_data['post_status'] = $old_status; + $this->active_admin_notices[] = 5; + + // Store missing fields for the error message. + if ( ! empty( $missing_fields ) ) { + set_transient( 'wcpt_missing_fields_' . $post_data_raw['ID'], $missing_fields, 60 ); + } + + // Store end date status for the error message. + if ( ! $end_date_passed ) { + set_transient( 'wcpt_end_date_not_passed_' . $post_data_raw['ID'], true, 60 ); + } + } + + return $post_data; + } + /** * Get a list of fields required to move to a certain post status * - * @param string $status 'needs-site' | 'scheduled' | 'any'. + * @param string $status 'needs-site' | 'scheduled' | 'closed' | 'any'. * * @return array */ @@ -1144,6 +1230,10 @@ public static function get_required_fields( $status, $post_id ) { // Required because the Events Widget needs a physical address in order to show events. $scheduled[] = self::get_address_key( $post_id ); + $closed = array( + 'Actual Attendees', + ); + switch ( $status ) { case 'needs-site': $required_fields = $needs_site; @@ -1153,6 +1243,10 @@ public static function get_required_fields( $status, $post_id ) { $required_fields = $scheduled; break; + case 'closed': + $required_fields = $closed; + break; + case 'any': default: $required_fields = array_merge( $needs_site, $scheduled ); @@ -1283,10 +1377,50 @@ public function get_admin_notices() { self::get_address_key( $post->ID ) ), ), + + 5 => array( + 'type' => 'error', + 'notice' => $this->get_close_validation_notice( $post->ID ), + ), ); } + /** + * Get the validation notice for closing a WordCamp + * + * @param int $post_id The post ID. + * + * @return string + */ + private function get_close_validation_notice( $post_id ) { + $missing_fields = get_transient( 'wcpt_missing_fields_' . $post_id ); + $end_date_not_passed = get_transient( 'wcpt_end_date_not_passed_' . $post_id ); + $errors = array(); + + if ( ! empty( $missing_fields ) ) { + $errors[] = sprintf( + __( 'The following required fields must be filled in: %s', 'wordcamporg' ), + implode( ', ', $missing_fields ) + ); + delete_transient( 'wcpt_missing_fields_' . $post_id ); + } + + if ( $end_date_not_passed ) { + $errors[] = __( 'The End Date must have passed before closing the event', 'wordcamporg' ); + delete_transient( 'wcpt_end_date_not_passed_' . $post_id ); + } + + if ( empty( $errors ) ) { + return __( 'This WordCamp cannot be closed at this time.', 'wordcamporg' ); + } + + $notice = __( 'This WordCamp cannot be closed. ', 'wordcamporg' ); + $notice .= implode( '. ', $errors ) . '.'; + + return $notice; + } + /** * Check if the post has geolocation data. * From 06912d7cd3e0f91ef34bb89156189ac6feff81c4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 02:21:46 +0000 Subject: [PATCH 03/14] Fix double period in validation error message Co-authored-by: dd32 <767313+dd32@users.noreply.github.com> --- .../wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php b/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php index 144e17f9a..f16377ad8 100644 --- a/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php +++ b/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php @@ -1416,7 +1416,7 @@ private function get_close_validation_notice( $post_id ) { } $notice = __( 'This WordCamp cannot be closed. ', 'wordcamporg' ); - $notice .= implode( '. ', $errors ) . '.'; + $notice .= rtrim( implode( '. ', $errors ), '.' ) . '.'; return $notice; } From 894be3221fb7ef150cda8baa7017295ffdda4c42 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 02:23:19 +0000 Subject: [PATCH 04/14] Add sanitization and handle both timestamp and string date formats Co-authored-by: dd32 <767313+dd32@users.noreply.github.com> --- .../plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php b/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php index f16377ad8..116e89d8f 100644 --- a/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php +++ b/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php @@ -1161,13 +1161,18 @@ public function require_complete_meta_to_close_wordcamp( $post_data, $post_data_ // Check if End Date has passed. // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce check would have done in `metabox_save`. - $end_date_value = $_POST['wcpt_end_date_yyyy_mm_dd'] ?? ''; + $end_date_value = isset( $_POST['wcpt_end_date_yyyy_mm_dd'] ) ? sanitize_text_field( wp_unslash( $_POST['wcpt_end_date_yyyy_mm_dd'] ) ) : ''; if ( empty( $end_date_value ) ) { $end_date_value = get_post_meta( $post_data_raw['ID'], 'End Date (YYYY-mm-dd)', true ); } $end_date_passed = true; if ( ! empty( $end_date_value ) ) { + // $end_date_value could be a timestamp from get_post_meta or a string from $_POST. + if ( ! is_numeric( $end_date_value ) ) { + $end_date_value = strtotime( $end_date_value ); + } + $end_date_at_midnight = strtotime( '23:59', $end_date_value ); if ( $end_date_at_midnight > time() ) { $end_date_passed = false; From e54f097bb05a0587ecd43ee09a02a2e218cef5b4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 02:25:55 +0000 Subject: [PATCH 05/14] Apply code review feedback: add sanitization, strict comparison, 23:59:59, remove unnecessary rtrim Co-authored-by: dd32 <767313+dd32@users.noreply.github.com> --- .../plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php b/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php index 116e89d8f..081366f70 100644 --- a/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php +++ b/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php @@ -1152,9 +1152,9 @@ public function require_complete_meta_to_close_wordcamp( $post_data, $post_data_ foreach ( $required_closed_fields as $field ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce check would have done in `metabox_save`. - $value = $_POST[ wcpt_key_to_str( $field, 'wcpt_' ) ] ?? ''; + $value = isset( $_POST[ wcpt_key_to_str( $field, 'wcpt_' ) ] ) ? sanitize_text_field( wp_unslash( $_POST[ wcpt_key_to_str( $field, 'wcpt_' ) ] ) ) : ''; - if ( empty( $value ) || 'null' == $value ) { + if ( empty( $value ) || 'null' === $value ) { $missing_fields[] = $field; } } @@ -1173,7 +1173,7 @@ public function require_complete_meta_to_close_wordcamp( $post_data, $post_data_ $end_date_value = strtotime( $end_date_value ); } - $end_date_at_midnight = strtotime( '23:59', $end_date_value ); + $end_date_at_midnight = strtotime( '23:59:59', $end_date_value ); if ( $end_date_at_midnight > time() ) { $end_date_passed = false; } @@ -1421,7 +1421,7 @@ private function get_close_validation_notice( $post_id ) { } $notice = __( 'This WordCamp cannot be closed. ', 'wordcamporg' ); - $notice .= rtrim( implode( '. ', $errors ), '.' ) . '.'; + $notice .= implode( '. ', $errors ) . '.'; return $notice; } From 99e7e0dc4b3f46986e7ff54ded82c93ebddeceac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 02:27:26 +0000 Subject: [PATCH 06/14] Fix error message formatting: add periods and simplify implode Co-authored-by: dd32 <767313+dd32@users.noreply.github.com> --- .../plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php b/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php index 081366f70..d77c2f701 100644 --- a/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php +++ b/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php @@ -1405,14 +1405,14 @@ private function get_close_validation_notice( $post_id ) { if ( ! empty( $missing_fields ) ) { $errors[] = sprintf( - __( 'The following required fields must be filled in: %s', 'wordcamporg' ), + __( 'The following required fields must be filled in: %s.', 'wordcamporg' ), implode( ', ', $missing_fields ) ); delete_transient( 'wcpt_missing_fields_' . $post_id ); } if ( $end_date_not_passed ) { - $errors[] = __( 'The End Date must have passed before closing the event', 'wordcamporg' ); + $errors[] = __( 'The End Date must have passed before closing the event.', 'wordcamporg' ); delete_transient( 'wcpt_end_date_not_passed_' . $post_id ); } @@ -1421,7 +1421,7 @@ private function get_close_validation_notice( $post_id ) { } $notice = __( 'This WordCamp cannot be closed. ', 'wordcamporg' ); - $notice .= implode( '. ', $errors ) . '.'; + $notice .= implode( ' ', $errors ); return $notice; } From 4e44fb2066bcf8e97637a4f91b45e0e8f44c9c3a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 02:28:52 +0000 Subject: [PATCH 07/14] Fix grammar in phpcs ignore comments Co-authored-by: dd32 <767313+dd32@users.noreply.github.com> --- .../wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php b/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php index d77c2f701..52bb8225c 100644 --- a/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php +++ b/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php @@ -1151,7 +1151,7 @@ public function require_complete_meta_to_close_wordcamp( $post_data, $post_data_ $missing_fields = array(); foreach ( $required_closed_fields as $field ) { - // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce check would have done in `metabox_save`. + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce check would have been done in `metabox_save`. $value = isset( $_POST[ wcpt_key_to_str( $field, 'wcpt_' ) ] ) ? sanitize_text_field( wp_unslash( $_POST[ wcpt_key_to_str( $field, 'wcpt_' ) ] ) ) : ''; if ( empty( $value ) || 'null' === $value ) { @@ -1160,7 +1160,7 @@ public function require_complete_meta_to_close_wordcamp( $post_data, $post_data_ } // Check if End Date has passed. - // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce check would have done in `metabox_save`. + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce check would have been done in `metabox_save`. $end_date_value = isset( $_POST['wcpt_end_date_yyyy_mm_dd'] ) ? sanitize_text_field( wp_unslash( $_POST['wcpt_end_date_yyyy_mm_dd'] ) ) : ''; if ( empty( $end_date_value ) ) { $end_date_value = get_post_meta( $post_data_raw['ID'], 'End Date (YYYY-mm-dd)', true ); From a8c037265869c082b38fa46670c73babdd062094 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 07:45:33 +0000 Subject: [PATCH 08/14] Refactor validation methods to reduce code duplication Co-authored-by: dd32 <767313+dd32@users.noreply.github.com> --- .../wcpt/wcpt-wordcamp/wordcamp-admin.php | 92 ++++++++++--------- 1 file changed, 50 insertions(+), 42 deletions(-) diff --git a/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php b/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php index 52bb8225c..8cd416cf4 100644 --- a/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php +++ b/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php @@ -49,16 +49,6 @@ public function __construct() { 2 ); // after enforce_post_status. - add_filter( - 'wp_insert_post_data', - array( - $this, - 'require_complete_meta_to_close_wordcamp', - ), - 12, - 2 - ); // after require_complete_meta_to_publish_wordcamp. - // Filters - Subtype filtering on the WordCamp list table. add_filter( 'views_edit-wordcamp', array( $this, 'alter_views' ) ); add_action( 'parse_query', array( $this, 'filter_by_subtype' ) ); @@ -1082,35 +1072,58 @@ public function require_complete_meta_to_publish_wordcamp( $post_data, $post_dat // The ID of the last site that was created before this rule went into effect, so that we don't apply the rule retroactively. $min_site_id = apply_filters( 'wcpt_require_complete_meta_min_site_id', '2416297' ); - $required_needs_site_fields = $this->get_required_fields( 'needs-site', $post_data_raw['ID'] ); - $required_scheduled_fields = $this->get_required_fields( 'scheduled', $post_data_raw['ID'] ); - // Needs Site. if ( 'wcpt-needs-site' == $post_data['post_status'] && absint( $post_data_raw['ID'] ) > $min_site_id ) { - foreach ( $required_needs_site_fields as $field ) { - - // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce check would have done in `metabox_save`. - $value = $_POST[ wcpt_key_to_str( $field, 'wcpt_' ) ] ?? ''; - - if ( empty( $value ) || 'null' == $value ) { - $post_data['post_status'] = 'wcpt-needs-email'; - $this->active_admin_notices[] = 1; - break; - } - } + $post_data = $this->validate_required_fields_for_status( + $post_data, + $post_data_raw, + 'needs-site', + 'wcpt-needs-email', + 1 + ); } // Scheduled. if ( 'wcpt-scheduled' == $post_data['post_status'] && isset( $post_data_raw['ID'] ) && absint( $post_data_raw['ID'] ) > $min_site_id ) { - foreach ( $required_scheduled_fields as $field ) { - // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce check would have done in `metabox_save`. - $value = $_POST[ wcpt_key_to_str( $field, 'wcpt_' ) ] ?? ''; - - if ( empty( $value ) || 'null' == $value ) { - $post_data['post_status'] = 'wcpt-needs-schedule'; - $this->active_admin_notices[] = 3; - break; - } + $post_data = $this->validate_required_fields_for_status( + $post_data, + $post_data_raw, + 'scheduled', + 'wcpt-needs-schedule', + 3 + ); + } + + // Closed. + if ( 'wcpt-closed' == $post_data['post_status'] ) { + $post_data = $this->validate_closed_status( $post_data, $post_data_raw ); + } + + return $post_data; + } + + /** + * Validate required fields for a specific status transition + * + * @param array $post_data Sanitized post data. + * @param array $post_data_raw Raw post data. + * @param string $status_type Status type for get_required_fields() ('needs-site', 'scheduled', 'closed'). + * @param string $fallback_status Status to revert to if validation fails. + * @param int $notice_id Admin notice ID to trigger. + * + * @return array Modified post data. + */ + private function validate_required_fields_for_status( $post_data, $post_data_raw, $status_type, $fallback_status, $notice_id ) { + $required_fields = $this->get_required_fields( $status_type, $post_data_raw['ID'] ); + + foreach ( $required_fields as $field ) { + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce check would have been done in `metabox_save`. + $value = isset( $_POST[ wcpt_key_to_str( $field, 'wcpt_' ) ] ) ? sanitize_text_field( wp_unslash( $_POST[ wcpt_key_to_str( $field, 'wcpt_' ) ] ) ) : ''; + + if ( empty( $value ) || 'null' === $value ) { + $post_data['post_status'] = $fallback_status; + $this->active_admin_notices[] = $notice_id; + break; } } @@ -1118,20 +1131,15 @@ public function require_complete_meta_to_publish_wordcamp( $post_data, $post_dat } /** - * Prevent WordCamps from being closed without required metadata + * Validate closed status transition with additional End Date check * * @param array $post_data Sanitized post data. * @param array $post_data_raw Raw post data. * - * @return array + * @return array Modified post data. */ - public function require_complete_meta_to_close_wordcamp( $post_data, $post_data_raw ) { - if ( WCPT_POST_TYPE_ID != $post_data['post_type'] || empty( $post_data_raw['ID'] ) ) { - return $post_data; - } - - // Only apply validation when trying to transition to closed status. - if ( 'wcpt-closed' != $post_data['post_status'] ) { + private function validate_closed_status( $post_data, $post_data_raw ) { + if ( empty( $post_data_raw['ID'] ) ) { return $post_data; } From 54525af19ca6e541196710d5f3e7f4bc01869b53 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 07:50:28 +0000 Subject: [PATCH 09/14] Show Actual Attendees field before event closes, make it readonly until event ends, add CampTix stats Co-authored-by: dd32 <767313+dd32@users.noreply.github.com> --- .../wcpt/wcpt-wordcamp/wordcamp-admin.php | 56 +++++++++++++------ 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php b/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php index 8cd416cf4..83e88ddad 100644 --- a/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php +++ b/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php @@ -480,15 +480,6 @@ public static function meta_keys( $meta_group = '' ) { unset( $retval['Series Event'] ); } - /* - * The "Actual Attendees" field is only able to be set after the event is concluded. - * - * get_post() allows this to target the editor, allowing for report export. - */ - if ( get_post() && get_post_status() !== 'wcpt-closed' ) { - unset( $retval['Actual Attendees'] ); - } - break; case 'all': @@ -526,14 +517,6 @@ public static function meta_keys( $meta_group = '' ) { unset( $retval['Series Event'] ); } - /* - * The "Actual Attendees" field is only able to be set after the event is concluded. - * - * get_post() allows this to target the editor, allowing for report export. - */ - if ( get_post() && get_post_status() !== 'wcpt-closed' ) { - unset( $retval['Actual Attendees'] ); - } $retval = array_merge( $retval, @@ -1330,6 +1313,25 @@ public static function get_protected_fields() { ); } + // Protect "Actual Attendees" field until the event has ended. + $post = get_post(); + if ( $post && WCPT_POST_TYPE_ID === $post->post_type ) { + $end_date = get_post_meta( $post->ID, 'End Date (YYYY-mm-dd)', true ); + + // If no end date is set, use the start date. + if ( empty( $end_date ) ) { + $end_date = get_post_meta( $post->ID, 'Start Date (YYYY-mm-dd)', true ); + } + + // If we have an end date and it hasn't passed yet, protect the field. + if ( ! empty( $end_date ) ) { + $end_date_at_midnight = strtotime( '23:59:59', $end_date ); + if ( $end_date_at_midnight > time() ) { + $protected_fields[] = 'Actual Attendees'; + } + } + } + return $protected_fields; } @@ -1737,6 +1739,26 @@ function wcpt_metabox( $meta_keys, $metabox ) { 'Series Event' => '(Campus Connect only) Event is part of a multi-venue or multi-session series (e.g., workshops held across several campuses)', ); + // Add CampTix ticket sales information to the Actual Attendees field message. + $post = get_post( $post_id ); + if ( $post && isset( $meta_keys['Actual Attendees'] ) ) { + $site_id = get_wordcamp_site_id( $post ); + if ( $site_id ) { + $camptix_stats = get_blog_option( $site_id, 'camptix_stats', array() ); + $attendees_attended = $camptix_stats['attended'] ?? 0; + $tickets_sold = $camptix_stats['sold'] ?? 0; + + if ( $attendees_attended > 0 || $tickets_sold > 0 ) { + $camptix_info = sprintf( + 'CampTix: %d attended, %d sold.', + $attendees_attended, + $tickets_sold + ); + $messages['Actual Attendees'] = 'Number of attendees who actually attended the event. ' . $camptix_info; + } + } + } + if ( 'wcpt_venue_info' === $metabox ) { $address_instructions = 'Please include the city, state/province and country.'; From 961c6e870e169a7d80db1d5968a6093c37960bd2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 08:02:46 +0000 Subject: [PATCH 10/14] Remove End Date validation, mark Actual Attendees as required when editable, update field messages Co-authored-by: dd32 <767313+dd32@users.noreply.github.com> --- .../wcpt/wcpt-wordcamp/wordcamp-admin.php | 79 +++++++------------ 1 file changed, 29 insertions(+), 50 deletions(-) diff --git a/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php b/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php index b5100c1d2..3facdd6f8 100644 --- a/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php +++ b/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php @@ -1153,40 +1153,13 @@ private function validate_closed_status( $post_data, $post_data_raw ) { } } - // Check if End Date has passed. - // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce check would have been done in `metabox_save`. - $end_date_value = isset( $_POST['wcpt_end_date_yyyy_mm_dd'] ) ? sanitize_text_field( wp_unslash( $_POST['wcpt_end_date_yyyy_mm_dd'] ) ) : ''; - if ( empty( $end_date_value ) ) { - $end_date_value = get_post_meta( $post_data_raw['ID'], 'End Date (YYYY-mm-dd)', true ); - } - - $end_date_passed = true; - if ( ! empty( $end_date_value ) ) { - // $end_date_value could be a timestamp from get_post_meta or a string from $_POST. - if ( ! is_numeric( $end_date_value ) ) { - $end_date_value = strtotime( $end_date_value ); - } - - $end_date_at_midnight = strtotime( '23:59:59', $end_date_value ); - if ( $end_date_at_midnight > time() ) { - $end_date_passed = false; - } - } - // If there are validation errors, prevent the status change. - if ( ! empty( $missing_fields ) || ! $end_date_passed ) { + if ( ! empty( $missing_fields ) ) { $post_data['post_status'] = $old_status; $this->active_admin_notices[] = 5; // Store missing fields for the error message. - if ( ! empty( $missing_fields ) ) { - set_transient( 'wcpt_missing_fields_' . $post_data_raw['ID'], $missing_fields, 60 ); - } - - // Store end date status for the error message. - if ( ! $end_date_passed ) { - set_transient( 'wcpt_end_date_not_passed_' . $post_data_raw['ID'], true, 60 ); - } + set_transient( 'wcpt_missing_fields_' . $post_data_raw['ID'], $missing_fields, 60 ); } return $post_data; @@ -1412,31 +1385,18 @@ public function get_admin_notices() { * @return string */ private function get_close_validation_notice( $post_id ) { - $missing_fields = get_transient( 'wcpt_missing_fields_' . $post_id ); - $end_date_not_passed = get_transient( 'wcpt_end_date_not_passed_' . $post_id ); - $errors = array(); + $missing_fields = get_transient( 'wcpt_missing_fields_' . $post_id ); if ( ! empty( $missing_fields ) ) { - $errors[] = sprintf( - __( 'The following required fields must be filled in: %s.', 'wordcamporg' ), + delete_transient( 'wcpt_missing_fields_' . $post_id ); + + return sprintf( + __( 'This WordCamp cannot be closed. The following required fields must be filled in: %s.', 'wordcamporg' ), implode( ', ', $missing_fields ) ); - delete_transient( 'wcpt_missing_fields_' . $post_id ); - } - - if ( $end_date_not_passed ) { - $errors[] = __( 'The End Date must have passed before closing the event.', 'wordcamporg' ); - delete_transient( 'wcpt_end_date_not_passed_' . $post_id ); - } - - if ( empty( $errors ) ) { - return __( 'This WordCamp cannot be closed at this time.', 'wordcamporg' ); } - $notice = __( 'This WordCamp cannot be closed. ', 'wordcamporg' ); - $notice .= implode( ' ', $errors ); - - return $notice; + return __( 'This WordCamp cannot be closed at this time.', 'wordcamporg' ); } /** @@ -1803,6 +1763,12 @@ function wcpt_metabox( $meta_keys, $metabox ) { global $post_id; $required_fields = WordCamp_Admin::get_required_fields( 'any', $post_id ); + $protected_fields = WordCamp_Admin::get_protected_fields(); + + // Add "Actual Attendees" to required fields when it's not protected (editable). + if ( ! in_array( 'Actual Attendees', $protected_fields, true ) && isset( $meta_keys['Actual Attendees'] ) ) { + $required_fields[] = 'Actual Attendees'; + } // @todo When you refactor meta_keys() to support changing labels -- see note in meta_keys() -- also make it support these notes. $messages = array( @@ -1818,9 +1784,17 @@ function wcpt_metabox( $meta_keys, $metabox ) { 'Series Event' => '(Campus Connect only) Event is part of a multi-venue or multi-session series (e.g., workshops held across several campuses)', ); - // Add CampTix ticket sales information to the Actual Attendees field message. + // Update the Actual Attendees field message based on whether it's protected and CampTix data. $post = get_post( $post_id ); if ( $post && isset( $meta_keys['Actual Attendees'] ) ) { + $is_protected = in_array( 'Actual Attendees', $protected_fields, true ); + + if ( $is_protected ) { + // Field is readonly - show message that it can't be set until after event concludes + $messages['Actual Attendees'] = 'This field cannot be set until after the event concludes.'; + } + + // Add CampTix ticket sales information regardless of protected status $site_id = get_wordcamp_site_id( $post ); if ( $site_id ) { $camptix_stats = get_blog_option( $site_id, 'camptix_stats', array() ); @@ -1833,7 +1807,12 @@ function wcpt_metabox( $meta_keys, $metabox ) { $attendees_attended, $tickets_sold ); - $messages['Actual Attendees'] = 'Number of attendees who actually attended the event. ' . $camptix_info; + + if ( $is_protected ) { + $messages['Actual Attendees'] .= ' ' . $camptix_info; + } else { + $messages['Actual Attendees'] = 'Number of attendees who actually attended the event. ' . $camptix_info; + } } } } From 4bd2a887fc2a9a3a7c6f5a8cd4ae440d70b47627 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 08:04:21 +0000 Subject: [PATCH 11/14] Remove trailing whitespace from blank lines Co-authored-by: dd32 <767313+dd32@users.noreply.github.com> --- .../plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php b/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php index 3facdd6f8..07aeba881 100644 --- a/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php +++ b/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php @@ -1764,7 +1764,7 @@ function wcpt_metabox( $meta_keys, $metabox ) { $required_fields = WordCamp_Admin::get_required_fields( 'any', $post_id ); $protected_fields = WordCamp_Admin::get_protected_fields(); - + // Add "Actual Attendees" to required fields when it's not protected (editable). if ( ! in_array( 'Actual Attendees', $protected_fields, true ) && isset( $meta_keys['Actual Attendees'] ) ) { $required_fields[] = 'Actual Attendees'; @@ -1788,12 +1788,12 @@ function wcpt_metabox( $meta_keys, $metabox ) { $post = get_post( $post_id ); if ( $post && isset( $meta_keys['Actual Attendees'] ) ) { $is_protected = in_array( 'Actual Attendees', $protected_fields, true ); - + if ( $is_protected ) { // Field is readonly - show message that it can't be set until after event concludes $messages['Actual Attendees'] = 'This field cannot be set until after the event concludes.'; } - + // Add CampTix ticket sales information regardless of protected status $site_id = get_wordcamp_site_id( $post ); if ( $site_id ) { From 7f11cb34f07c1a2b2cf6298e3131c0e83b05695d Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Fri, 13 Feb 2026 21:47:08 +1000 Subject: [PATCH 12/14] Add tests for closed status validation requiring Actual Attendees Tests the new validate_closed_status logic including: - Closing blocked when Actual Attendees is missing - Closing allowed when Actual Attendees is provided - Already-closed WordCamps can be resaved without re-validation - Actual Attendees field protected before event end date - Actual Attendees field editable after event end date - Start date used as fallback when no end date set - Missing fields stored in transient for error display Co-Authored-By: Claude Opus 4.6 --- .../plugins/wcpt/tests/bootstrap.php | 1 + .../wcpt-wordcamp/test-wordcamp-admin.php | 270 ++++++++++++++++++ 2 files changed, 271 insertions(+) create mode 100644 public_html/wp-content/plugins/wcpt/tests/wcpt-wordcamp/test-wordcamp-admin.php diff --git a/public_html/wp-content/plugins/wcpt/tests/bootstrap.php b/public_html/wp-content/plugins/wcpt/tests/bootstrap.php index 493149857..80085dfa2 100644 --- a/public_html/wp-content/plugins/wcpt/tests/bootstrap.php +++ b/public_html/wp-content/plugins/wcpt/tests/bootstrap.php @@ -12,6 +12,7 @@ function manually_load_plugins() { require_once dirname( dirname( dirname( __DIR__ ) ) ) . '/sunrise.php'; require_once dirname( __DIR__ ) . '/wcpt-wordcamp/wordcamp-new-site.php'; + require_once dirname( __DIR__ ) . '/wcpt-wordcamp/wordcamp-admin.php'; } tests_add_filter( 'muplugins_loaded', __NAMESPACE__ . '\manually_load_plugins' ); diff --git a/public_html/wp-content/plugins/wcpt/tests/wcpt-wordcamp/test-wordcamp-admin.php b/public_html/wp-content/plugins/wcpt/tests/wcpt-wordcamp/test-wordcamp-admin.php new file mode 100644 index 000000000..31bfc71ad --- /dev/null +++ b/public_html/wp-content/plugins/wcpt/tests/wcpt-wordcamp/test-wordcamp-admin.php @@ -0,0 +1,270 @@ +post->create( array( 'post_type' => WCPT_POST_TYPE_ID ) ); + + $fields = WordCamp_Admin::get_required_fields( 'closed', $post_id ); + + $this->assertContains( 'Actual Attendees', $fields ); + } + + /** + * @covers WordCamp_Admin::get_required_fields + */ + public function test_get_required_fields_closed_does_not_include_scheduled_fields() { + $post_id = self::factory()->post->create( array( 'post_type' => WCPT_POST_TYPE_ID ) ); + + $fields = WordCamp_Admin::get_required_fields( 'closed', $post_id ); + + $this->assertNotContains( 'Start Date (YYYY-mm-dd)', $fields ); + $this->assertNotContains( 'Location', $fields ); + } + + /** + * Closing a WordCamp should be blocked when Actual Attendees is not filled. + * + * @covers WordCamp_Admin::require_complete_meta_to_publish_wordcamp + */ + public function test_closing_blocked_without_actual_attendees() { + $post_id = self::factory()->post->create( array( + 'post_type' => WCPT_POST_TYPE_ID, + 'post_status' => 'wcpt-scheduled', + ) ); + + // Simulate no Actual Attendees in POST data. + $_POST = array(); + + $post_data = array( + 'post_type' => WCPT_POST_TYPE_ID, + 'post_status' => 'wcpt-closed', + ); + + $post_data_raw = array( + 'ID' => $post_id, + ); + + $result = self::$admin->require_complete_meta_to_publish_wordcamp( $post_data, $post_data_raw ); + + $this->assertNotEquals( 'wcpt-closed', $result['post_status'], 'Status should not be wcpt-closed when Actual Attendees is missing.' ); + $this->assertEquals( 'wcpt-scheduled', $result['post_status'], 'Status should revert to the previous status.' ); + } + + /** + * Closing a WordCamp should succeed when Actual Attendees is filled. + * + * @covers WordCamp_Admin::require_complete_meta_to_publish_wordcamp + */ + public function test_closing_allowed_with_actual_attendees() { + $post_id = self::factory()->post->create( array( + 'post_type' => WCPT_POST_TYPE_ID, + 'post_status' => 'wcpt-scheduled', + ) ); + + // Simulate Actual Attendees in POST data. + $_POST = array( + 'wcpt_actual_attendees' => '150', + ); + + $post_data = array( + 'post_type' => WCPT_POST_TYPE_ID, + 'post_status' => 'wcpt-closed', + ); + + $post_data_raw = array( + 'ID' => $post_id, + ); + + $result = self::$admin->require_complete_meta_to_publish_wordcamp( $post_data, $post_data_raw ); + + $this->assertEquals( 'wcpt-closed', $result['post_status'], 'Status should be wcpt-closed when Actual Attendees is provided.' ); + } + + /** + * Resaving an already-closed WordCamp should not require re-validation. + * + * @covers WordCamp_Admin::require_complete_meta_to_publish_wordcamp + */ + public function test_resaving_closed_wordcamp_allowed() { + $post_id = self::factory()->post->create( array( + 'post_type' => WCPT_POST_TYPE_ID, + 'post_status' => 'wcpt-closed', + ) ); + + // Simulate no Actual Attendees in POST data (e.g. field wasn't re-submitted). + $_POST = array(); + + $post_data = array( + 'post_type' => WCPT_POST_TYPE_ID, + 'post_status' => 'wcpt-closed', + ); + + $post_data_raw = array( + 'ID' => $post_id, + ); + + $result = self::$admin->require_complete_meta_to_publish_wordcamp( $post_data, $post_data_raw ); + + $this->assertEquals( 'wcpt-closed', $result['post_status'], 'Status should remain wcpt-closed when resaving an already-closed WordCamp.' ); + } + + /** + * Non-WordCamp post types should pass through without validation. + * + * @covers WordCamp_Admin::require_complete_meta_to_publish_wordcamp + */ + public function test_non_wordcamp_post_type_passes_through() { + $post_data = array( + 'post_type' => 'post', + 'post_status' => 'wcpt-closed', + ); + + $post_data_raw = array( + 'ID' => 1, + ); + + $result = self::$admin->require_complete_meta_to_publish_wordcamp( $post_data, $post_data_raw ); + + $this->assertEquals( $post_data, $result, 'Non-WordCamp post data should not be modified.' ); + } + + /** + * Actual Attendees should be protected before the event end date passes. + * + * @covers WordCamp_Admin::get_protected_fields + */ + public function test_actual_attendees_protected_before_end_date() { + $post_id = self::factory()->post->create( array( + 'post_type' => WCPT_POST_TYPE_ID, + 'post_status' => 'wcpt-scheduled', + ) ); + + // Set end date to the future. + update_post_meta( $post_id, 'End Date (YYYY-mm-dd)', strtotime( '+30 days' ) ); + + // get_protected_fields() uses get_post() which relies on the global $post. + $GLOBALS['post'] = get_post( $post_id ); + + $protected = WordCamp_Admin::get_protected_fields(); + + $this->assertContains( 'Actual Attendees', $protected, 'Actual Attendees should be protected before the event ends.' ); + + // Clean up. + unset( $GLOBALS['post'] ); + } + + /** + * Actual Attendees should not be protected after the event end date passes. + * + * @covers WordCamp_Admin::get_protected_fields + */ + public function test_actual_attendees_not_protected_after_end_date() { + $post_id = self::factory()->post->create( array( + 'post_type' => WCPT_POST_TYPE_ID, + 'post_status' => 'wcpt-scheduled', + ) ); + + // Set end date to the past. + update_post_meta( $post_id, 'End Date (YYYY-mm-dd)', strtotime( '-30 days' ) ); + + // get_protected_fields() uses get_post() which relies on the global $post. + $GLOBALS['post'] = get_post( $post_id ); + + $protected = WordCamp_Admin::get_protected_fields(); + + $this->assertNotContains( 'Actual Attendees', $protected, 'Actual Attendees should not be protected after the event ends.' ); + + // Clean up. + unset( $GLOBALS['post'] ); + } + + /** + * When no end date is set, start date should be used for protection. + * + * @covers WordCamp_Admin::get_protected_fields + */ + public function test_actual_attendees_uses_start_date_when_no_end_date() { + $post_id = self::factory()->post->create( array( + 'post_type' => WCPT_POST_TYPE_ID, + 'post_status' => 'wcpt-scheduled', + ) ); + + // Set only start date in the future, no end date. + update_post_meta( $post_id, 'Start Date (YYYY-mm-dd)', strtotime( '+30 days' ) ); + + $GLOBALS['post'] = get_post( $post_id ); + + $protected = WordCamp_Admin::get_protected_fields(); + + $this->assertContains( 'Actual Attendees', $protected, 'Actual Attendees should be protected using start date when no end date is set.' ); + + // Clean up. + unset( $GLOBALS['post'] ); + } + + /** + * Closing should store missing fields in a transient. + * + * @covers WordCamp_Admin::require_complete_meta_to_publish_wordcamp + */ + public function test_closing_stores_missing_fields_transient() { + $post_id = self::factory()->post->create( array( + 'post_type' => WCPT_POST_TYPE_ID, + 'post_status' => 'wcpt-scheduled', + ) ); + + $_POST = array(); + + $post_data = array( + 'post_type' => WCPT_POST_TYPE_ID, + 'post_status' => 'wcpt-closed', + ); + + $post_data_raw = array( + 'ID' => $post_id, + ); + + self::$admin->require_complete_meta_to_publish_wordcamp( $post_data, $post_data_raw ); + + $transient = get_transient( 'wcpt_missing_fields_' . $post_id ); + + $this->assertNotEmpty( $transient, 'Missing fields transient should be set.' ); + $this->assertContains( 'Actual Attendees', $transient, 'Transient should contain Actual Attendees.' ); + } + + /** + * Clean up $_POST after each test. + */ + public function tear_down() { + $_POST = array(); + + parent::tear_down(); + } +} From 59dbefb5d65a281bd10506b05692feebc61370f6 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Fri, 13 Feb 2026 21:55:11 +1000 Subject: [PATCH 13/14] Fix tests to bypass status transition hooks during post creation Create posts with draft status and update via $wpdb to avoid triggering WordCamp-specific hooks (Slack notifications) that fail without full environment setup. Co-Authored-By: Claude Opus 4.6 --- .../wcpt-wordcamp/test-wordcamp-admin.php | 69 +++++++++++-------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/public_html/wp-content/plugins/wcpt/tests/wcpt-wordcamp/test-wordcamp-admin.php b/public_html/wp-content/plugins/wcpt/tests/wcpt-wordcamp/test-wordcamp-admin.php index 31bfc71ad..3f296db6d 100644 --- a/public_html/wp-content/plugins/wcpt/tests/wcpt-wordcamp/test-wordcamp-admin.php +++ b/public_html/wp-content/plugins/wcpt/tests/wcpt-wordcamp/test-wordcamp-admin.php @@ -25,11 +25,41 @@ public static function set_up_before_class() { self::$admin = new WordCamp_Admin(); } + /** + * Create a WordCamp post with the given status, bypassing transition hooks + * that would otherwise fail due to missing Slack notification dependencies. + * + * @param string $status The desired post status. + * + * @return int Post ID. + */ + private function create_wordcamp( $status = 'draft' ) { + global $wpdb; + + // Create with draft status to avoid triggering transition hooks. + $post_id = self::factory()->post->create( array( + 'post_type' => WCPT_POST_TYPE_ID, + 'post_status' => 'draft', + ) ); + + // Set the desired status directly in the database to avoid triggering hooks. + if ( 'draft' !== $status ) { + $wpdb->update( + $wpdb->posts, + array( 'post_status' => $status ), + array( 'ID' => $post_id ) + ); + clean_post_cache( $post_id ); + } + + return $post_id; + } + /** * @covers WordCamp_Admin::get_required_fields */ public function test_get_required_fields_closed_includes_actual_attendees() { - $post_id = self::factory()->post->create( array( 'post_type' => WCPT_POST_TYPE_ID ) ); + $post_id = $this->create_wordcamp(); $fields = WordCamp_Admin::get_required_fields( 'closed', $post_id ); @@ -40,7 +70,7 @@ public function test_get_required_fields_closed_includes_actual_attendees() { * @covers WordCamp_Admin::get_required_fields */ public function test_get_required_fields_closed_does_not_include_scheduled_fields() { - $post_id = self::factory()->post->create( array( 'post_type' => WCPT_POST_TYPE_ID ) ); + $post_id = $this->create_wordcamp(); $fields = WordCamp_Admin::get_required_fields( 'closed', $post_id ); @@ -54,10 +84,7 @@ public function test_get_required_fields_closed_does_not_include_scheduled_field * @covers WordCamp_Admin::require_complete_meta_to_publish_wordcamp */ public function test_closing_blocked_without_actual_attendees() { - $post_id = self::factory()->post->create( array( - 'post_type' => WCPT_POST_TYPE_ID, - 'post_status' => 'wcpt-scheduled', - ) ); + $post_id = $this->create_wordcamp( 'wcpt-scheduled' ); // Simulate no Actual Attendees in POST data. $_POST = array(); @@ -83,10 +110,7 @@ public function test_closing_blocked_without_actual_attendees() { * @covers WordCamp_Admin::require_complete_meta_to_publish_wordcamp */ public function test_closing_allowed_with_actual_attendees() { - $post_id = self::factory()->post->create( array( - 'post_type' => WCPT_POST_TYPE_ID, - 'post_status' => 'wcpt-scheduled', - ) ); + $post_id = $this->create_wordcamp( 'wcpt-scheduled' ); // Simulate Actual Attendees in POST data. $_POST = array( @@ -113,10 +137,7 @@ public function test_closing_allowed_with_actual_attendees() { * @covers WordCamp_Admin::require_complete_meta_to_publish_wordcamp */ public function test_resaving_closed_wordcamp_allowed() { - $post_id = self::factory()->post->create( array( - 'post_type' => WCPT_POST_TYPE_ID, - 'post_status' => 'wcpt-closed', - ) ); + $post_id = $this->create_wordcamp( 'wcpt-closed' ); // Simulate no Actual Attendees in POST data (e.g. field wasn't re-submitted). $_POST = array(); @@ -161,10 +182,7 @@ public function test_non_wordcamp_post_type_passes_through() { * @covers WordCamp_Admin::get_protected_fields */ public function test_actual_attendees_protected_before_end_date() { - $post_id = self::factory()->post->create( array( - 'post_type' => WCPT_POST_TYPE_ID, - 'post_status' => 'wcpt-scheduled', - ) ); + $post_id = $this->create_wordcamp(); // Set end date to the future. update_post_meta( $post_id, 'End Date (YYYY-mm-dd)', strtotime( '+30 days' ) ); @@ -186,10 +204,7 @@ public function test_actual_attendees_protected_before_end_date() { * @covers WordCamp_Admin::get_protected_fields */ public function test_actual_attendees_not_protected_after_end_date() { - $post_id = self::factory()->post->create( array( - 'post_type' => WCPT_POST_TYPE_ID, - 'post_status' => 'wcpt-scheduled', - ) ); + $post_id = $this->create_wordcamp(); // Set end date to the past. update_post_meta( $post_id, 'End Date (YYYY-mm-dd)', strtotime( '-30 days' ) ); @@ -211,10 +226,7 @@ public function test_actual_attendees_not_protected_after_end_date() { * @covers WordCamp_Admin::get_protected_fields */ public function test_actual_attendees_uses_start_date_when_no_end_date() { - $post_id = self::factory()->post->create( array( - 'post_type' => WCPT_POST_TYPE_ID, - 'post_status' => 'wcpt-scheduled', - ) ); + $post_id = $this->create_wordcamp(); // Set only start date in the future, no end date. update_post_meta( $post_id, 'Start Date (YYYY-mm-dd)', strtotime( '+30 days' ) ); @@ -235,10 +247,7 @@ public function test_actual_attendees_uses_start_date_when_no_end_date() { * @covers WordCamp_Admin::require_complete_meta_to_publish_wordcamp */ public function test_closing_stores_missing_fields_transient() { - $post_id = self::factory()->post->create( array( - 'post_type' => WCPT_POST_TYPE_ID, - 'post_status' => 'wcpt-scheduled', - ) ); + $post_id = $this->create_wordcamp( 'wcpt-scheduled' ); $_POST = array(); From e7f819df0389f11b9f5fb69b35038c220757a009 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Fri, 13 Feb 2026 22:40:27 +1000 Subject: [PATCH 14/14] Fix PHPCS errors in wordcamp-admin and tests - Remove double empty line in meta keys definition - Remove trailing whitespace on blank lines - Add full-stop punctuation to inline comments - Add phpcs:ignore for global $post override in tests Co-Authored-By: Claude Opus 4.6 --- .../wcpt/tests/wcpt-wordcamp/test-wordcamp-admin.php | 3 +++ .../plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php | 11 +++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/public_html/wp-content/plugins/wcpt/tests/wcpt-wordcamp/test-wordcamp-admin.php b/public_html/wp-content/plugins/wcpt/tests/wcpt-wordcamp/test-wordcamp-admin.php index 3f296db6d..b36e17fc8 100644 --- a/public_html/wp-content/plugins/wcpt/tests/wcpt-wordcamp/test-wordcamp-admin.php +++ b/public_html/wp-content/plugins/wcpt/tests/wcpt-wordcamp/test-wordcamp-admin.php @@ -188,6 +188,7 @@ public function test_actual_attendees_protected_before_end_date() { update_post_meta( $post_id, 'End Date (YYYY-mm-dd)', strtotime( '+30 days' ) ); // get_protected_fields() uses get_post() which relies on the global $post. + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Required to set context for get_protected_fields(). $GLOBALS['post'] = get_post( $post_id ); $protected = WordCamp_Admin::get_protected_fields(); @@ -210,6 +211,7 @@ public function test_actual_attendees_not_protected_after_end_date() { update_post_meta( $post_id, 'End Date (YYYY-mm-dd)', strtotime( '-30 days' ) ); // get_protected_fields() uses get_post() which relies on the global $post. + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Required to set context for get_protected_fields(). $GLOBALS['post'] = get_post( $post_id ); $protected = WordCamp_Admin::get_protected_fields(); @@ -231,6 +233,7 @@ public function test_actual_attendees_uses_start_date_when_no_end_date() { // Set only start date in the future, no end date. update_post_meta( $post_id, 'Start Date (YYYY-mm-dd)', strtotime( '+30 days' ) ); + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Required to set context for get_protected_fields(). $GLOBALS['post'] = get_post( $post_id ); $protected = WordCamp_Admin::get_protected_fields(); diff --git a/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php b/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php index 07aeba881..6703d562b 100644 --- a/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php +++ b/public_html/wp-content/plugins/wcpt/wcpt-wordcamp/wordcamp-admin.php @@ -520,7 +520,6 @@ public static function meta_keys( $meta_group = '' ) { unset( $retval['Series Event'] ); } - $retval = array_merge( $retval, array( @@ -1293,7 +1292,7 @@ public static function get_protected_fields() { $post = get_post(); if ( $post && WCPT_POST_TYPE_ID === $post->post_type ) { $end_date = get_post_meta( $post->ID, 'End Date (YYYY-mm-dd)', true ); - + // If no end date is set, use the start date. if ( empty( $end_date ) ) { $end_date = get_post_meta( $post->ID, 'Start Date (YYYY-mm-dd)', true ); @@ -1389,7 +1388,7 @@ private function get_close_validation_notice( $post_id ) { if ( ! empty( $missing_fields ) ) { delete_transient( 'wcpt_missing_fields_' . $post_id ); - + return sprintf( __( 'This WordCamp cannot be closed. The following required fields must be filled in: %s.', 'wordcamporg' ), implode( ', ', $missing_fields ) @@ -1790,11 +1789,11 @@ function wcpt_metabox( $meta_keys, $metabox ) { $is_protected = in_array( 'Actual Attendees', $protected_fields, true ); if ( $is_protected ) { - // Field is readonly - show message that it can't be set until after event concludes + // Field is readonly - show message that it can't be set until after event concludes. $messages['Actual Attendees'] = 'This field cannot be set until after the event concludes.'; } - // Add CampTix ticket sales information regardless of protected status + // Add CampTix ticket sales information regardless of protected status. $site_id = get_wordcamp_site_id( $post ); if ( $site_id ) { $camptix_stats = get_blog_option( $site_id, 'camptix_stats', array() ); @@ -1807,7 +1806,7 @@ function wcpt_metabox( $meta_keys, $metabox ) { $attendees_attended, $tickets_sold ); - + if ( $is_protected ) { $messages['Actual Attendees'] .= ' ' . $camptix_info; } else {