Changeset 13529 for sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/wporg-gp-translation-events.php
- Timestamp:
- 04/15/2024 01:37:55 PM (22 months ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/wporg-gp-translation-events.php
r13298 r13529 25 25 use WP_Post; 26 26 use WP_Query; 27 use Wporg\TranslationEvents\Attendee\Attendee; 28 use Wporg\TranslationEvents\Attendee\Attendee_Repository; 29 use Wporg\TranslationEvents\Event\Event_Form_Handler; 30 use Wporg\TranslationEvents\Event\Event_Repository_Cached; 31 use Wporg\TranslationEvents\Event\Event_Repository_Interface; 27 32 28 33 class Translation_Events { 29 public const CPT = 'translation_event'; 30 public const USER_META_KEY_ATTENDING = 'translation-events-attending'; 31 32 public static function get_instance() { 34 public const CPT = 'translation_event'; 35 36 public static function get_instance(): Translation_Events { 33 37 static $instance = null; 34 38 if ( null === $instance ) { 39 require_once __DIR__ . '/autoload.php'; 35 40 $instance = new self(); 36 41 } … … 38 43 } 39 44 45 public static function get_event_repository(): Event_Repository_Interface { 46 static $event_repository = null; 47 if ( null === $event_repository ) { 48 $event_repository = new Event_Repository_Cached( self::get_attendee_repository() ); 49 } 50 return $event_repository; 51 } 52 53 public static function get_attendee_repository(): Attendee_Repository { 54 static $attendee_repository = null; 55 if ( null === $attendee_repository ) { 56 $attendee_repository = new Attendee_Repository(); 57 } 58 return $attendee_repository; 59 } 60 40 61 public function __construct() { 41 \add_action( 'wp_ajax_submit_event_ajax', array( $this, 'submit_event_ajax' ) ); 42 \add_action( 'wp_ajax_nopriv_submit_event_ajax', array( $this, 'submit_event_ajax' ) ); 43 \add_action( 'wp_enqueue_scripts', array( $this, 'register_translation_event_js' ) ); 44 \add_action( 'init', array( $this, 'register_event_post_type' ) ); 45 \add_action( 'add_meta_boxes', array( $this, 'event_meta_boxes' ) ); 46 \add_action( 'save_post', array( $this, 'save_event_meta_boxes' ) ); 47 \add_action( 'transition_post_status', array( $this, 'event_status_transition' ), 10, 3 ); 48 \add_filter( 'gp_nav_menu_items', array( $this, 'gp_event_nav_menu_items' ), 10, 2 ); 49 \add_filter( 'wp_insert_post_data', array( $this, 'generate_event_slug' ), 10, 2 ); 50 \add_action( 'gp_init', array( $this, 'gp_init' ) ); 51 \add_action( 'gp_before_translation_table', array( $this, 'add_active_events_current_user' ) ); 52 \register_activation_hook( __FILE__, array( $this, 'activate' ) ); 62 add_action( 'wp_ajax_submit_event_ajax', array( $this, 'submit_event_ajax' ) ); 63 add_action( 'wp_ajax_nopriv_submit_event_ajax', array( $this, 'submit_event_ajax' ) ); 64 add_action( 'wp_enqueue_scripts', array( $this, 'register_translation_event_js' ) ); 65 add_action( 'init', array( $this, 'register_event_post_type' ) ); 66 add_action( 'add_meta_boxes', array( $this, 'event_meta_boxes' ) ); 67 add_action( 'save_post', array( $this, 'save_event_meta_boxes' ) ); 68 add_action( 'transition_post_status', array( $this, 'event_status_transition' ), 10, 3 ); 69 add_filter( 'gp_nav_menu_items', array( $this, 'gp_event_nav_menu_items' ), 10, 2 ); 70 add_filter( 'wp_insert_post_data', array( $this, 'generate_event_slug' ), 10, 2 ); 71 add_action( 'gp_init', array( $this, 'gp_init' ) ); 72 add_action( 'gp_before_translation_table', array( $this, 'add_active_events_current_user' ) ); 73 74 if ( is_admin() ) { 75 Upgrade::upgrade_if_needed(); 76 } 53 77 } 54 78 55 79 public function gp_init() { 56 require_once __DIR__ . '/templates/helper-functions.php';57 require_once __DIR__ . '/includes/active-events-cache.php';58 require_once __DIR__ . '/includes/event.php';59 require_once __DIR__ . '/includes/routes/route.php';60 require_once __DIR__ . '/includes/routes/event/create.php';61 require_once __DIR__ . '/includes/routes/event/details.php';62 require_once __DIR__ . '/includes/routes/event/edit.php';63 require_once __DIR__ . '/includes/routes/event/list.php';64 require_once __DIR__ . '/includes/routes/user/attend-event.php';65 require_once __DIR__ . '/includes/routes/user/my-events.php';66 require_once __DIR__ . '/includes/stats-calculator.php';67 require_once __DIR__ . '/includes/stats-listener.php';68 69 80 GP::$router->add( '/events?', array( 'Wporg\TranslationEvents\Routes\Event\List_Route', 'handle' ) ); 70 81 GP::$router->add( '/events/new', array( 'Wporg\TranslationEvents\Routes\Event\Create_Route', 'handle' ) ); 71 82 GP::$router->add( '/events/edit/(\d+)', array( 'Wporg\TranslationEvents\Routes\Event\Edit_Route', 'handle' ) ); 72 83 GP::$router->add( '/events/attend/(\d+)', array( 'Wporg\TranslationEvents\Routes\User\Attend_Event_Route', 'handle' ), 'post' ); 84 GP::$router->add( '/events/host/(\d+)/(\d+)', array( 'Wporg\TranslationEvents\Routes\User\Host_Event_Route', 'handle' ), 'post' ); 73 85 GP::$router->add( '/events/my-events', array( 'Wporg\TranslationEvents\Routes\User\My_Events_Route', 'handle' ) ); 74 86 GP::$router->add( '/events/([a-z0-9_-]+)', array( 'Wporg\TranslationEvents\Routes\Event\Details_Route', 'handle' ) ); 75 87 76 $active_events_cache = new Active_Events_Cache(); 77 $stats_listener = new Stats_Listener( $active_events_cache ); 88 $stats_listener = new Stats_Listener( 89 self::get_event_repository(), 90 self::get_attendee_repository(), 91 ); 78 92 $stats_listener->start(); 79 }80 81 public function activate() {82 global $gp_table_prefix;83 $create_table = "84 CREATE TABLE `{$gp_table_prefix}event_actions` (85 `translate_event_actions_id` int(11) unsigned NOT NULL AUTO_INCREMENT,86 `event_id` int(10) NOT NULL COMMENT 'Post_ID of the translation_event post in the wp_posts table',87 `original_id` int(10) NOT NULL COMMENT 'ID of the translation',88 `user_id` int(10) NOT NULL COMMENT 'ID of the user who made the action',89 `action` enum('approve','create','reject','request_changes') NOT NULL COMMENT 'The action that the user made (create, reject, etc)',90 `locale` varchar(10) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL COMMENT 'Locale of the translation',91 `happened_at` datetime NOT NULL COMMENT 'When the action happened, in UTC',92 PRIMARY KEY (`translate_event_actions_id`),93 UNIQUE KEY `event_per_translated_original_per_user` (`event_id`,`locale`,`original_id`,`user_id`)94 ) COMMENT='Tracks translation actions that happened during a translation event'";95 require_once ABSPATH . 'wp-admin/includes/upgrade.php';96 dbDelta( $create_table );97 93 } 98 94 … … 131 127 */ 132 128 public function event_meta_boxes() { 133 \add_meta_box( 'event_dates', 'Event Dates', array( $this, 'event_dates_meta_box' ), self::CPT, 'normal', 'high' );129 add_meta_box( 'event_dates', 'Event Dates', array( $this, 'event_dates_meta_box' ), self::CPT, 'normal', 'high' ); 134 130 } 135 131 … … 176 172 177 173 /** 178 * Validate the event dates.179 *180 * @param string $event_start The event start date.181 * @param string $event_end The event end date.182 * @return bool Whether the event dates are valid.183 * @throws Exception When dates are invalid.184 */185 public function validate_event_dates( string $event_start, string $event_end ): bool {186 if ( ! $event_start || ! $event_end ) {187 return false;188 }189 $event_start = new DateTime( $event_start );190 $event_end = new DateTime( $event_end );191 if ( $event_start < $event_end ) {192 return true;193 }194 return false;195 }196 197 /**198 174 * Handle the event form submission for the creation, editing, and deletion of events. This function is called via AJAX. 199 175 */ 200 176 public function submit_event_ajax() { 201 if ( ! is_user_logged_in() ) { 202 wp_send_json_error( esc_html__( 'The user must be logged in.', 'gp-translation-events' ), 403 ); 203 } 204 $action = isset( $_POST['form_name'] ) ? sanitize_text_field( wp_unslash( $_POST['form_name'] ) ) : ''; 205 $event_id = null; 206 $event = null; 207 $response_message = ''; 208 $form_actions = array( 'draft', 'publish', 'delete' ); 209 $is_nonce_valid = false; 210 $nonce_name = '_event_nonce'; 211 if ( ! in_array( $action, array( 'create_event', 'edit_event', 'delete_event' ), true ) ) { 212 wp_send_json_error( esc_html__( 'Invalid form name.', 'gp-translation-events' ), 403 ); 213 } 214 /** 215 * Filter the ability to create, edit, or delete an event. 216 * 217 * @param bool $can_crud_event Whether the user can create, edit, or delete an event. 218 */ 219 $can_crud_event = apply_filters( 'gp_translation_events_can_crud_event', GP::$permission->current_user_can( 'admin' ) ); 220 if ( 'create_event' === $action && ( ! $can_crud_event ) ) { 221 wp_send_json_error( esc_html__( 'The user does not have permission to create an event.', 'gp-translation-events' ), 403 ); 222 } 223 if ( 'edit_event' === $action ) { 224 $event_id = isset( $_POST['event_id'] ) ? sanitize_text_field( wp_unslash( $_POST['event_id'] ) ) : ''; 225 $event = get_post( $event_id ); 226 if ( ! ( $can_crud_event || current_user_can( 'edit_post', $event_id ) || intval( $event->post_author ) === get_current_user_id() ) ) { 227 wp_send_json_error( esc_html__( 'The user does not have permission to edit or delete the event.', 'gp-translation-events' ), 403 ); 228 } 229 } 230 if ( 'delete_event' === $action ) { 231 $event_id = isset( $_POST['event_id'] ) ? sanitize_text_field( wp_unslash( $_POST['event_id'] ) ) : ''; 232 $event = get_post( $event_id ); 233 if ( ! ( $can_crud_event || current_user_can( 'delete_post', $event->ID ) || get_current_user_id() === $event->post_author ) ) { 234 wp_send_json_error( esc_html__( 'You do not have permission to delete this event.', 'gp-translation-events' ), 403 ); 235 } 236 } 237 if ( isset( $_POST[ $nonce_name ] ) ) { 238 $nonce_value = sanitize_text_field( wp_unslash( $_POST[ $nonce_name ] ) ); 239 if ( wp_verify_nonce( $nonce_value, $nonce_name ) ) { 240 $is_nonce_valid = true; 241 } 242 } 243 if ( ! $is_nonce_valid ) { 244 wp_send_json_error( esc_html__( 'Nonce verification failed.', 'gp-translation-events' ), 403 ); 245 } 246 // This is a list of slugs that are not allowed, as they conflict with the event URLs. 247 $invalid_slugs = array( 'new', 'edit', 'attend', 'my-events' ); 248 $title = isset( $_POST['event_title'] ) ? sanitize_text_field( wp_unslash( $_POST['event_title'] ) ) : ''; 249 // This will be sanitized by santitize_post which is called in wp_insert_post. 250 $description = isset( $_POST['event_description'] ) ? force_balance_tags( wp_unslash( $_POST['event_description'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized 251 $event_start = isset( $_POST['event_start'] ) ? sanitize_text_field( wp_unslash( $_POST['event_start'] ) ) : ''; 252 $event_end = isset( $_POST['event_end'] ) ? sanitize_text_field( wp_unslash( $_POST['event_end'] ) ) : ''; 253 $event_timezone = isset( $_POST['event_timezone'] ) ? sanitize_text_field( wp_unslash( $_POST['event_timezone'] ) ) : ''; 254 if ( isset( $title ) && in_array( sanitize_title( $title ), $invalid_slugs, true ) ) { 255 wp_send_json_error( esc_html__( 'Invalid slug.', 'gp-translation-events' ), 422 ); 256 } 257 258 $is_valid_event_date = false; 259 try { 260 $is_valid_event_date = $this->validate_event_dates( $event_start, $event_end ); 261 } catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch 262 // Deliberately ignored, handled below. 263 } 264 if ( ! $is_valid_event_date ) { 265 wp_send_json_error( esc_html__( 'Invalid event dates.', 'gp-translation-events' ), 422 ); 266 } 267 268 $event_status = ''; 269 if ( isset( $_POST['event_form_action'] ) && in_array( $_POST['event_form_action'], $form_actions, true ) ) { 270 $event_status = sanitize_text_field( wp_unslash( $_POST['event_form_action'] ) ); 271 } 272 273 if ( ! isset( $_POST['form_name'] ) ) { 274 wp_send_json_error( esc_html__( 'Form name must be set.', 'gp-translation-events' ), 422 ); 275 } 276 277 if ( 'create_event' === $action ) { 278 $event_id = wp_insert_post( 279 array( 280 'post_type' => self::CPT, 281 'post_title' => $title, 282 'post_content' => $description, 283 'post_status' => $event_status, 284 ) 285 ); 286 $response_message = esc_html__( 'Event created successfully!', 'gp-translation-events' ); 287 } 288 if ( 'edit_event' === $action ) { 289 if ( ! isset( $_POST['event_id'] ) ) { 290 wp_send_json_error( esc_html__( 'Event id is required.', 'gp-translation-events' ), 422 ); 291 } 292 $event_id = sanitize_text_field( wp_unslash( $_POST['event_id'] ) ); 293 $event = get_post( $event_id ); 294 if ( ! $event || self::CPT !== $event->post_type || ! ( current_user_can( 'edit_post', $event->ID ) || intval( $event->post_author ) === get_current_user_id() ) ) { 295 wp_send_json_error( esc_html__( 'Event does not exist.', 'gp-translation-events' ), 404 ); 296 } 297 wp_update_post( 298 array( 299 'ID' => $event_id, 300 'post_title' => $title, 301 'post_content' => $description, 302 'post_status' => $event_status, 303 ) 304 ); 305 $response_message = esc_html__( 'Event updated successfully!', 'gp-translation-events' ); 306 } 307 if ( 'delete_event' === $action ) { 308 $event_id = sanitize_text_field( wp_unslash( $_POST['event_id'] ) ); 309 $event = get_post( $event_id ); 310 if ( ! $event || self::CPT !== $event->post_type ) { 311 wp_send_json_error( esc_html__( 'Event does not exist.', 'gp-translation-events' ), 404 ); 312 } 313 if ( ! ( current_user_can( 'delete_post', $event->ID ) || get_current_user_id() === $event->post_author ) ) { 314 wp_send_json_error( 'You do not have permission to delete this event' ); 315 } 316 $stats_calculator = new Stats_Calculator(); 317 try { 318 $event_stats = $stats_calculator->for_event( $event ); 319 } catch ( Exception $e ) { 320 wp_send_json_error( esc_html__( 'Failed to calculate event stats.', 'gp-translation-events' ), 500 ); 321 } 322 if ( ! empty( $event_stats->rows() ) ) { 323 wp_send_json_error( esc_html__( 'Event has translations and cannot be deleted.', 'gp-translation-events' ), 422 ); 324 } 325 wp_trash_post( $event_id ); 326 $response_message = esc_html__( 'Event deleted successfully!', 'gp-translation-events' ); 327 } 328 if ( ! $event_id ) { 329 wp_send_json_error( esc_html__( 'Event could not be created or updated.', 'gp-translation-events' ), 422 ); 330 } 331 if ( 'delete_event' !== $_POST['form_name'] ) { 332 try { 333 update_post_meta( $event_id, '_event_start', $this->convert_to_utc( $event_start, $event_timezone ) ); 334 update_post_meta( $event_id, '_event_end', $this->convert_to_utc( $event_end, $event_timezone ) ); 335 } catch ( Exception $e ) { 336 wp_send_json_error( esc_html__( 'Invalid start or end', 'gp-translation-events' ), 422 ); 337 } 338 339 update_post_meta( $event_id, '_event_timezone', $event_timezone ); 340 } 341 try { 342 Active_Events_Cache::invalidate(); 343 } catch ( Exception $e ) { 344 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log 345 error_log( $e ); 346 } 347 348 list( $permalink, $post_name ) = get_sample_permalink( $event_id ); 349 $permalink = str_replace( '%pagename%', $post_name, $permalink ); 350 wp_send_json_success( 351 array( 352 'message' => $response_message, 353 'eventId' => $event_id, 354 'eventUrl' => str_replace( '%pagename%', $post_name, $permalink ), 355 'eventStatus' => $event_status, 356 'eventEditUrl' => esc_url( gp_url( '/events/edit/' . $event_id ) ), 357 'eventDeleteUrl' => esc_url( gp_url( '/events/my-events/' ) ), 358 ) 359 ); 360 } 361 362 363 364 365 /** 366 * Convert a date time in a time zone to UTC. 367 * 368 * @param string $date_time The date time in the time zone. 369 * @param string $time_zone The time zone. 370 * @return string The date time in UTC. 371 * @throws Exception When dates are invalid. 372 */ 373 public function convert_to_utc( string $date_time, string $time_zone ): string { 374 $date_time = new DateTime( $date_time, new DateTimeZone( $time_zone ) ); 375 $date_time->setTimezone( new DateTimeZone( 'UTC' ) ); 376 return $date_time->format( 'Y-m-d H:i:s' ); 177 $form_handler = new Event_Form_Handler( self::get_event_repository(), self::get_attendee_repository() ); 178 // Nonce verification is done by the form handler. 179 // phpcs:ignore WordPress.Security.NonceVerification.Missing 180 $form_handler->handle( $_POST ); 377 181 } 378 182 … … 400 204 * @param string $old_status The old post status. 401 205 * @param WP_Post $post The post object. 206 * 207 * @throws Exception 402 208 */ 403 209 public function event_status_transition( string $new_status, string $old_status, WP_Post $post ): void { … … 406 212 } 407 213 if ( 'publish' === $new_status && ( 'new' === $old_status || 'draft' === $old_status ) ) { 408 $current_user_id = get_current_user_id(); 409 $user_attending_events = get_user_meta( $current_user_id, self::USER_META_KEY_ATTENDING, true ) ?: array(); 410 $is_user_attending_event = in_array( $post->ID, $user_attending_events, true ); 411 if ( ! $is_user_attending_event ) { 412 $new_user_attending_events = $user_attending_events; 413 $new_user_attending_events[ $post->ID ] = true; 414 update_user_meta( $current_user_id, self::USER_META_KEY_ATTENDING, $new_user_attending_events, $user_attending_events ); 214 $event_id = $post->ID; 215 $user_id = $post->post_author; 216 $attendee_repository = self::get_attendee_repository(); 217 $attendee = $attendee_repository->get_attendee( $event_id, $user_id ); 218 219 if ( null === $attendee ) { 220 $attendee = new Attendee( $event_id, $user_id ); 221 $attendee->mark_as_host(); 222 $attendee_repository->insert_attendee( $attendee ); 415 223 } 416 224 } … … 466 274 * Add the active events for the current user before the translation table. 467 275 * 468 * @ return void276 * @throws Exception 469 277 */ 470 278 public function add_active_events_current_user(): void { 471 $user_attending_events = get_user_meta( get_current_user_id(), self::USER_META_KEY_ATTENDING, true ) ?: array(); 472 if ( empty( $user_attending_events ) ) { 279 $attendee_repository = new Attendee_Repository(); 280 $user_attending_event_ids = $attendee_repository->get_events_for_user( get_current_user_id() ); 281 if ( empty( $user_attending_event_ids ) ) { 473 282 return; 474 283 } … … 477 286 $user_attending_events_args = array( 478 287 'post_type' => self::CPT, 479 'post__in' => array_keys( $user_attending_events ),288 'post__in' => $user_attending_event_ids, 480 289 'post_status' => 'publish', 481 290 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query … … 499 308 'order' => 'ASC', 500 309 ); 310 501 311 $user_attending_events_query = new WP_Query( $user_attending_events_args ); 502 312 $number_of_events = $user_attending_events_query->post_count;
Note: See TracChangeset
for help on using the changeset viewer.