Making WordPress.org


Ignore:
Timestamp:
04/15/2024 01:37:55 PM (22 months 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.