Making WordPress.org

Changeset 13683


Ignore:
Timestamp:
05/09/2024 08:33:51 AM (2 years 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.