Making WordPress.org


Ignore:
Timestamp:
04/15/2024 01:37:55 PM (2 years ago)
Author:
amieiro
Message:

Translate: Sync "Translation Events" from GitHub

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  
    2525use WP_Post;
    2626use WP_Query;
     27use Wporg\TranslationEvents\Attendee\Attendee;
     28use Wporg\TranslationEvents\Attendee\Attendee_Repository;
     29use Wporg\TranslationEvents\Event\Event_Form_Handler;
     30use Wporg\TranslationEvents\Event\Event_Repository_Cached;
     31use Wporg\TranslationEvents\Event\Event_Repository_Interface;
    2732
    2833class 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 {
    3337                static $instance = null;
    3438                if ( null === $instance ) {
     39                        require_once __DIR__ . '/autoload.php';
    3540                        $instance = new self();
    3641                }
     
    3843        }
    3944
     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
    4061        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                }
    5377        }
    5478
    5579        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 
    6980                GP::$router->add( '/events?', array( 'Wporg\TranslationEvents\Routes\Event\List_Route', 'handle' ) );
    7081                GP::$router->add( '/events/new', array( 'Wporg\TranslationEvents\Routes\Event\Create_Route', 'handle' ) );
    7182                GP::$router->add( '/events/edit/(\d+)', array( 'Wporg\TranslationEvents\Routes\Event\Edit_Route', 'handle' ) );
    7283                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' );
    7385                GP::$router->add( '/events/my-events', array( 'Wporg\TranslationEvents\Routes\User\My_Events_Route', 'handle' ) );
    7486                GP::$router->add( '/events/([a-z0-9_-]+)', array( 'Wporg\TranslationEvents\Routes\Event\Details_Route', 'handle' ) );
    7587
    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                );
    7892                $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 );
    9793        }
    9894
     
    131127         */
    132128        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' );
    134130        }
    135131
     
    176172
    177173        /**
    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         /**
    198174         * Handle the event form submission for the creation, editing, and deletion of events. This function is called via AJAX.
    199175         */
    200176        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 );
    377181        }
    378182
     
    400204         * @param string  $old_status The old post status.
    401205         * @param WP_Post $post       The post object.
     206         *
     207         * @throws Exception
    402208         */
    403209        public function event_status_transition( string $new_status, string $old_status, WP_Post $post ): void {
     
    406212                }
    407213                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 );
    415223                        }
    416224                }
     
    466274         * Add the active events for the current user before the translation table.
    467275         *
    468          * @return void
     276         * @throws Exception
    469277         */
    470278        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 ) ) {
    473282                        return;
    474283                }
     
    477286                $user_attending_events_args = array(
    478287                        'post_type'   => self::CPT,
    479                         'post__in'    => array_keys( $user_attending_events ),
     288                        'post__in'    => $user_attending_event_ids,
    480289                        'post_status' => 'publish',
    481290                        // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
     
    499308                        'order'       => 'ASC',
    500309                );
     310
    501311                $user_attending_events_query = new WP_Query( $user_attending_events_args );
    502312                $number_of_events            = $user_attending_events_query->post_count;
Note: See TracChangeset for help on using the changeset viewer.