Making WordPress.org

Changeset 13683


Ignore:
Timestamp:
05/09/2024 08:33:51 AM (21 months ago)
Author:
amieiro
Message:

Translate: Sync "Translation Events" from GitHub

Location:
sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events
Files:
20 added
24 edited

Legend:

Unmodified
Added
Removed
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/assets/css/translation-events.css

    r13541 r13683  
    77.translation-event-form #event-title,
    88.translation-event-form #event-description {
    9     width: 30%;
     9    width: auto;
     10    resize: both;
    1011}
    1112
     
    5455    margin: 1rem;
    5556}
     57
    5658.event-details-stats table {
    5759    width: 100%;
     
    8486}
    8587
    86 .event-contributors li, .event-attendees li {
     88.event-contributors li,
     89.event-attendees li {
    8790    display: inline-block;
    8891    list-style-type: none;
     
    9295}
    9396
    94 .event-contributors li .avatar, .event-attendees li .avatar {
     97.event-contributors li .avatar,
     98.event-attendees li .avatar,
     99td a.attendee-avatar .avatar {
    95100    border-radius: 50%;
    96101    vertical-align: middle;
     
    107112    font-size: smaller;
    108113}
     114
    109115.hide-event-url {
    110116    display: none;
     
    247253}
    248254
    249 input[type="submit"].remove-as-host, input[type="submit"].convert-to-host {
     255input[type="submit"].remove-as-host,
     256input[type="submit"].convert-to-host {
    250257    text-align: center;
    251258    display: inline;
     
    280287}
    281288
    282 .event-page-edit-link {
     289.event-page-edit-link, .event-page-attendees-link {
    283290    float: right;
    284291    text-decoration: none;
     
    349356    text-decoration: none;
    350357}
     358
    351359ul.text-snippets {
    352360    padding: 0;
    353361    margin-left: 160px;
    354362}
     363
    355364.first-time-contributor-tada::after {
    356365    content: ' 🎉';
     366}
     367
     368ul#translation-links li {
     369    margin-bottom: .5em;
     370}
     371
     372.icons li .name {
     373    display: none;
     374}
     375
     376.icons li {
     377    width: auto !important;
     378}
     379
     380.event-attendees h2,
     381.event-contributors h2 {
     382    cursor: pointer;
     383    display: inline-block;
     384}
     385
     386ul.event-attendees-filter {
     387    padding-left: 1rem;
     388}
     389
     390ul.event-attendees-filter li {
     391    display: inline;
     392    margin-right: .4em;
     393}
     394
     395form.translation-event-form {
     396    float: left;
     397    width: 85%;
     398}
     399
     400div.event-edit-right {
     401    float: right;
     402    width: 15%;
     403    text-align: right;
     404}
     405
     406.view-event-page {
     407    float: right;
     408}
     409
     410a.active-filter {
     411    font-weight: bold;
     412}
     413
     414.event-edit-right a.manage-attendees-btn {
     415    margin-top: 1em;
     416}
     417
     418#quick-add.loading::after {
     419    content: 'Loading...';
     420}
     421
     422#quick-add summary {
     423    cursor: pointer;
     424}
     425
     426#quick-add {
     427    padding: 0 .5em;
     428    border-left: #d9d8d8 thin solid;
     429    border-bottom: #d9d8d8 thin solid;
     430    max-height: 8em;
     431    overflow: auto;
    357432}
    358433
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/assets/js/translation-events.js

    r13529 r13683  
    2222                );
    2323
    24                 $( '.delete-event' ).on(
     24                $( '.trash-event' ).on(
    2525                    'click',
    2626                    function ( e ) {
    2727                        e.preventDefault();
    28                         handleDelete()
     28                        handleTrash()
    2929                    }
    3030                );
     
    3939                    }
    4040                );
     41
     42                $( '.event-attendees h2, .event-contributors h2' ).on(
     43                    'click',
     44                    function ( e ) {
     45                            e.preventDefault();
     46                            $( this ).closest( 'body' ).toggleClass( 'icons' );
     47                            $( '.convert-to-host, .remove-as-host' ).toggle();
     48                    }
     49                );
     50
     51                $( '#quick-add' ).on(
     52                    'toggle',
     53                    function () {
     54                        if ( $( this ).data( 'loaded' ) ) {
     55                            return;
     56                        }
     57                        $( this ).addClass( 'loading' );
     58                        const options = {
     59                            weekday: 'short',
     60                            day: 'numeric',
     61                            month: 'short',
     62                            year: 'numeric'
     63                        };
     64
     65                        fetch( 'https://central.wordcamp.org/wp-json/wp/v2/wordcamps?per_page=30&status=wcpt-scheduled' ).then(
     66                            response => response.json()
     67                        ).then(
     68                            function ( data ) {
     69                                data.sort( ( a, b ) => a['Start Date (YYYY-mm-dd)'] - b['Start Date (YYYY-mm-dd)'] );
     70                                const ul = $( '<ul>' );
     71                                for ( const wordcamp of data ) {
     72                                    const li = $( '<li>' ).data( 'wordcamp', wordcamp );
     73                                    li.append( $( '<a>' ).attr( 'href', wordcamp.link ).text( wordcamp.title.rendered ) );
     74                                    li.append( $( '<span>' ).text( ' ' + new Date( 1000 * wordcamp['Start Date (YYYY-mm-dd)'] ).toLocaleDateString( navigator.language, options ) + ' - ' + new Date( 1000 * wordcamp['End Date (YYYY-mm-dd)'] ).toLocaleDateString( navigator.language, options ) ) );
     75                                    ul.append( li );
     76                                }
     77                                $( '#quick-add' ).data( 'loaded', true ).removeClass( 'loading' ).append( ul );
     78                            }
     79                        );
     80                    }
     81                );
     82                $( document ).on(
     83                    'click',
     84                    '#quick-add a',
     85                    function ( e ) {
     86                        e.preventDefault();
     87                        e.stopPropagation();
     88
     89                        const wordcamp = $( e.target ).closest( 'li' ).data( 'wordcamp' );
     90                        if ( ! wordcamp ) {
     91                            return;
     92                        }
     93
     94                        $( '#event-title' ).val( wordcamp.title.rendered );
     95                        $( '#event-description' ).val( wordcamp.content.rendered );
     96                        $( '#event-start' ).val( new Date( 1000 * wordcamp['Start Date (YYYY-mm-dd)'] ).toISOString().slice( 0,11 ) + '09:00' );
     97                        $( '#event-end' ).val( new Date( 1000 * wordcamp['End Date (YYYY-mm-dd)'] ).toISOString().slice( 0,11 ) + '18:00' );
     98                        $( '#event-timezone' ).val( wordcamp['Event Timezone'] );
     99
     100                    }
     101                );
     102
    41103            }
    42104        );
     
    100162                            $( '#event-url' ).removeClass( 'hide-event-url' ).find( 'a' ).attr( 'href', response.data.eventUrl ).text( response.data.eventUrl );
    101163                            if ( $is_creation ) {
    102                                 $( '#delete-button' ).toggle();
     164                                $( '#trash-button' ).toggle();
    103165                            }
    104166                            $gp.notices.success( response.data.message );
     
    114176        }
    115177
    116         function handleDelete() {
     178        function handleTrash() {
    117179            if ( ! confirm( 'Are you sure you want to delete this event?' ) ) {
    118180                return;
    119181            }
    120182            const $form = $( '.translation-event-form' );
    121             $( '#form-name' ).val( 'delete_event' );
    122             $( '#event-form-action' ).val( 'delete' );
     183            $( '#form-name' ).val( 'trash_event' );
     184            $( '#event-form-action' ).val( 'trash' );
    123185            $.ajax(
    124186                {
     
    127189                    data:$form.serialize(),
    128190                    success: function ( response ) {
    129                         window.location = response.data.eventDeleteUrl;
     191                        window.location = response.data.eventTrashUrl;
    130192                    },
    131193                    error: function ( error ) {
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/autoload.php

    r13541 r13683  
    22
    33require_once __DIR__ . '/includes/upgrade.php';
     4require_once __DIR__ . '/includes/urls.php';
    45require_once __DIR__ . '/templates/helper-functions.php';
    56require_once __DIR__ . '/includes/routes/route.php';
     7require_once __DIR__ . '/includes/routes/attendee/list.php';
    68require_once __DIR__ . '/includes/routes/event/create.php';
    79require_once __DIR__ . '/includes/routes/event/details.php';
    810require_once __DIR__ . '/includes/routes/event/edit.php';
     11require_once __DIR__ . '/includes/routes/event/trash.php';
     12require_once __DIR__ . '/includes/routes/event/delete.php';
    913require_once __DIR__ . '/includes/routes/event/list.php';
     14require_once __DIR__ . '/includes/routes/event/list-trashed.php';
     15require_once __DIR__ . '/includes/routes/event/translations.php';
    1016require_once __DIR__ . '/includes/routes/user/attend-event.php';
    1117require_once __DIR__ . '/includes/routes/user/host-event.php';
     
    1925require_once __DIR__ . '/includes/event/event-repository-cached.php';
    2026require_once __DIR__ . '/includes/event/event-form-handler.php';
     27require_once __DIR__ . '/includes/notifications/notifications-schedule.php';
     28require_once __DIR__ . '/includes/notifications/notifications-send.php';
     29require_once __DIR__ . '/includes/event/event-capabilities.php';
    2130require_once __DIR__ . '/includes/stats/stats-calculator.php';
    2231require_once __DIR__ . '/includes/stats/stats-importer.php';
    2332require_once __DIR__ . '/includes/stats/stats-listener.php';
     33require_once __DIR__ . '/includes/project/project-repository.php';
     34require_once __DIR__ . '/includes/translation/translation-repository.php';
    2435require_once __DIR__ . '/includes/event-text-snippet.php';
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/attendee/attendee-repository.php

    r13541 r13683  
    44
    55use Exception;
    6 use WP_User;
    76
    87class Attendee_Repository {
     
    8382            $wpdb->prepare(
    8483                "
    85                 select *
    86                 from {$gp_table_prefix}event_attendees
     84                select
     85                    user_id,
     86                    is_host,
     87                    (
     88                        select group_concat( distinct locale )
     89                        from {$gp_table_prefix}event_actions
     90                        where event_id = attendees.event_id
     91                          and user_id = attendees.user_id
     92                    ) as locales
     93                from {$gp_table_prefix}event_attendees attendees
    8794                where event_id = %d
    8895                  and user_id = %d
     
    100107        }
    101108
    102         $attendee = new Attendee( $row->event_id, $row->user_id );
    103         if ( '1' === $row->is_host ) {
    104             $attendee->mark_as_host();
    105         }
    106 
    107         return $attendee;
    108     }
    109 
    110     /**
    111      * @return Attendee[] Attendees of the event.
    112      */
    113     public function get_attendees( int $event_id ): array { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
    114         // TODO.
    115         return array();
    116     }
    117 
    118     /**
    119      * Get the hosts' users for an event.
    120      *
    121      * @param int $event_id The id of the event.
    122      *
    123      * @return Attendee[] The hosts of the event.
    124      * @throws Exception
    125      */
    126     public function get_hosts( int $event_id ): array {
     109        return new Attendee(
     110            $event_id,
     111            $row->user_id,
     112            '1' === $row->is_host,
     113            null === $row->locales ? array() : explode( ',', $row->locales ),
     114        );
     115    }
     116
     117    /**
     118     * Retrieve all the attendees of an event.
     119     *
     120     * @return Attendee[] Attendees of the event. Associative array with user_id as key.
     121     * @throws Exception
     122     */
     123    public function get_attendees( int $event_id ): array {
    127124        global $wpdb, $gp_table_prefix;
    128125
     
    133130            $wpdb->prepare(
    134131                "
    135                 select event_id, user_id
    136                 from {$gp_table_prefix}event_attendees
    137                 where event_id = %d and is_host = 1
     132                select
     133                    user_id,
     134                    is_host,
     135                    (
     136                        select group_concat( distinct locale )
     137                        from {$gp_table_prefix}event_actions
     138                        where event_id = attendees.event_id
     139                          and user_id = attendees.user_id
     140                    ) as locales
     141                from {$gp_table_prefix}event_attendees attendees
     142                where event_id = %d
    138143            ",
    139144                array(
    140145                    $event_id,
    141146                )
    142             )
    143         );
    144         // phpcs:enable
    145 
    146         $hosts = array();
    147         foreach ( $rows as $row ) {
    148             $host = new Attendee( $row->event_id, $row->user_id );
    149             $host->mark_as_host();
    150             $hosts[] = $host;
    151         }
    152         return $hosts;
     147            ),
     148            OBJECT_K,
     149        );
     150        // phpcs:enable
     151
     152        return array_map(
     153            function ( $row ) use ( $event_id ) {
     154                return new Attendee(
     155                    $event_id,
     156                    $row->user_id,
     157                    '1' === $row->is_host,
     158                    null === $row->locales ? array() : explode( ',', $row->locales ),
     159                );
     160            },
     161            $rows,
     162        );
     163    }
     164
     165    /**
     166     * Get attendees without contributions for an event.
     167     *
     168     * @param int $event_id The id of the event.
     169     *
     170     * @return Attendee[] Associative array with user_id as key.
     171     * @throws Exception
     172     */
     173    public function get_attendees_not_contributing( int $event_id ): array {
     174        return array_filter(
     175            $this->get_attendees( $event_id ),
     176            function ( Attendee $attendee ) {
     177                return ! $attendee->is_contributor();
     178            }
     179        );
     180    }
     181
     182    /**
     183     * Get the hosts' users for an event.
     184     *
     185     * @param int $event_id The id of the event.
     186     *
     187     * @return Attendee[] The hosts of the event. Associative array with user_id as key.
     188     * @throws Exception
     189     */
     190    public function get_hosts( int $event_id ): array {
     191        return array_filter(
     192            $this->get_attendees( $event_id ),
     193            function ( Attendee $attendee ) {
     194                return $attendee->is_host();
     195            }
     196        );
    153197    }
    154198
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/attendee/attendee.php

    r13529 r13683  
    1111
    1212    /**
     13     * @var string[]
     14     */
     15    private array $contributed_locales;
     16
     17    /**
    1318     * @throws Exception
    1419     */
    15     public function __construct( int $event_id, int $user_id ) {
     20    public function __construct( int $event_id, int $user_id, bool $is_host = false, array $contributed_locales = array() ) {
    1621        if ( $event_id < 1 ) {
    1722            throw new Exception( 'invalid event id' );
     
    2126        }
    2227
    23         $this->event_id = $event_id;
    24         $this->user_id  = $user_id;
    25         $this->is_host  = false;
     28        $this->event_id            = $event_id;
     29        $this->user_id             = $user_id;
     30        $this->is_host             = $is_host;
     31        $this->contributed_locales = $contributed_locales;
    2632    }
    2733
     
    3844    }
    3945
     46    public function is_contributor(): bool {
     47        return ! empty( $this->contributed_locales );
     48    }
     49
    4050    public function mark_as_host(): void {
    4151        $this->is_host = true;
     
    4555        $this->is_host = false;
    4656    }
     57
     58    /**
     59     * @return string[]
     60     */
     61    public function contributed_locales(): array {
     62        return $this->contributed_locales;
     63    }
    4764}
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/event/event-form-handler.php

    r13541 r13683  
    66use DateTimeZone;
    77use Exception;
    8 use GP;
    98use WP_Error;
    10 use Wporg\TranslationEvents\Attendee\Attendee;
    11 use Wporg\TranslationEvents\Attendee\Attendee_Repository;
     9use Wporg\TranslationEvents\Notifications\Notifications_Schedule;
    1210use Wporg\TranslationEvents\Stats\Stats_Calculator;
     11use Wporg\TranslationEvents\Urls;
    1312
    1413class Event_Form_Handler {
    1514    private Event_Repository_Interface $event_repository;
    16     private Attendee_Repository $attendee_repository;
    17 
    18     public function __construct( Event_Repository_Interface $event_repository, Attendee_Repository $attendee_repository ) {
    19         $this->event_repository    = $event_repository;
    20         $this->attendee_repository = $attendee_repository;
     15    private Notifications_Schedule $notifications_schedule;
     16
     17    public function __construct( Event_Repository_Interface $event_repository ) {
     18        $this->event_repository       = $event_repository;
     19        $this->notifications_schedule = new Notifications_Schedule( $this->event_repository );
    2120    }
    2221
     
    2524            wp_send_json_error( esc_html__( 'The user must be logged in.', 'gp-translation-events' ), 403 );
    2625        }
    27         $action           = isset( $form_data['form_name'] ) ? sanitize_text_field( wp_unslash( $form_data['form_name'] ) ) : '';
    28         $response_message = '';
    29         $is_nonce_valid   = false;
    30         $nonce_name       = '_event_nonce';
    31         if ( ! in_array( $action, array( 'create_event', 'edit_event', 'delete_event' ), true ) ) {
     26
     27        $action = isset( $form_data['form_name'] ) ? sanitize_text_field( wp_unslash( $form_data['form_name'] ) ) : '';
     28        if ( ! in_array( $action, array( 'create_event', 'edit_event', 'trash_event' ), true ) ) {
    3229            wp_send_json_error( esc_html__( 'Invalid form name.', 'gp-translation-events' ), 403 );
    3330        }
    34         /**
    35          * Filter the ability to create, edit, or delete an event.
    36          *
    37          * @param bool $can_crud_event Whether the user can create, edit, or delete an event.
    38          */
    39         $can_crud_event = apply_filters( 'gp_translation_events_can_crud_event', GP::$permission->current_user_can( 'admin' ) );
    40         if ( 'create_event' === $action && ( ! $can_crud_event ) ) {
    41             wp_send_json_error( esc_html__( 'The user does not have permission to create an event.', 'gp-translation-events' ), 403 );
    42         }
    43         if ( 'edit_event' === $action ) {
    44             $event_id = isset( $form_data['event_id'] ) ? sanitize_text_field( wp_unslash( $form_data['event_id'] ) ) : '';
    45             $event    = $this->event_repository->get_event( $event_id );
    46             $attendee = $this->attendee_repository->get_attendee( $event->id(), get_current_user_id() );
    47             if ( ! ( $can_crud_event || ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'edit_post', $event_id ) || $event->author_id() === get_current_user_id() ) ) {
    48                 wp_send_json_error( esc_html__( 'The user does not have permission to edit or delete the event.', 'gp-translation-events' ), 403 );
    49             }
    50         }
    51         if ( 'delete_event' === $action ) {
    52             $event_id         = isset( $form_data['event_id'] ) ? sanitize_text_field( wp_unslash( $form_data['event_id'] ) ) : '';
    53             $event            = $this->event_repository->get_event( $event_id );
    54             $attendee         = $this->attendee_repository->get_attendee( $event->id(), get_current_user_id() );
    55             $stats_calculator = new Stats_Calculator();
    56             if ( $stats_calculator->event_has_stats( $event->id() ) ) {
    57                 wp_send_json_error( esc_html__( 'The event has stats so it cannot be deleted.', 'gp-translation-events' ), 422 );
    58             }
    59             if ( ! ( $can_crud_event || ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'delete_post', $event_id ) || get_current_user_id() === $event->author_id() ) ) {
    60                 wp_send_json_error( esc_html__( 'You do not have permission to delete this event.', 'gp-translation-events' ), 403 );
    61             }
    62         }
     31
     32        $event_id = isset( $form_data['event_id'] ) ? sanitize_text_field( wp_unslash( $form_data['event_id'] ) ) : 0;
     33
     34        if ( 'create_event' === $action && ( ! current_user_can( 'create_translation_event' ) ) ) {
     35            wp_send_json_error( esc_html__( 'You do not have permissions to create events.', 'gp-translation-events' ), 403 );
     36        }
     37        if ( 'edit_event' === $action && ( ! current_user_can( 'edit_translation_event', $event_id ) ) ) {
     38            wp_send_json_error( esc_html__( 'You do not have permissions to edit this event.', 'gp-translation-events' ), 403 );
     39        }
     40        if ( 'trash_event' === $action && ( ! current_user_can( 'trash_translation_event', $event_id ) ) ) {
     41            wp_send_json_error( esc_html__( 'You do not have permissions to delete this event.', 'gp-translation-events' ), 403 );
     42        }
     43
     44        $is_nonce_valid = false;
     45        $nonce_name     = '_event_nonce';
    6346        if ( isset( $form_data[ $nonce_name ] ) ) {
    6447            $nonce_value = sanitize_text_field( wp_unslash( $form_data[ $nonce_name ] ) );
     
    7154        }
    7255
    73         if ( 'delete_event' === $action ) {
    74             // Delete event.
     56        $response_message = '';
     57        if ( 'trash_event' === $action ) {
     58            // Trash event.
    7559            $event_id = intval( sanitize_text_field( wp_unslash( $form_data['event_id'] ) ) );
    7660            $event    = $this->event_repository->get_event( $event_id );
     
    8973            }
    9074
    91             if ( false === $this->event_repository->delete_event( $event ) ) {
     75            if ( false === $this->event_repository->trash_event( $event ) ) {
    9276                $response_message = esc_html__( 'Failed to delete event.', 'gp-translation-events' );
    9377                $event_status     = $event->status();
    9478            } else {
    9579                $response_message = esc_html__( 'Event deleted successfully.', 'gp-translation-events' );
    96                 $event_status     = 'deleted';
     80                $event_status     = 'trashed';
     81                $this->notifications_schedule->delete_scheduled_emails( $event_id );
    9782            }
    9883        } else {
     
    135120                }
    136121                $response_message = esc_html__( 'Event created successfully.', 'gp-translation-events' );
     122                $this->notifications_schedule->schedule_emails( $result );
    137123            }
    138124            if ( 'edit_event' === $action ) {
     
    159145                }
    160146                $response_message = esc_html__( 'Event updated successfully', 'gp-translation-events' );
     147                $this->notifications_schedule->schedule_emails( $result );
    161148            }
    162149
     
    165152        }
    166153
    167         list( $permalink, $post_name ) = get_sample_permalink( $event_id );
    168         $permalink                     = str_replace( '%pagename%', $post_name, $permalink );
    169154        wp_send_json_success(
    170155            array(
    171                 'message'        => $response_message,
    172                 'eventId'        => $event_id,
    173                 'eventUrl'       => str_replace( '%pagename%', $post_name, $permalink ),
    174                 'eventStatus'    => $event_status,
    175                 'eventEditUrl'   => esc_url( gp_url( '/events/edit/' . $event_id ) ),
    176                 'eventDeleteUrl' => esc_url( gp_url( '/events/my-events/' ) ),
     156                'message'       => $response_message,
     157                'eventId'       => $event_id,
     158                'eventStatus'   => $event_status,
     159                'eventUrl'      => Urls::event_details_absolute( $event_id ),
     160                'eventEditUrl'  => Urls::event_edit( $event_id ),
     161                'eventTrashUrl' => Urls::my_events(), // The URL the user is redirected to after trashing.
    177162            )
    178163        );
     
    201186
    202187        $event_status = '';
    203         if ( isset( $data['event_form_action'] ) && in_array( $data['event_form_action'], array( 'draft', 'publish', 'delete' ), true ) ) {
     188        if ( isset( $data['event_form_action'] ) && in_array( $data['event_form_action'], array( 'draft', 'publish', 'trash' ), true ) ) {
    204189            $event_status = sanitize_text_field( wp_unslash( $data['event_form_action'] ) );
    205190        }
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/event/event-repository-cached.php

    r13529 r13683  
    3030        $this->invalidate_cache();
    3131        return $event_id_or_error;
     32    }
     33
     34    public function trash_event( Event $event ) {
     35        parent::trash_event( $event );
     36        $this->invalidate_cache();
    3237    }
    3338
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/event/event-repository-interface.php

    r13529 r13683  
    2626
    2727    /**
    28      * Delete an Event.
     28     * Trash an Event.
    2929     *
    30      * @param Event $event Event to delete.
     30     * @param Event $event Event to trash.
     31     *
     32     * @return Event|false Trashed event or false on error.
     33     */
     34    public function trash_event( Event $event );
     35
     36    /**
     37     * Permanently delete an Event.
     38     *
     39     * @param Event $event Event to permanently delete.
    3140     *
    3241     * @return Event|false Deleted event or false on error.
     
    8190
    8291    /**
     92     * Get events that are trashed.
     93     *
     94     * @param int $page      Index of the page to return.
     95     * @param int $page_size Page size.
     96     *
     97     * @return Events_Query_Result
     98     * @throws Exception
     99     */
     100    public function get_trashed_events( int $page = -1, int $page_size = -1 ): Events_Query_Result;
     101
     102    /**
    83103     * Get events that are currently active for a given user.
     104     *
     105     * @param int $user_id   Id of the user.
     106     * @param int $page      Index of the page to return.
     107     * @param int $page_size Page size.
     108     *
     109     * @return Events_Query_Result
     110     * @throws Exception
     111     */
     112    public function get_current_events_for_user( int $user_id, int $page = -1, int $page_size = -1 ): Events_Query_Result;
     113
     114    /**
     115     * Get events that are currently active or happening in the future, for a given user.
    84116     *
    85117     * @param int $user_id   Id of the user.
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/event/event-repository.php

    r13529 r13683  
    2121    }
    2222
     23    /**
     24     * Get or create the parent post for the year.
     25     *
     26     * @return int|WP_Error
     27     */
     28    private function get_year_post_id( string $year ) {
     29        $year_post = get_page_by_path( $year, OBJECT, self::POST_TYPE );
     30        if ( ! $year_post ) {
     31            return wp_insert_post(
     32                array(
     33                    'post_type'    => self::POST_TYPE,
     34                    'post_title'   => $year,
     35                    'post_name'    => $year,
     36                    'post_status'  => 'publish',
     37                    'post_content' => '',
     38                )
     39            );
     40        }
     41        return $year_post->ID;
     42    }
     43
    2344    public function insert_event( Event $event ) {
     45        $post_parent = $this->get_year_post_id( $event->start()->utc()->format( 'Y' ) );
     46        if ( is_wp_error( $post_parent ) ) {
     47            return $post_parent;
     48        }
     49
    2450        $event_id_or_error = wp_insert_post(
    2551            array(
     
    2955                'post_content' => $event->description(),
    3056                'post_status'  => $event->status(),
     57                'post_parent'  => $post_parent,
    3158            )
    3259        );
     
    4168
    4269    public function update_event( Event $event ) {
     70        $post_parent = $this->get_year_post_id( $event->start()->utc()->format( 'Y' ) );
     71        if ( is_wp_error( $post_parent ) ) {
     72            return $post_parent;
     73        }
    4374        $event_id_or_error = wp_update_post(
    4475            array(
     
    4879                'post_content' => $event->description(),
    4980                'post_status'  => $event->status(),
     81                'post_parent'  => $post_parent,
    5082            )
    5183        );
     
    5890    }
    5991
    60     public function delete_event( Event $event ) {
     92    public function trash_event( Event $event ) {
    6193        $result = wp_trash_post( $event->id() );
    6294        if ( ! $result ) {
    6395            return false;
    6496        }
     97        return $event;
     98    }
     99
     100    public function delete_event( Event $event ) {
     101        $result = wp_delete_post( $event->id(), true );
     102        if ( ! $result ) {
     103            return false;
     104        }
     105
     106        // Delete attendees.
     107        $attendees = $this->attendee_repository->get_attendees( $event->id() );
     108        foreach ( $attendees as $attendee ) {
     109            $this->attendee_repository->remove_attendee( $event->id(), $attendee->user_id() );
     110        }
     111
     112        // Delete stats.
     113        global $wpdb, $gp_table_prefix;
     114        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
     115        // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
     116        $wpdb->delete(
     117            "{$gp_table_prefix}event_actions",
     118            array( 'event_id' => $event->id() ),
     119            array( '%d' ),
     120        );
     121        // phpcs:enable
     122
    65123        return $event;
    66124    }
     
    153211    }
    154212
     213    public function get_trashed_events( int $page = - 1, int $page_size = - 1 ): Events_Query_Result {
     214        return $this->execute_events_query(
     215            $page,
     216            $page_size,
     217            array(
     218                'post_status' => 'trash',
     219            )
     220        );
     221    }
     222
     223    public function get_current_events_for_user( int $user_id, int $page = -1, int $page_size = -1 ): Events_Query_Result {
     224        $now = new DateTimeImmutable( 'now', new DateTimeZone( 'UTC' ) );
     225
     226        // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
     227        // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key
     228        return $this->execute_events_query(
     229            $page,
     230            $page_size,
     231            array(
     232                'meta_query' => array(
     233                    array(
     234                        'key'     => '_event_start',
     235                        'value'   => $now->format( 'Y-m-d H:i:s' ),
     236                        'compare' => '<=',
     237                        'type'    => 'DATETIME',
     238                    ),
     239                    array(
     240                        'key'     => '_event_end',
     241                        'value'   => $now->format( 'Y-m-d H:i:s' ),
     242                        'compare' => '>=',
     243                        'type'    => 'DATETIME',
     244                    ),
     245                ),
     246                'meta_key'   => '_event_start',
     247                'orderby'    => 'meta_value',
     248                'order'      => 'ASC',
     249            ),
     250            $this->attendee_repository->get_events_for_user( $user_id ),
     251        );
     252        // phpcs:enable
     253    }
     254
    155255    public function get_current_and_upcoming_events_for_user( int $user_id, int $page = -1, int $page_size = -1 ): Events_Query_Result {
    156256        $now = new DateTimeImmutable( 'now', new DateTimeZone( 'UTC' ) );
     
    414514
    415515    private function update_event_meta( Event $event ) {
     516        $hosts     = $this->attendee_repository->get_hosts( $event->id() );
     517        $hosts_ids = array_map(
     518            function ( $host ) {
     519                return $host->user_id();
     520            },
     521            $hosts
     522        );
     523        $hosts_ids = implode( ', ', $hosts_ids );
    416524        update_post_meta( $event->id(), '_event_start', $event->start()->utc()->format( 'Y-m-d H:i:s' ) );
    417525        update_post_meta( $event->id(), '_event_end', $event->end()->utc()->format( 'Y-m-d H:i:s' ) );
    418526        update_post_meta( $event->id(), '_event_timezone', $event->timezone()->getName() );
     527        update_post_meta( $event->id(), '_hosts', $hosts_ids );
    419528    }
    420529}
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/event/event.php

    r13541 r13683  
    8888    }
    8989
     90    public function is_trashed(): bool {
     91        return 'trash' === $this->status;
     92    }
     93
    9094    public function is_active(): bool {
    9195        $now = new DateTimeImmutable( 'now', new DateTimeZone( 'UTC' ) );
     
    142146     */
    143147    public function set_status( string $status ): void {
    144         if ( ! in_array( $status, array( 'draft', 'publish' ), true ) ) {
     148        if ( ! in_array( $status, array( 'draft', 'publish', 'trash' ), true ) ) {
    145149            throw new InvalidStatus();
    146150        }
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/create.php

    r13529 r13683  
    1717            exit;
    1818        }
    19         $event_page_title         = 'Create Event';
    20         $event_form_name          = 'create_event';
    21         $css_show_url             = 'hide-event-url';
    22         $event_id                 = null;
    23         $event_title              = '';
    24         $event_description        = '';
    25         $event_url                = '';
    26         $create_delete_button     = true;
    27         $visibility_delete_button = 'none';
    28         $event_timezone           = null;
    29         $event_start              = new Event_Start_Date( date_i18n( 'Y - m - d H:i' ) );
    30         $event_end                = new Event_End_Date( date_i18n( 'Y - m - d H:i' ) );
     19
     20        if ( ! current_user_can( 'create_translation_event' ) ) {
     21            $this->die_with_error( 'You do not have permission to create events.' );
     22        }
     23
     24        $event_page_title        = 'Create Event';
     25        $event_form_name         = 'create_event';
     26        $css_show_url            = 'hide-event-url';
     27        $event_id                = null;
     28        $event_title             = '';
     29        $event_description       = '';
     30        $event_url               = '';
     31        $create_trash_button     = true;
     32        $visibility_trash_button = 'none';
     33        $event_timezone          = null;
     34        $event_start             = new Event_Start_Date( date_i18n( 'Y - m - d H:i' ) );
     35        $event_end               = new Event_End_Date( date_i18n( 'Y - m - d H:i' ) );
     36        $event_slug              = '';
    3137
    3238        $this->tmpl( 'events-form', get_defined_vars() );
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/details.php

    r13541 r13683  
    44
    55use Exception;
    6 use GP;
    76use Wporg\TranslationEvents\Attendee\Attendee;
    87use Wporg\TranslationEvents\Attendee\Attendee_Repository;
    98use Wporg\TranslationEvents\Event\Event_Repository_Interface;
     9use Wporg\TranslationEvents\Project\Project_Repository;
    1010use Wporg\TranslationEvents\Routes\Route;
    1111use Wporg\TranslationEvents\Stats\Stats_Calculator;
     12use Wporg\TranslationEvents\Translation\Translation_Repository;
    1213use Wporg\TranslationEvents\Translation_Events;
    1314
     
    1819    private Event_Repository_Interface $event_repository;
    1920    private Attendee_Repository $attendee_repository;
     21    private Translation_Repository $translation_repository;
     22    private Project_Repository $project_repository;
     23    private Stats_Calculator $stats_calculator;
    2024
    2125    public function __construct() {
    2226        parent::__construct();
    23         $this->event_repository    = Translation_Events::get_event_repository();
    24         $this->attendee_repository = Translation_Events::get_attendee_repository();
     27        $this->event_repository       = Translation_Events::get_event_repository();
     28        $this->attendee_repository    = Translation_Events::get_attendee_repository();
     29        $this->translation_repository = new Translation_Repository();
     30        $this->project_repository     = new Project_Repository();
     31        $this->stats_calculator       = new Stats_Calculator();
    2532    }
    2633
     
    3643        }
    3744
    38         /**
    39          * Filter the ability to create, edit, or delete an event.
    40          *
    41          * @param bool $can_crud_event Whether the user can create, edit, or delete an event.
    42          */
    43         $can_crud_event = apply_filters( 'gp_translation_events_can_crud_event', GP::$permission->current_user_can( 'admin' ) );
    44         if ( 'publish' !== $event->status() && ! $can_crud_event ) {
     45        if ( ! current_user_can( 'view_translation_event', $event->id() ) ) {
    4546            $this->die_with_error( esc_html__( 'You are not authorized to view this page.', 'gp-translation-events' ), 403 );
    4647        }
     
    5253        $event_end         = $event->end();
    5354
    54         $attendee          = $this->attendee_repository->get_attendee( $event->id(), $user->ID );
    55         $user_is_attending = $attendee instanceof Attendee;
     55        $projects              = $this->project_repository->get_for_event( $event->id() );
     56        $attendees             = $this->attendee_repository->get_attendees( $event->id() );
     57        $current_user_attendee = $attendees[ $user->ID ] ?? null;
     58        $user_is_attending     = $current_user_attendee instanceof Attendee;
     59        $user_is_contributor   = $user_is_attending && $current_user_attendee->is_contributor();
    5660
    57         $stats_calculator = new Stats_Calculator();
     61        $hosts = array_filter(
     62            $attendees,
     63            function ( Attendee $attendee ) {
     64                return $attendee->is_host();
     65            }
     66        );
     67
     68        $contributors = array_filter(
     69            $attendees,
     70            function ( Attendee $attendee ) {
     71                return $attendee->is_contributor();
     72            }
     73        );
     74
     75        $attendees_not_contributing = array_filter(
     76            $attendees,
     77            function ( Attendee $attendee ) {
     78                return ! $attendee->is_contributor();
     79            }
     80        );
     81
     82        $contributor_ids = array_map(
     83            function ( Attendee $contributor ) {
     84                return $contributor->user_id();
     85            },
     86            $contributors
     87        );
     88
     89        $new_contributor_ids = array();
     90        $translations_counts = $this->translation_repository->count_translations_before( $contributor_ids, $event->start() );
     91        foreach ( $translations_counts as $user_id => $count ) {
     92            if ( $count <= 10 ) {
     93                $new_contributor_ids[ $user_id ] = true;
     94            }
     95        }
     96
    5897        try {
    59             $event_stats   = $stats_calculator->for_event( $event->id() );
    60             $contributors  = $stats_calculator->get_contributors( $event->id() );
    61             $attendees     = $stats_calculator->get_attendees_not_contributing( $event->id() );
    62             $attendee_repo = $this->attendee_repository;
    63             $hosts         = $this->attendee_repository->get_hosts( $event->id() );
    64             $projects      = $stats_calculator->get_projects( $event->id() );
     98            $event_stats = $this->stats_calculator->for_event( $event->id() );
    6599        } catch ( Exception $e ) {
    66100            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
     
    69103        }
    70104
    71         $is_editable_event = true;
    72         if ( $event_end->is_in_the_past() || $stats_calculator->event_has_stats( $event->id() ) ) {
    73             $is_editable_event = false;
    74         }
    75 
    76105        $this->tmpl( 'event', get_defined_vars() );
    77106    }
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/edit.php

    r13541 r13683  
    33namespace Wporg\TranslationEvents\Routes\Event;
    44
    5 use Wporg\TranslationEvents\Attendee\Attendee;
    6 use Wporg\TranslationEvents\Attendee\Attendee_Repository;
    75use Wporg\TranslationEvents\Event\Event_Repository_Interface;
    86use Wporg\TranslationEvents\Routes\Route;
    9 use Wporg\TranslationEvents\Stats\Stats_Calculator;
    107use Wporg\TranslationEvents\Translation_Events;
     8use Wporg\TranslationEvents\Urls;
    119
    1210/**
     
    1513class Edit_Route extends Route {
    1614    private Event_Repository_Interface $event_repository;
    17     private Attendee_Repository $attendee_repository;
    1815
    1916    public function __construct() {
    2017        parent::__construct();
    21         $this->event_repository    = Translation_Events::get_event_repository();
    22         $this->attendee_repository = Translation_Events::get_attendee_repository();
     18        $this->event_repository = Translation_Events::get_event_repository();
    2319    }
    2420
     
    2925            exit;
    3026        }
    31         $event    = $this->event_repository->get_event( $event_id );
    32         $attendee = $this->attendee_repository->get_attendee( $event->id(), get_current_user_id() );
    3327
    34         if ( ! $event || ! ( ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'edit_post', $event->id() ) || $event->author_id() === get_current_user_id() ) ) {
    35             $this->die_with_error( esc_html__( 'Event does not exist, or you do not have permission to edit it.', 'gp-translation-events' ), 403 );
     28        $event = $this->event_repository->get_event( $event_id );
     29        if ( ! $event ) {
     30            $this->die_with_404();
    3631        }
    37         if ( 'trash' === $event->status() ) {
    38             $this->die_with_error( esc_html__( 'You cannot edit a trashed event', 'gp-translation-events' ), 403 );
     32
     33        if ( ! current_user_can( 'edit_translation_event', $event->id() ) ) {
     34            $this->die_with_error( esc_html__( 'You do not have permission to edit this event.', 'gp-translation-events' ), 403 );
    3935        }
    4036
    4137        include ABSPATH . 'wp-admin/includes/post.php';
    42         $event_page_title              = 'Edit Event';
    43         $event_form_name               = 'edit_event';
    44         $css_show_url                  = '';
    45         $event_title                   = $event->title();
    46         $event_description             = $event->description();
    47         $event_status                  = $event->status();
    48         list( $permalink, $post_name ) = get_sample_permalink( $event->id() );
    49         $permalink                     = str_replace( '%pagename%', $post_name, $permalink );
    50         $event_url                     = get_site_url() . gp_url( wp_make_link_relative( $permalink ) );
    51         $event_timezone                = $event->timezone();
    52         $event_start                   = $event->start();
    53         $event_end                     = $event->end();
    54         $create_delete_button          = false;
    55         $visibility_delete_button      = 'inline-flex';
    56 
    57         if ( $event->end()->is_in_the_past() ) {
    58             $this->die_with_error( esc_html__( 'You cannot edit a past event.', 'gp-translation-events' ), 403 );
    59         }
    60 
    61         $stats_calculator = new Stats_Calculator();
    62 
    63         if ( $stats_calculator->event_has_stats( $event->id() ) ) {
    64             $this->die_with_error( esc_html__( 'You cannot edit an event with translations.', 'gp-translation-events' ), 403 );
    65         }
    66 
    67         if ( ! $stats_calculator->event_has_stats( $event->id() ) ) {
    68             $current_user = wp_get_current_user();
    69             if ( ( $current_user->ID === $event->author_id() || ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'manage_options' ) ) && ! $event->end()->is_in_the_past() ) {
    70                 $create_delete_button = true;
    71             }
    72         }
     38        $event_page_title        = 'Edit Event';
     39        $event_form_name         = 'edit_event';
     40        $css_show_url            = '';
     41        $event_title             = $event->title();
     42        $event_description       = $event->description();
     43        $event_status            = $event->status();
     44        $event_url               = Urls::event_details_absolute( $event_id );
     45        $event_timezone          = $event->timezone();
     46        $event_start             = $event->start();
     47        $event_end               = $event->end();
     48        $event_slug              = $event->slug();
     49        $create_trash_button     = current_user_can( 'trash_translation_event', $event->id() );
     50        $visibility_trash_button = 'inline-flex';
    7351
    7452        $this->tmpl( 'events-form', get_defined_vars() );
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/user/attend-event.php

    r13541 r13683  
    99use Wporg\TranslationEvents\Stats\Stats_Importer;
    1010use Wporg\TranslationEvents\Translation_Events;
     11use Wporg\TranslationEvents\Urls;
    1112
    1213/**
     
    4849        $attendee = $this->attendee_repository->get_attendee( $event->id(), $user_id );
    4950        if ( $attendee instanceof Attendee ) {
     51            if ( $attendee->is_contributor() ) {
     52                $this->die_with_error( esc_html__( 'Contributors cannot un-attend the event', 'gp-translation-events' ), 403 );
     53            }
    5054            $this->attendee_repository->remove_attendee( $event->id(), $user_id );
    5155        } else {
     
    6064        }
    6165
    62         wp_safe_redirect( gp_url( "/events/{$event->slug()}" ) );
     66        wp_safe_redirect( Urls::event_details( $event->id() ) );
    6367        exit;
    6468    }
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/user/host-event.php

    r13541 r13683  
    88use Wporg\TranslationEvents\Routes\Route;
    99use Wporg\TranslationEvents\Translation_Events;
     10use Wporg\TranslationEvents\Urls;
    1011
    1112/**
     
    3738        $current_user = wp_get_current_user();
    3839        if ( ! $current_user->exists() ) {
    39             $this->die_with_error( esc_html__( "Only logged-in users can manage event's hosts.", 'gp-translation-events' ), 403 );
     40            $this->die_with_error( esc_html__( "Only logged-in users can manage the event's hosts.", 'gp-translation-events' ), 403 );
    4041        }
    4142
    42         $current_user_attendee = $this->attendee_repository->get_attendee( $event_id, $current_user->ID );
    43         if ( ! current_user_can( 'manage_options' ) && ! $current_user_attendee->is_host() ) {
    44             $this->die_with_error( esc_html__( "This user does not have permissions to manage event's hosts.", 'gp-translation-events' ), 403 );
     43        if ( ! current_user_can( 'edit_translation_event', $event_id ) ) {
     44            $this->die_with_error( esc_html__( "You do not have permissions to manage the event's hosts.", 'gp-translation-events' ), 403 );
    4545        }
    4646
     
    6060
    6161            $this->attendee_repository->update_attendee( $affected_attendee );
     62            $this->event_repository->update_event( $event );
    6263        }
    6364
    64         wp_safe_redirect( gp_url( "/events/{$event->slug()}" ) );
     65        wp_safe_redirect( Urls::event_attendees( $event->id() ) );
    6566        exit;
    6667    }
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/stats/stats-calculator.php

    r13565 r13683  
    44
    55use Exception;
    6 use WP_Post;
    7 use WP_User;
    8 use GP;
    96use GP_Locale;
    107use GP_Locales;
    11 use DateTimeImmutable;
    12 use DateTimeZone;
    138
    149class Stats_Row {
    1510    public int $created;
    1611    public int $reviewed;
     12    public int $waiting;
    1713    public int $users;
    1814    public ?GP_Locale $language = null;
    1915
    20     public function __construct( $created, $reviewed, $users, ?GP_Locale $language = null ) {
     16    public function __construct( $created, $reviewed, $waiting, $users, ?GP_Locale $language = null ) {
    2117        $this->created  = $created;
    2218        $this->reviewed = $reviewed;
     19        $this->waiting  = $waiting;
    2320        $this->users    = $users;
    2421        $this->language = $language;
     
    104101                    sum(action = 'create') as created,
    105102                    count(*) as total,
    106                     count(distinct user_id) as users
    107                 from {$gp_table_prefix}event_actions
     103                    sum(t.status = 'waiting') as waiting,
     104                    count(distinct ea.user_id) as users
     105                from {$gp_table_prefix}event_actions ea
     106                left join {$gp_table_prefix}translations t ON ea.original_id = t.original_id and ea.user_id = t.user_id
    108107                where event_id = %d
    109108                group by locale with rollup
     
    131130            }
    132131
     132            if ( is_null( $row->waiting ) ) {
     133                // The corresponding translations are missing. Could be a unit test or data corruption.
     134                $row->waiting = 0;
     135            }
     136
    133137            $stats_row = new Stats_Row(
    134138                $row->created,
    135139                $row->total - $row->created,
     140                $row->waiting,
    136141                $row->users,
    137142                $lang
     
    146151
    147152        return $stats;
    148     }
    149 
    150     /**
    151      * Get contributors for an event.
    152      */
    153     public function get_contributors( int $event_id ): array {
    154         global $wpdb, $gp_table_prefix;
    155 
    156         // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
    157         // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
    158         // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
    159         // phpcs thinks we're doing a schema change but we aren't.
    160         // phpcs:disable WordPress.DB.DirectDatabaseQuery.SchemaChange
    161         $rows = $wpdb->get_results(
    162             $wpdb->prepare(
    163                 "
    164                 select user_id, group_concat( distinct locale ) as locales
    165                 from {$gp_table_prefix}event_actions
    166                 where event_id = %d
    167                 group by user_id
    168             ",
    169                 array(
    170                     $event_id,
    171                 )
    172             )
    173         );
    174         // phpcs:enable
    175 
    176         $users = array();
    177         foreach ( $rows as $row ) {
    178             $user          = new WP_User( $row->user_id );
    179             $user->locales = explode( ',', $row->locales );
    180             $users[]       = $user;
    181         }
    182 
    183         uasort(
    184             $users,
    185             function ( $a, $b ) {
    186                 return strcasecmp( $a->display_name, $b->display_name );
    187             }
    188         );
    189 
    190         return $users;
    191     }
    192 
    193     /**
    194      * Get attendees without contributions for an event.
    195      */
    196     public function get_attendees_not_contributing( int $event_id ): array {
    197         global $wpdb, $gp_table_prefix;
    198 
    199         // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
    200         // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
    201         // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
    202         $all_attendees_ids = $wpdb->get_col(
    203             $wpdb->prepare(
    204                 "
    205                 select distinct user_id
    206                 from {$gp_table_prefix}event_attendees
    207                 where event_id = %d
    208             ",
    209                 array(
    210                     $event_id,
    211                 )
    212             ),
    213         );
    214 
    215         $contributing_ids = $wpdb->get_col(
    216             $wpdb->prepare(
    217                 "
    218                 select distinct user_id
    219                 from {$gp_table_prefix}event_actions
    220                 where event_id = %d
    221             ",
    222                 array(
    223                     $event_id,
    224                 )
    225             )
    226         );
    227 
    228         $attendees_not_contributing_ids = array_diff( $all_attendees_ids, $contributing_ids );
    229 
    230         $attendees_not_contributing = array();
    231         foreach ( $attendees_not_contributing_ids as $user_id ) {
    232             $attendees_not_contributing[] = new WP_User( $user_id );
    233         }
    234 
    235         return $attendees_not_contributing;
    236     }
    237 
    238     /**
    239      * Get projects for an event.
    240      */
    241     public function get_projects( int $event_id ): array {
    242         global $wpdb, $gp_table_prefix;
    243 
    244         // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
    245         // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
    246         // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
    247         // phpcs thinks we're doing a schema change but we aren't.
    248         // phpcs:disable WordPress.DB.DirectDatabaseQuery.SchemaChange
    249         $rows = $wpdb->get_results(
    250             $wpdb->prepare(
    251                 "
    252                 select
    253                     o.project_id as project,
    254                     group_concat( distinct e.locale ) as locales,
    255                     sum(action = 'create') as created,
    256                     count(*) as total,
    257                     count(distinct user_id) as users
    258                 from {$gp_table_prefix}event_actions e, {$gp_table_prefix}originals o
    259                 where e.event_id = %d and e.original_id = o.id
    260                 group by o.project_id
    261             ",
    262                 array(
    263                     $event_id,
    264                 )
    265             )
    266         );
    267         // phpcs:enable
    268 
    269         $projects = array();
    270         foreach ( $rows as $row ) {
    271             $row->project      = GP::$project->get( $row->project );
    272             $project_name      = $row->project->name;
    273             $parent_project_id = $row->project->parent_project_id;
    274             while ( $parent_project_id ) {
    275                 $parent_project    = GP::$project->get( $parent_project_id );
    276                 $parent_project_id = $parent_project->parent_project_id;
    277                 $project_name      = substr( htmlspecialchars_decode( $parent_project->name ), 0, 35 ) . ' - ' . $project_name;
    278             }
    279             $projects[ $project_name ] = $row;
    280         }
    281 
    282         ksort( $projects );
    283 
    284         return $projects;
    285153    }
    286154
     
    301169        return ! empty( $stats->rows() );
    302170    }
    303 
    304     /**
    305      * Check if a user is a new translation contributor. A new contributor is a user who has made 10 or fewer translations before event start time.
    306      *
    307      * @param Event_Start_Date $event_start The event start date.
    308      * @param int              $user_id      The user ID.
    309      *
    310      * @return bool True if the user is a new translation contributor, false otherwise.
    311      */
    312     public function is_new_translation_contributor( $event_start, $user_id ) {
    313         global $wpdb, $gp_table_prefix;
    314         $new_contributor_max_translation_count = 10;
    315         $event_start_date_time                 = $event_start->__toString();
    316         // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
    317         // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
    318         // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
    319         // phpcs:disable WordPress.DB.DirectDatabaseQuery.SchemaChange
    320         $user_translations_count = $wpdb->get_var(
    321             $wpdb->prepare(
    322                 "
    323             select count(*) from {$gp_table_prefix}translations where user_id = %d and date_added < %s
    324         ",
    325                 array(
    326                     $user_id,
    327                     $event_start_date_time,
    328                 )
    329             )
    330         );
    331 
    332         if ( get_userdata( $user_id ) && ! $user_translations_count ) {
    333             return true;
    334         }
    335         return $user_translations_count <= $new_contributor_max_translation_count;
    336     }
    337171}
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/upgrade.php

    r13541 r13683  
    22
    33namespace Wporg\TranslationEvents;
    4 
    5 use Exception;
    6 use WP_Query;
    7 use Wporg\TranslationEvents\Attendee\Attendee;
    8 use Wporg\TranslationEvents\Stats\Stats_Calculator;
    94
    105class Upgrade {
     
    2823        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    2924        dbDelta( self::get_database_schema_sql() );
    30 
    31         // Run version-specific upgrades.
    32         $is_running_tests = 'yes' === getenv( 'WPORG_TRANSLATION_EVENTS_TESTS' );
    33         if ( $previous_version < 2 && ! $is_running_tests ) {
    34             try {
    35                 self::v2_import_legacy_attendees();
    36             } catch ( Exception $e ) {
    37                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
    38                 error_log( $e );
    39             }
    40         }
    4125
    4226        update_option( self::VERSION_OPTION, self::VERSION );
     
    7054        ";
    7155    }
    72 
    73     /**
    74      * Previously, event attendance was tracked through user_meta.
    75      * This function imports this legacy attendance information into the attendees table.
    76      *
    77      * Instead of looping through all users, we consider only users who have contributed to an event.
    78      *
    79      * @throws Exception
    80      */
    81     private static function v2_import_legacy_attendees(): void {
    82         $query = new WP_Query(
    83             array(
    84                 'post_type'   => Translation_Events::CPT,
    85                 'post_status' => 'publish',
    86             )
    87         );
    88 
    89         $events              = $query->get_posts();
    90         $stats_calculator    = new Stats_Calculator();
    91         $attendee_repository = Translation_Events::get_attendee_repository();
    92         foreach ( $events as $event ) {
    93             $host_attendee = new Attendee( $event->ID, intval( $event->post_author ) );
    94             $host_attendee->mark_as_host();
    95             $attendee_repository->insert_attendee( $host_attendee );
    96 
    97             foreach ( $stats_calculator->get_contributors( $event->ID ) as $user ) {
    98                 $attendee = $attendee_repository->get_attendee( $event->ID, $user->id );
    99                 if ( ! $attendee ) {
    100                     $attendee = new Attendee( $event->ID, $user->ID );
    101                     $attendee_repository->insert_attendee( $attendee );
    102                 }
    103             }
    104         }
    105     }
    10656}
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/templates/event.php

    r13566 r13683  
    99use WP_User;
    1010use Wporg\TranslationEvents\Attendee\Attendee;
    11 use Wporg\TranslationEvents\Attendee\Attendee_Repository;
    1211use Wporg\TranslationEvents\Event\Event;
    1312use Wporg\TranslationEvents\Event\Event_End_Date;
     
    1615use Wporg\TranslationEvents\Stats\Stats_Row;
    1716
    18 /** @var Attendee_Repository $attendee_repo */
    19 /** @var Attendee $attendee */
     17/** @var bool $user_is_attending */
     18/** @var bool $user_is_contributor */
     19/** @var Attendee[] $attendees_not_contributing */
     20/** @var Attendee[] $contributors */
     21/** @var array $new_contributor_ids */
    2022/** @var Event $event */
    2123/** @var int $event_id */
     
    5355                <ul>
    5456                    <?php foreach ( $contributors as $contributor ) : ?>
    55                         <li class="event-contributor" title="<?php echo esc_html( implode( ', ', $contributor->locales ) ); ?>">
    56                             <a href="<?php echo esc_url( get_author_posts_url( $contributor->ID ) ); ?>" class="avatar"><?php echo get_avatar( $contributor->ID, 48 ); ?></a>
    57                             <a href="<?php echo esc_url( get_author_posts_url( $contributor->ID ) ); ?>" class="name"><?php echo esc_html( get_the_author_meta( 'display_name', $contributor->ID ) ); ?></a>
    58                             <?php if ( $stats_calculator->is_new_translation_contributor( $event_start, $contributor->ID ) ) : ?>
     57                        <li class="event-contributor" title="<?php echo esc_html( implode( ', ', $contributor->contributed_locales() ) ); ?>">
     58                            <a href="<?php echo esc_url( get_author_posts_url( $contributor->user_id() ) ); ?>" class="avatar"><?php echo get_avatar( $contributor->user_id(), 48 ); ?></a>
     59                            <a href="<?php echo esc_url( get_author_posts_url( $contributor->user_id() ) ); ?>" class="name"><?php echo esc_html( get_the_author_meta( 'display_name', $contributor->user_id() ) ); ?></a>
     60                            <?php if ( isset( $new_contributor_ids[ $contributor->user_id() ] ) ) : ?>
    5961                                <span class="first-time-contributor-tada" title="<?php esc_html_e( 'New Translation Contributor', 'gp-translation-events' ); ?>"></span>
    6062                            <?php endif; ?>
    61                             <?php
    62                             if ( ! $event->end()->is_in_the_past() ) :
    63                                 if ( ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'manage_options' ) || $user->ID === $event->author_id() ) :
    64                                     $_attendee = $attendee_repo->get_attendee( $event_id, $contributor->ID );
    65                                     if ( $_attendee instanceof Attendee ) :
    66                                         echo '<form class="add-remove-user-as-host" method="post" action="' . esc_url( gp_url( "/events/host/$event_id/$contributor->ID" ) ) . '">';
    67                                         if ( $_attendee->is_host() ) :
    68                                             echo '<input type="submit" class="button is-primary remove-as-host" value="Remove as host"/>';
    69                                         else :
    70                                             echo '<input type="submit" class="button is-secondary convert-to-host" value="Make co-host"/>';
    71                                         endif;
    72                                         echo '</form>';
    73                                     else :
    74                                         echo '<span class="event-not-attending">' . esc_html__( 'Not attending', 'gp-translation-events' ) . '</span>';
    75                                     endif;
    76                                 endif;
    77                             endif;
    78                             ?>
    7963                        </li>
    8064                    <?php endforeach; ?>
     
    8266            </div>
    8367        <?php endif; ?>
    84         <?php if ( ! empty( $attendees ) && ( ! $event->end()->is_in_the_past() || ( ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'manage_options' ) || $user->ID === $event->author_id() ) ) ) : ?>
     68        <?php if ( ! empty( $attendees_not_contributing ) && current_user_can( 'edit_translation_event', $event->id() ) ) : ?>
    8569            <div class="event-attendees">
    8670                <h2>
    8771                <?php
    8872                // translators: %d is the number of attendees.
    89                 echo esc_html( sprintf( __( 'Attendees (%d)', 'gp-translation-events' ), number_format_i18n( count( $attendees ) ) ) );
     73                echo esc_html( sprintf( __( 'Attendees (%d)', 'gp-translation-events' ), number_format_i18n( count( $attendees_not_contributing ) ) ) );
    9074                ?>
    9175                </h2>
    9276                <ul>
    93                     <?php foreach ( $attendees as $_user ) : ?>
     77                    <?php foreach ( $attendees_not_contributing as $_attendee ) : ?>
    9478                        <li class="event-attendee">
    95                             <a href="<?php echo esc_url( get_author_posts_url( $_user->ID ) ); ?>" class="avatar"><?php echo get_avatar( $_user->ID, 48 ); ?></a>
    96                             <a href="<?php echo esc_url( get_author_posts_url( $_user->ID ) ); ?>" class="name"><?php echo esc_html( get_the_author_meta( 'display_name', $_user->ID ) ); ?></a>
    97                             <?php if ( $stats_calculator->is_new_translation_contributor( $event_start, $_user->ID ) ) : ?>
     79                            <a href="<?php echo esc_url( get_author_posts_url( $_attendee->user_id() ) ); ?>" class="avatar"><?php echo get_avatar( $_attendee->user_id(), 48 ); ?></a>
     80                            <a href="<?php echo esc_url( get_author_posts_url( $_attendee->user_id() ) ); ?>" class="name"><?php echo esc_html( get_the_author_meta( 'display_name', $_attendee->user_id() ) ); ?></a>
     81                            <?php if ( isset( $new_contributor_ids[ $_attendee->user_id() ] ) ) : ?>
    9882                                <span class="first-time-contributor-tada" title="<?php esc_html_e( 'New Translation Contributor', 'gp-translation-events' ); ?>"></span>
    9983                            <?php endif; ?>
    100                             <?php
    101                             if ( ! $event->end()->is_in_the_past() ) :
    102                                 if ( ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'manage_options' ) || $user->ID === $event->author_id() ) :
    103                                     $_attendee = $attendee_repo->get_attendee( $event_id, $_user->ID );
    104                                     if ( $_attendee instanceof Attendee ) :
    105                                         echo '<form class="add-remove-user-as-host" method="post" action="' . esc_url( gp_url( "/events/host/$event_id/$_user->ID" ) ) . '">';
    106                                         if ( $_attendee->is_host() ) :
    107                                             echo '<input type="submit" class="button is-primary remove-as-host" value="Remove as host"/>';
    108                                         else :
    109                                             echo '<input type="submit" class="button is-secondary convert-to-host" value="Make co-host"/>';
    110                                         endif;
    111                                         echo '</form>';
    112                                     endif;
    113                                 endif;
    114                             endif;
    115                             ?>
    11684                        </li>
    11785                    <?php endforeach; ?>
     
    12593                    <thead>
    12694                    <tr>
    127                         <th scope="col">Locale</th>
    128                         <th scope="col">Translations created</th>
    129                         <th scope="col">Translations reviewed</th>
    130                         <th scope="col">Contributors</th>
     95                        <th scope="col"><?php esc_html_e( 'Translations', 'gp-translation-events' ); ?></th>
     96                        <th scope="col"><?php esc_html_e( 'Created', 'gp-translation-events' ); ?></th>
     97                        <th scope="col"><?php esc_html_e( 'Waiting', 'gp-translation-events' ); ?></th>
     98                        <th scope="col"><?php esc_html_e( 'Reviewed', 'gp-translation-events' ); ?></th>
     99                        <th scope="col"><?php esc_html_e( 'Contributors', 'gp-translation-events' ); ?></th>
    131100                    </tr>
    132101                    </thead>
     
    136105                    <tr>
    137106                        <td title="<?php echo esc_html( $_locale ); ?> "><a href="<?php echo esc_url( gp_url_join( gp_url( '/languages' ), $row->language->slug ) ); ?>"><?php echo esc_html( $row->language->english_name ); ?></a></td>
    138                         <td><?php echo esc_html( $row->created ); ?></td>
     107                        <td><a href="<?php echo esc_url( Urls::event_translations( $event->id(), $row->language->slug ) ); ?>"><?php echo esc_html( $row->created ); ?></a></td>
     108                        <td><a href="<?php echo esc_url( Urls::event_translations( $event->id(), $row->language->slug, 'waiting' ) ); ?>"><?php echo esc_html( $row->waiting ); ?></a></td>
    139109                        <td><?php echo esc_html( $row->reviewed ); ?></td>
    140110                        <td><?php echo esc_html( $row->users ); ?></td>
     
    144114                        <td>Total</td>
    145115                        <td><?php echo esc_html( $event_stats->totals()->created ); ?></td>
     116                        <td><?php echo esc_html( $event_stats->totals()->waiting ); ?></td>
    146117                        <td><?php echo esc_html( $event_stats->totals()->reviewed ); ?></td>
    147118                        <td><?php echo esc_html( $event_stats->totals()->users ); ?></td>
     
    155126                    <?php foreach ( $projects as $project_name => $row ) : ?>
    156127                    <li class="event-project" title="<?php echo esc_html( str_replace( ',', ', ', $row->locales ) ); ?>">
    157                         <a href="<?php echo esc_url( gp_url_project( $row->project ) ); ?>"><?php echo esc_html( $project_name ); ?></a> <small> to
    158128                        <?php
     129                        $row_locales = array();
    159130                        foreach ( explode( ',', $row->locales ) as $_locale ) {
    160                             $_locale = GP_Locales::by_slug( $_locale );
    161                             ?>
    162                             <a href="<?php echo esc_url( gp_url_project_locale( $row->project, $_locale->slug, 'default' ) ); ?>"><?php echo esc_html( $_locale->english_name ); ?></a>
    163                             <?php
     131                            $_locale       = GP_Locales::by_slug( $_locale );
     132                            $row_locales[] = '<a href="' . esc_url( gp_url_project_locale( $row->project, $_locale->slug, 'default' ) ) . '">' . esc_html( $_locale->english_name ) . '</a>';
    164133                        }
    165                         // translators: %d: Number of contributors.
    166                         echo esc_html( sprintf( _n( 'by %d contributor', 'by %d contributors', $row->users, 'gp-translation-events' ), $row->users ) );
     134                        echo wp_kses_post(
     135                            wp_sprintf(
     136                                // translators: 1: Project translated. 2: List of languages. 3: Number of contributors.
     137                                _n(
     138                                    '%1$s <small>to %2$l by %3$d contributor</small>',
     139                                    '%1$s <small>to %2$l by %3$d contributors</small>',
     140                                    $row->users,
     141                                    'gp-translation-events'
     142                                ),
     143                                '<a href="' . esc_url( gp_url_project( $row->project ) ) . '">' . esc_html( $project_name ) . '</a>',
     144                                $row_locales,
     145                                $row->users
     146                            )
     147                        );
    167148                        ?>
    168                         </small>
    169149                    </li>
    170150                <?php endforeach; ?>
     
    175155                <p class="event-stats-text">
    176156                    <?php
    177                     $new_contributors = array_filter(
    178                         $contributors,
    179                         function ( $contributor ) use ( $stats_calculator, $event_start ) {
    180                             return $stats_calculator->is_new_translation_contributor( $event_start, $contributor->ID );
    181                         }
    182                     );
    183 
    184157                    $new_contributors_text = '';
    185                     if ( ! empty( $new_contributors ) ) {
     158                    if ( ! empty( $new_contributor_ids ) ) {
    186159                        $new_contributors_text = sprintf(
    187160                            // translators: %d is the number of new contributors.
    188                             _n( '(%d new contributor 🎉)', '(%d new contributors 🎉)', count( $new_contributors ), 'gp-translation-events' ),
    189                             count( $new_contributors )
     161                            _n( '(%d new contributor 🎉)', '(%d new contributors 🎉)', count( $new_contributor_ids ), 'gp-translation-events' ),
     162                            count( $new_contributor_ids )
    190163                        );
    191164                    }
    192165
    193166                    echo wp_kses(
    194                         sprintf(
    195                             // translators: %1$s: Event title, %2$d: Number of contributors, %3$s: is a parenthesis with potential text "x new contributors", %4$d: Number of languages, %5$s: List of languages, %6$d: Number of strings translated, %7$d: Number of strings reviewed.
    196                             __( 'At the <strong>%1$s</strong> event, we had %2$d people %3$s who contributed in %4$d languages (%5$s), translated %6$d strings and reviewed %7$d strings.', 'gp-translation-events' ),
     167                        wp_sprintf(
     168                            // translators: %1$s: Event title, %2$d: Number of contributors, %3$s: is a parenthesis with potential text "x new contributors", %4$d: Number of languages, %5$l: List of languages, %6$d: Number of strings translated, %7$d: Number of strings reviewed.
     169                            __( 'At the <strong>%1$s</strong> event, we had %2$d people %3$s who contributed in %4$d languages (%5$l), translated %6$d strings and reviewed %7$d strings.', 'gp-translation-events' ),
    197170                            esc_html( $event_title ),
    198171                            esc_html( $event_stats->totals()->users ),
    199172                            $new_contributors_text,
    200173                            count( $event_stats->rows() ),
    201                             esc_html(
    202                                 implode(
    203                                     ', ',
    204                                     array_map(
    205                                         function ( $row ) {
    206                                             return $row->language->english_name;
    207                                         },
    208                                         $event_stats->rows()
    209                                     )
    210                                 )
     174                            array_map(
     175                                function ( $row ) {
     176                                    return $row->language->english_name;
     177                                },
     178                                $event_stats->rows()
    211179                            ),
    212180                            esc_html( $event_stats->totals()->created ),
     
    220188                    <?php
    221189                    echo wp_kses(
    222                         sprintf(
    223                         // translators: %s the contributors.
    224                             esc_html__( 'Contributors were %s.', 'gp-translation-events' ),
    225                             implode(
    226                                 ', ',
    227                                 array_map(
    228                                     function ( $contributor ) use ( $stats_calculator, $event_start ) {
    229                                         $append_tada = '';
    230                                         if ( $stats_calculator->is_new_translation_contributor( $event_start, $contributor->ID ) ) {
     190                        wp_sprintf(
     191                            // translators: %s List of contributors.
     192                            _n(
     193                                'Contributor was %l.',
     194                                'Contributors were %l.',
     195                                count( $contributors ),
     196                                'gp-translation-events'
     197                            ),
     198                            array_map(
     199                                function ( $contributor ) {
     200                                    $append_tada = '';
     201                                    if ( isset( $new_contributor_ids[ $contributor->user_id() ] ) ) {
    231202                                            $append_tada = ' <span class="new-contributor" title="' . esc_html__( 'New Translation Contributor', 'gp-translation-events' ) . '">🎉</span>';
    232                                         }
    233                                         return '@' . $contributor->user_login . $append_tada;
    234                                     },
    235                                     $contributors
    236                                 )
     203                                    }
     204                                    return '@' . ( new WP_User( $contributor->user_id() ) )->user_login . $append_tada;
     205                                },
     206                                $contributors
    237207                            )
    238208                        ),
     
    268238        <div class="event-details-join">
    269239            <?php if ( $event_end->is_in_the_past() ) : ?>
    270                 <?php if ( $attendee instanceof Attendee ) : ?>
     240                <?php if ( $user_is_attending ) : ?>
    271241                    <button disabled="disabled" class="button is-primary attend-btn"><?php esc_html_e( 'You attended', 'gp-translation-events' ); ?></button>
    272242                <?php endif; ?>
     243            <?php elseif ( $user_is_contributor ) : ?>
     244                <?php // Contributors can't un-attend so don't show anything. ?>
    273245            <?php else : ?>
    274                 <form class="event-details-attend" method="post" action="<?php echo esc_url( gp_url( "/events/attend/$event_id" ) ); ?>">
    275                     <?php if ( $attendee instanceof Attendee ) : ?>
    276                         <input type="submit" class="button is-secondary attending-btn" value="You're attending" />
     246                <form class="event-details-attend" method="post" action="<?php echo esc_url( Urls::event_toggle_attendee( $event_id ) ); ?>">
     247                    <?php if ( $user_is_attending ) : ?>
     248                        <input type="submit" class="button is-secondary attending-btn" value="<?php esc_attr_e( "You're attending", 'gp-translation-events' ); ?>" />
    277249                    <?php else : ?>
    278                         <input type="submit" class="button is-primary attend-btn" value="Attend Event"/>
     250                        <input type="submit" class="button is-primary attend-btn" value="<?php esc_attr_e( 'Attend Event', 'gp-translation-events' ); ?>"/>
    279251                    <?php endif; ?>
    280252                </form>
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/templates/events-form.php

    r13529 r13683  
    2929<form class="translation-event-form" action="" method="post">
    3030    <?php wp_nonce_field( '_event_nonce', '_event_nonce' ); ?>
     31    <?php if ( ! $event_id ) : ?>
     32        <details id="quick-add"><summary><?php esc_html_e( 'Upcoming WordCamps', 'gp-translation-events' ); ?></summary><div class="loading"></div></details>
     33    <?php endif; ?>
    3134    <input type="hidden" name="action" value="submit_event_ajax">
    3235    <input type="hidden" id="form-name" name="form_name" value="<?php echo esc_attr( $event_form_name ); ?>">
     
    3437    <input type="hidden" id="event-form-action" name="event_form_action">
    3538    <div>
    36         <label for="event-title">Event Title</label>
    37         <input type="text" id="event-title" name="event_title" value="<?php echo esc_html( $event_title ); ?>" required>
     39        <label for="event-title"><?php esc_html_e( 'Event Title', 'gp-translation-events' ); ?></label>
     40        <input type="text" id="event-title" name="event_title" value="<?php echo esc_html( $event_title ); ?>" required size="42">
    3841    </div>
    3942    <div id="event-url" class="<?php echo esc_attr( $css_show_url ); ?>">
    40         <label for="event-permalink">Event URL</label>
     43        <label for="event-permalink"><?php esc_html_e( 'Event URL', 'gp-translation-events' ); ?></label>
    4144        <a id="event-permalink" class="event-permalink" href="<?php echo esc_url( $event_url ); ?>" target="_blank"><?php echo esc_url( $event_url ); ?></a>
    4245    </div>
    4346    <div>
    44         <label for="event-description">Event Description</label>
    45         <textarea id="event-description" name="event_description" rows="4" required><?php echo esc_html( $event_description ); ?></textarea>
     47        <label for="event-description"><?php esc_html_e( 'Event Description', 'gp-translation-events' ); ?></label>
     48        <textarea id="event-description" name="event_description" rows="4" cols="40" required><?php echo esc_html( $event_description ); ?></textarea>
    4649        <?php
    4750        echo wp_kses(
     
    5962        ?>
    6063            <div>
    61         <label for="event-start">Start Date</label>
     64        <label for="event-start"><?php esc_html_e( 'Start Date', 'gp-translation-events' ); ?></label>
    6265        <input type="datetime-local" id="event-start" name="event_start" value="<?php echo esc_attr( $event_start->format( 'Y-m-d H:i' ) ); ?>" required>
    6366    </div>
    6467    <div>
    65         <label for="event-end">End Date</label>
     68        <label for="event-end"><?php esc_html_e( 'End Date', 'gp-translation-events' ); ?></label>
    6669        <input type="datetime-local" id="event-end" name="event_end" value="<?php echo esc_attr( $event_end->format( 'Y-m-d H:i' ) ); ?>" required>
    6770    </div>
    6871    <div>
    69         <label for="event-timezone">Event Timezone</label>
     72        <label for="event-timezone"><?php esc_html_e( 'Event Timezone', 'gp-translation-events' ); ?></label>
    7073        <select id="event-timezone" name="event_timezone" required>
    7174            <?php
     
    9699        <button class="button is-primary submit-event" type="submit"  data-event-status="publish">Publish Event</button>
    97100    <?php endif; ?>
    98     <?php if ( isset( $create_delete_button ) && $create_delete_button ) : ?>
    99         <button id="delete-button" class="button is-destructive delete-event" type="submit" name="submit" value="Delete" style="display: <?php echo esc_attr( $visibility_delete_button ); ?>">Delete Event</button>
     101    <?php if ( isset( $create_trash_button ) && $create_trash_button ) : ?>
     102        <button id="trash-button" class="button is-destructive trash-event" type="submit" name="submit" value="Delete" style="display: <?php echo esc_attr( $visibility_trash_button ); ?>">Delete Event</button>
    100103    <?php endif; ?>
    101104    </div>
     
    127130</form>
    128131</div>
     132<?php if ( $event_id ) : ?>
     133    <div class="event-edit-right">
     134        <a class="manage-attendees-btn button is-primary" href="<?php echo esc_url( Urls::event_attendees( $event_id ) ); ?>"><?php esc_html_e( 'Manage Attendees', 'gp-translation-events' ); ?></a>
     135    </div>
     136<?php endif; ?>
    129137<div class="clear"></div>
    130138<?php gp_tmpl_footer(); ?>
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/templates/events-header.php

    r13541 r13683  
    33namespace Wporg\TranslationEvents;
    44
    5 use GP;
     5use WP_User;
    66use Wporg\TranslationEvents\Attendee\Attendee;
    77use Wporg\TranslationEvents\Event\Event;
    88
     9/** @var WP_User $user */
     10/** @var Attendee[] $hosts */
    911/** @var Attendee $attendee */
    10 /** @var Event  $event */
     12/** @var Event $event */
    1113/** @var string $event_page_title */
    12 /** @var bool   $is_editable_event */
    1314?>
    1415
     
    2223    <ul class="event-list-nav">
    2324        <?php if ( is_user_logged_in() ) : ?>
    24             <li><a href="<?php echo esc_url( gp_url( '/events/my-events/' ) ); ?>">My Events</a></li>
    25             <?php
    26             /**
    27              * Filter the ability to create, edit, or delete an event.
    28              *
    29              * @param bool $can_crud_event Whether the user can create, edit, or delete an event.
    30              */
    31             $can_crud_event = apply_filters( 'gp_translation_events_can_crud_event', GP::$permission->current_user_can( 'admin' ) );
    32             if ( $can_crud_event ) :
    33                 ?>
    34                 <li><a class="button is-primary" href="<?php echo esc_url( gp_url( '/events/new/' ) ); ?>">Create Event</a></li>
     25            <?php if ( current_user_can( 'manage_translation_events' ) ) : ?>
     26                <li><a href="<?php echo esc_url( Urls::events_trashed() ); ?>">Deleted Events</a></li>
     27            <?php endif; ?>
     28            <li><a href="<?php echo esc_url( Urls::my_events() ); ?>">My Events</a></li>
     29            <?php if ( current_user_can( 'create_translation_event' ) ) : ?>
     30                <li><a class="button is-primary" href="<?php echo esc_url( Urls::event_create() ); ?>">Create Event</a></li>
    3531            <?php endif; ?>
    3632        <?php endif; ?>
    3733    </ul>
    38     <?php if ( isset( $event ) && ! isset( $event_form_name ) ) : ?>
     34    <?php if ( isset( $event ) && ! isset( $event_form_name ) && ! isset( $hide_sub_head ) ) : ?>
    3935    <p class="event-sub-head">
    4036            <span class="event-host">
    4137                <?php
    42                 if ( count( $hosts ) > 0 ) :
    43                     if ( 1 === count( $hosts ) ) :
    44                         esc_html_e( 'Host:', 'gp-translation-events' );
     38                if ( isset( $hosts ) ) :
     39                    if ( count( $hosts ) > 0 ) :
     40                        if ( 1 === count( $hosts ) ) :
     41                            esc_html_e( 'Host:', 'gp-translation-events' );
     42                        else :
     43                            esc_html_e( 'Hosts:', 'gp-translation-events' );
     44                        endif;
    4545                    else :
    46                         esc_html_e( 'Hosts:', 'gp-translation-events' );
     46                        esc_html_e( 'Created by:', 'gp-translation-events' );
     47                        ?>
     48                        &nbsp;<a href="<?php echo esc_attr( get_author_posts_url( $user->ID ) ); ?>"><?php echo esc_html( get_the_author_meta( 'display_name', $user->ID ) ); ?></a>
     49                        <?php
    4750                    endif;
    48                 else :
    49                     esc_html_e( 'Created by:', 'gp-translation-events' );
    5051                    ?>
    51                     &nbsp;<a href="<?php echo esc_attr( get_author_posts_url( $user->ID ) ); ?>"><?php echo esc_html( get_the_author_meta( 'display_name', $user->ID ) ); ?></a>
    52                     <?php
    53                 endif;
    54                 ?>
    55                 <?php foreach ( $hosts as $host ) : ?>
    56                     &nbsp;<a href="<?php echo esc_attr( get_author_posts_url( $host->user_id() ) ); ?>"><?php echo esc_html( get_the_author_meta( 'display_name', $host->user_id() ) ); ?></a>
    57                     <?php if ( end( $hosts ) !== $host ) : ?>
    58                         ,
    59                     <?php else : ?>
    60                         .
    61                     <?php endif; ?>
    62                 <?php endforeach; ?>
     52                    <?php foreach ( $hosts as $host ) : ?>
     53                        &nbsp;<a href="<?php echo esc_attr( get_author_posts_url( $host->user_id() ) ); ?>"><?php echo esc_html( get_the_author_meta( 'display_name', $host->user_id() ) ); ?></a>
     54                        <?php if ( end( $hosts ) !== $host ) : ?>
     55                            ,
     56                        <?php else : ?>
     57                            .
     58                        <?php endif; ?>
     59                    <?php endforeach; ?>
     60                <?php endif; ?>
    6361            </span>
    64             <?php $show_edit_button = ( ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'edit_post', $event->id() ) ) && $is_editable_event; ?>
    65             <?php if ( $show_edit_button ) : ?>
    66                 <a class="event-page-edit-link" href="<?php echo esc_url( gp_url( 'events/edit/' . $event->id() ) ); ?>"><span class="dashicons dashicons-edit"></span><?php esc_html_e( 'Edit event', 'gp-translation-events' ); ?></a>
     62            <?php if ( current_user_can( 'edit_translation_event', $event->id() ) ) : ?>
     63                <a class="event-page-edit-link" href="<?php echo esc_url( Urls::event_edit( $event->id() ) ); ?>"><span class="dashicons dashicons-edit"></span><?php esc_html_e( 'Edit event', 'gp-translation-events' ); ?></a>
     64            <?php elseif ( current_user_can( 'edit_translation_event_attendees', $event->id() ) ) : ?>
     65                <a class="event-page-attendees-link" href="<?php echo esc_url( Urls::event_attendees( $event->id() ) ); ?>"><?php esc_html_e( 'Manage attendees', 'gp-translation-events' ); ?></a>
    6766            <?php endif ?>
    6867        </p>
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/templates/events-list.php

    r13529 r13683  
    3030    <h2><?php esc_html_e( 'Current events', 'gp-translation-events' ); ?></h2>
    3131    <ul class="event-list">
    32         <?php
    33         foreach ( $current_events_query->events as $event ) :
    34             $event_url = gp_url( wp_make_link_relative( get_the_permalink( $event->id() ) ) );
    35             ?>
     32        <?php foreach ( $current_events_query->events as $event ) : ?>
    3633            <li class="event-list-item">
    37                 <a href="<?php echo esc_url( $event_url ); ?>"><?php echo esc_html( $event->title() ); ?></a>
     34                <a href="<?php echo esc_url( Urls::event_details( $event->id() ) ); ?>"><?php echo esc_html( $event->title() ); ?></a>
    3835                <span class="event-list-date">ends <?php $event->end()->print_relative_time_html(); ?></time></span>
    3936                <?php echo esc_html( get_the_excerpt( $event->id() ) ); ?>
    4037            </li>
    41             <?php
    42         endforeach;
    43         ?>
     38        <?php endforeach; ?>
    4439    </ul>
    4540
     
    6459    <h2><?php esc_html_e( 'Upcoming events', 'gp-translation-events' ); ?></h2>
    6560    <ul class="event-list">
    66         <?php
    67         foreach ( $upcoming_events_query->events as $event ) :
    68             $event_url = gp_url( wp_make_link_relative( get_the_permalink( $event->id() ) ) );
    69             ?>
     61        <?php foreach ( $upcoming_events_query->events as $event ) : ?>
    7062            <li class="event-list-item">
    71                 <a href="<?php echo esc_url( $event_url ); ?>"><?php echo esc_html( $event->title() ); ?></a>
     63                <a href="<?php echo esc_url( Urls::event_details( $event->id() ) ); ?>"><?php echo esc_html( $event->title() ); ?></a>
    7264                <span class="event-list-date">starts <?php $event->start()->print_relative_time_html(); ?></span>
    7365                <?php echo esc_html( get_the_excerpt( $event->id() ) ); ?>
    7466            </li>
    75             <?php
    76         endforeach;
    77         ?>
     67        <?php endforeach; ?>
    7868    </ul>
    7969
     
    9787    <h2><?php esc_html_e( 'Past events', 'gp-translation-events' ); ?></h2>
    9888    <ul class="event-list">
    99         <?php
    100         foreach ( $past_events_query->events as $event ) :
    101             $event_url = gp_url( wp_make_link_relative( get_the_permalink( $event->id() ) ) );
    102             ?>
     89        <?php foreach ( $past_events_query->events as $event ) : ?>
    10390            <li class="event-list-item">
    104                 <a href="<?php echo esc_url( $event_url ); ?>"><?php echo esc_html( $event->title() ); ?></a>
     91                <a href="<?php echo esc_url( Urls::event_details( $event->id() ) ); ?>"><?php echo esc_html( $event->title() ); ?></a>
    10592                <span class="event-list-date">ended <?php $event->end()->print_relative_time_html( 'F j, Y H:i T' ); ?></span>
    10693                <?php esc_html( get_the_excerpt( $event->id() ) ); ?>
    10794            </li>
    108             <?php
    109         endforeach;
    110         ?>
     95        <?php endforeach; ?>
    11196    </ul>
    11297
     
    139124        <?php else : ?>
    140125            <ul class="event-attending-list">
    141                 <?php
    142                 foreach ( $user_attending_events_query->events as $event ) :
    143                     $event_url = gp_url( wp_make_link_relative( get_the_permalink( $event->id() ) ) );
    144                     ?>
     126                <?php foreach ( $user_attending_events_query->events as $event ) : ?>
    145127                    <li class="event-list-item">
    146                         <a href="<?php echo esc_url( $event_url ); ?>"><?php echo esc_html( $event->title() ); ?></a>
     128                        <a href="<?php echo esc_url( Urls::event_details( $event->id() ) ); ?>"><?php echo esc_html( $event->title() ); ?></a>
    147129                        <?php if ( $event->start() === $event->end() ) : ?>
    148130                            <span class="event-list-date events-i-am-attending"><?php $event->start()->print_time_html( 'F j, Y H:i T' ); ?></span>
     
    151133                        <?php endif; ?>
    152134                    </li>
    153                     <?php
    154                 endforeach;
    155                 ?>
     135                <?php endforeach; ?>
    156136            </ul>
    157137            <?php
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/templates/events-my-events.php

    r13541 r13683  
    2626        <?php
    2727        foreach ( $events_i_host_query->events as $event ) :
    28             list( $permalink, $post_name ) = get_sample_permalink( $event->id() );
    29             $permalink                     = str_replace( '%pagename%', $post_name, $permalink );
    30             $event_url                     = gp_url( wp_make_link_relative( $permalink ) );
    31             $event_edit_url                = gp_url( 'events/edit/' . $event->id() );
    32             $stats_calculator              = new Stats_Calculator();
    33             $has_stats                     = $stats_calculator->event_has_stats( $event->id() );
     28            $stats_calculator = new Stats_Calculator();
     29            $has_stats        = $stats_calculator->event_has_stats( $event->id() );
    3430            ?>
    3531            <li class="event-list-item">
    36                 <a class="event-link-<?php echo esc_attr( $event->status() ); ?>" href="<?php echo esc_url( $event_url ); ?>"><?php echo esc_html( $event->title() ); ?></a>
     32                <a class="event-link-<?php echo esc_attr( $event->status() ); ?>" href="<?php echo esc_url( Urls::event_details( $event->id() ) ); ?>"><?php echo esc_html( $event->title() ); ?></a>
    3733                <?php if ( ! $event->end()->is_in_the_past() && ! $has_stats ) : ?>
    38                     <a href="<?php echo esc_url( $event_edit_url ); ?>" class="button is-small action edit">Edit</a>
     34                    <a href="<?php echo esc_url( Urls::event_edit( $event->id() ) ); ?>" class="button is-small action edit">Edit</a>
    3935                <?php endif; ?>
    4036                <?php if ( 'draft' === $event->status() ) : ?>
     
    7369            <?php
    7470            foreach ( $events_i_created_query->events as $event ) :
    75                 list( $permalink, $post_name ) = get_sample_permalink( $event->id() );
    76                 $permalink                     = str_replace( '%pagename%', $post_name, $permalink );
    77                 $event_url                     = gp_url( wp_make_link_relative( $permalink ) );
    78                 $event_edit_url                = gp_url( 'events/edit/' . $event->id() );
    79                 $stats_calculator              = new Stats_Calculator();
    80                 $has_stats                     = $stats_calculator->event_has_stats( $event->id() );
     71                $stats_calculator = new Stats_Calculator();
     72                $has_stats        = $stats_calculator->event_has_stats( $event->id() );
    8173                ?>
    8274                <li class="event-list-item">
    83                     <a class="event-link-<?php echo esc_attr( $event->status() ); ?>" href="<?php echo esc_url( $event_url ); ?>"><?php echo esc_html( $event->title() ); ?></a>
     75                    <a class="event-link-<?php echo esc_attr( $event->status() ); ?>" href="<?php echo esc_url( Urls::event_details( $event->id() ) ); ?>"><?php echo esc_html( $event->title() ); ?></a>
    8476                    <?php if ( ! $event->end()->is_in_the_past() && ! $has_stats ) : ?>
    85                         <a href="<?php echo esc_url( $event_edit_url ); ?>" class="button is-small action edit">Edit</a>
     77                        <a href="<?php echo esc_url( Urls::event_edit( $event->id() ) ); ?>" class="button is-small action edit">Edit</a>
    8678                    <?php endif; ?>
    8779                    <?php if ( 'draft' === $event->status() ) : ?>
     
    118110    <?php if ( ! empty( $events_i_attended_query->events ) ) : ?>
    119111        <ul>
    120         <?php
    121         foreach ( $events_i_attended_query->events as $event ) :
    122             list( $permalink, $post_name ) = get_sample_permalink( $event->id() );
    123             $permalink                     = str_replace( '%pagename%', $post_name, $permalink );
    124             $event_url                     = gp_url( wp_make_link_relative( $permalink ) );
    125             ?>
     112        <?php foreach ( $events_i_attended_query->events as $event ) : ?>
    126113            <li class="event-list-item">
    127                 <a class="event-link-<?php echo esc_attr( $event->status() ); ?>" href="<?php echo esc_url( $event_url ); ?>"><?php echo esc_html( $event->title() ); ?></a>
     114                <a class="event-link-<?php echo esc_attr( $event->status() ); ?>" href="<?php echo esc_url( Urls::event_details( $event->id() ) ); ?>"><?php echo esc_html( $event->title() ); ?></a>
    128115                <?php if ( $event->start() === $event->end() ) : ?>
    129116                    <span class="event-list-date events-i-am-attending"><?php $event->start()->print_time_html(); ?></span>
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/templates/helper-functions.php

    r13268 r13683  
    11<?php
     2
     3use Wporg\TranslationEvents\Urls;
     4
    25/**
    36 * Get event breadcrumb.
     
    912function gp_breadcrumb_translation_events( $extra_items = array() ) {
    1013    $breadcrumb = array(
    11         empty( $extra_items ) ? __( 'Events', 'gp-translation-events' ) : gp_link_get( gp_url( '/events' ), __( 'Events', 'gp-translation-events' ) ),
     14        empty( $extra_items ) ? __( 'Events', 'gp-translation-events' ) : gp_link_get( Urls::events_home(), __( 'Events', 'gp-translation-events' ) ),
    1215    );
    1316    if ( ! empty( $extra_items ) ) {
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/wporg-gp-translation-events.php

    r13541 r13683  
    2323use Exception;
    2424use GP;
     25use GP_Locales;
    2526use WP_Post;
    2627use WP_Query;
    2728use Wporg\TranslationEvents\Attendee\Attendee;
    2829use Wporg\TranslationEvents\Attendee\Attendee_Repository;
     30use Wporg\TranslationEvents\Event\Event_Capabilities;
    2931use Wporg\TranslationEvents\Event\Event_Form_Handler;
    3032use Wporg\TranslationEvents\Event\Event_Repository_Cached;
    3133use Wporg\TranslationEvents\Event\Event_Repository_Interface;
     34use Wporg\TranslationEvents\Notifications\Notifications_Send;
     35use Wporg\TranslationEvents\Stats\Stats_Calculator;
    3236use Wporg\TranslationEvents\Stats\Stats_Listener;
    3337
    3438class Translation_Events {
    3539    public const CPT = 'translation_event';
     40
     41    private Event_Capabilities $event_capabilities;
    3642
    3743    public static function get_instance(): Translation_Events {
     
    6571        add_action( 'wp_enqueue_scripts', array( $this, 'register_translation_event_js' ) );
    6672        add_action( 'init', array( $this, 'register_event_post_type' ) );
     73        add_action( 'init', array( $this, 'send_notifications' ) );
    6774        add_action( 'add_meta_boxes', array( $this, 'event_meta_boxes' ) );
    6875        add_action( 'save_post', array( $this, 'save_event_meta_boxes' ) );
     
    7279        add_action( 'gp_init', array( $this, 'gp_init' ) );
    7380        add_action( 'gp_before_translation_table', array( $this, 'add_active_events_current_user' ) );
     81        add_filter( 'wp_post_revision_meta_keys', array( $this, 'wp_post_revision_meta_keys' ) );
     82        add_filter( 'pre_wp_unique_post_slug', array( $this, 'pre_wp_unique_post_slug' ), 10, 6 );
    7483
    7584        if ( is_admin() ) {
    7685            Upgrade::upgrade_if_needed();
    7786        }
     87
     88        $this->event_capabilities = new Event_Capabilities(
     89            self::get_event_repository(),
     90            self::get_attendee_repository(),
     91            new Stats_Calculator()
     92        );
     93        $this->event_capabilities->register_hooks();
    7894    }
    7995
    8096    public function gp_init() {
     97        $locale = '(' . implode( '|', wp_list_pluck( GP_Locales::locales(), 'slug' ) ) . ')';
     98        $slug   = '((?:2[0-9]{3}/)?[a-z0-9_-]+)';
     99        $status = '(waiting)';
     100        $id     = '(\d+)';
     101
    81102        GP::$router->add( '/events?', array( 'Wporg\TranslationEvents\Routes\Event\List_Route', 'handle' ) );
     103        GP::$router->add( '/events/trashed?', array( 'Wporg\TranslationEvents\Routes\Event\List_Trashed_Route', 'handle' ) );
    82104        GP::$router->add( '/events/new', array( 'Wporg\TranslationEvents\Routes\Event\Create_Route', 'handle' ) );
    83         GP::$router->add( '/events/edit/(\d+)', array( 'Wporg\TranslationEvents\Routes\Event\Edit_Route', 'handle' ) );
    84         GP::$router->add( '/events/attend/(\d+)', array( 'Wporg\TranslationEvents\Routes\User\Attend_Event_Route', 'handle' ), 'post' );
    85         GP::$router->add( '/events/host/(\d+)/(\d+)', array( 'Wporg\TranslationEvents\Routes\User\Host_Event_Route', 'handle' ), 'post' );
     105        GP::$router->add( "/events/edit/$id", array( 'Wporg\TranslationEvents\Routes\Event\Edit_Route', 'handle' ) );
     106        GP::$router->add( "/events/trash/$id", array( 'Wporg\TranslationEvents\Routes\Event\Trash_Route', 'handle' ) );
     107        GP::$router->add( "/events/delete/$id", array( 'Wporg\TranslationEvents\Routes\Event\Delete_Route', 'handle' ) );
     108        GP::$router->add( "/events/attend/$id", array( 'Wporg\TranslationEvents\Routes\User\Attend_Event_Route', 'handle' ), 'post' );
     109        GP::$router->add( "/events/host/$id/$id", array( 'Wporg\TranslationEvents\Routes\User\Host_Event_Route', 'handle' ), 'post' );
    86110        GP::$router->add( '/events/my-events', array( 'Wporg\TranslationEvents\Routes\User\My_Events_Route', 'handle' ) );
    87         GP::$router->add( '/events/([a-z0-9_-]+)', array( 'Wporg\TranslationEvents\Routes\Event\Details_Route', 'handle' ) );
     111        GP::$router->add( "/events/$slug/translations/$locale/$status", array( 'Wporg\TranslationEvents\Routes\Event\Translations_Route', 'handle' ) );
     112        GP::$router->add( "/events/$slug/translations/$locale", array( 'Wporg\TranslationEvents\Routes\Event\Translations_Route', 'handle' ) );
     113        GP::$router->add( "/events/$slug", array( 'Wporg\TranslationEvents\Routes\Event\Details_Route', 'handle' ) );
     114        GP::$router->add( "/events/$slug/attendees", array( 'Wporg\TranslationEvents\Routes\Attendee\List_Route', 'handle' ) );
    88115
    89116        $stats_listener = new Stats_Listener(
     
    113140
    114141        $args = array(
    115             'labels'      => $labels,
    116             'public'      => true,
    117             'has_archive' => true,
    118             'menu_icon'   => 'dashicons-calendar',
    119             'supports'    => array( 'title', 'editor', 'thumbnail', 'revisions' ),
    120             'rewrite'     => array( 'slug' => 'events' ),
    121             'show_ui'     => false,
     142            'labels'       => $labels,
     143            'public'       => true,
     144            'has_archive'  => true,
     145            'hierarchical' => true,
     146            'menu_icon'    => 'dashicons-calendar',
     147            'supports'     => array( 'title', 'editor', 'thumbnail', 'revisions' ),
     148            'rewrite'      => array( 'slug' => 'events' ),
     149            'show_ui'      => false,
    122150        );
    123151
     
    163191            }
    164192        }
    165 
    166193        $fields = array( 'event_start', 'event_end' );
    167194        foreach ( $fields as $field ) {
     
    219246
    220247            if ( null === $attendee ) {
    221                 $attendee = new Attendee( $event_id, $user_id );
    222                 $attendee->mark_as_host();
     248                $attendee = new Attendee( $event_id, $user_id, true );
    223249                $attendee_repository->insert_attendee( $attendee );
    224250            }
     
    237263            return $items;
    238264        }
    239         $new[ esc_url( gp_url( '/events/' ) ) ] = esc_html__( 'Events', 'gp-translation-events' );
     265        $new[ esc_url( Urls::events_home() ) ] = esc_html__( 'Events', 'gp-translation-events' );
    240266        return array_merge( $items, $new );
    241267    }
     
    254280    public function generate_event_slug( array $data, array $postarr ): array {
    255281        if ( self::CPT === $data['post_type'] ) {
     282            if ( isset( $data['post_name'] ) && preg_match( '/^2[0-9]+$/', $data['post_name'] ) ) {
     283                return $data;
     284            }
    256285            if ( 'draft' === $data['post_status'] ) {
    257286                $data['post_name'] = sanitize_title( $data['post_title'] );
     
    278307     */
    279308    public function add_active_events_current_user(): void {
    280         $attendee_repository      = new Attendee_Repository();
    281         $user_attending_event_ids = $attendee_repository->get_events_for_user( get_current_user_id() );
    282         if ( empty( $user_attending_event_ids ) ) {
    283             return;
    284         }
    285 
    286         $current_datetime_utc       = ( new DateTime( 'now', new DateTimeZone( 'UTC' ) ) )->format( 'Y-m-d H:i:s' );
    287         $user_attending_events_args = array(
    288             'post_type'   => self::CPT,
    289             'post__in'    => $user_attending_event_ids,
    290             'post_status' => 'publish',
    291             // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
    292             'meta_query'  => array(
    293                 array(
    294                     'key'     => '_event_start',
    295                     'value'   => $current_datetime_utc,
    296                     'compare' => '<=',
    297                     'type'    => 'DATETIME',
    298                 ),
    299                 array(
    300                     'key'     => '_event_end',
    301                     'value'   => $current_datetime_utc,
    302                     'compare' => '>=',
    303                     'type'    => 'DATETIME',
    304                 ),
    305             ),
    306             // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
    307             'meta_key'    => '_event_start',
    308             'orderby'     => 'meta_value',
    309             'order'       => 'ASC',
    310         );
    311 
    312         $user_attending_events_query = new WP_Query( $user_attending_events_args );
    313         $number_of_events            = $user_attending_events_query->post_count;
     309        $event_repository            = self::get_event_repository();
     310        $user_attending_events_query = $event_repository->get_current_events_for_user( get_current_user_id() );
     311        $events                      = $user_attending_events_query->events;
     312
     313        $number_of_events = count( $events );
    314314        if ( 0 === $number_of_events ) {
    315315            return;
     
    320320        $content .= sprintf( _n( 'Contributing to %d event:', 'Contributing to %d events:', $number_of_events, 'gp-translation-events' ), $number_of_events );
    321321        $content .= '&nbsp;&nbsp;';
     322
     323        foreach ( array_splice( $events, 0, 2 ) as $event ) {
     324            $content .= '<span class="active-events-before-translation-table"><a href="' . Urls::event_details( $event->id() ) . '" target="_blank">' . esc_html( $event->title() ) . '</a></span>';
     325        }
     326
    322327        if ( $number_of_events > 3 ) {
    323             $counter = 0;
    324             while ( $user_attending_events_query->have_posts() && $counter < 2 ) {
    325                 $user_attending_events_query->the_post();
    326                 $url      = esc_url( gp_url( '/events/' . get_post_field( 'post_name', get_post() ) ) );
    327                 $content .= '<span class="active-events-before-translation-table"><a href="' . $url . '" target="_blank">' . get_the_title() . '</a></span>';
    328                 ++$counter;
    329             }
    330 
    331328            $remaining_events = $number_of_events - 2;
    332             $url              = esc_url( gp_url( '/events/' ) );
    333329            /* translators: %d: Number of remaining events */
    334             $content .= '<span class="remaining-events"><a href="' . $url . '" target="_blank">' . sprintf( esc_html__( ' and %d more events.', 'gp-translation-events' ), $remaining_events ) . '</a></span>';
    335 
    336         } else {
    337             while ( $user_attending_events_query->have_posts() ) {
    338                 $user_attending_events_query->the_post();
    339                 $url      = esc_url( gp_url( '/events/' . get_post_field( 'post_name', get_post() ) ) );
    340                 $content .= '<span class="active-events-before-translation-table"><a href="' . $url . '" target="_blank">' . get_the_title() . '</a></span>';
    341             }
    342         }
     330            $content .= '<span class="remaining-events"><a href="' . esc_url( Urls::events_home() ) . '" target="_blank">' . sprintf( esc_html__( ' and %d more events.', 'gp-translation-events' ), $remaining_events ) . '</a></span>';
     331        }
     332
    343333        $content .= '</div>';
    344334
     
    360350        );
    361351    }
     352
     353    /**
     354     * Send notifications for the events.
     355     */
     356    public function send_notifications() {
     357        new Notifications_Send( self::get_event_repository(), self::get_attendee_repository() );
     358    }
     359
     360    /**
     361     * Add the event meta keys to the list of meta keys to keep in post revisions.
     362     *
     363     * @param array $keys The list of meta keys to keep in post revisions.
     364     *
     365     * @return array The modified list of meta keys to keep in post revisions.
     366     */
     367    public function wp_post_revision_meta_keys( array $keys ): array {
     368        $meta_keys_to_keep = array( '_event_start', '_event_end', '_event_timezone', '_hosts' );
     369        return array_merge( $keys, $meta_keys_to_keep );
     370    }
     371
     372    public function pre_wp_unique_post_slug( $override_slug, string $slug, int $post_id, string $post_status, string $post_type, int $post_parent ) {
     373        if ( self::CPT !== $post_type || $post_parent ) {
     374            return $override_slug;
     375        }
     376        // Normally the slug is not allowed to be a year, this overrides it since we have a CPT and translate.wordpress.org doesn't have blog posts.
     377        if ( preg_match( '/^2[0-9]{3}$/', $slug ) ) {
     378            return $slug;
     379        }
     380        return $override_slug;
     381    }
    362382}
    363383Translation_Events::get_instance();
Note: See TracChangeset for help on using the changeset viewer.