Making WordPress.org

Changeset 13529


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

Translate: Sync "Translation Events" from GitHub

Location:
sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events
Files:
14 added
2 deleted
16 edited

Legend:

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

    r13299 r13529  
    44    vertical-align: top;
    55}
     6
    67.translation-event-form #event-title,
    78.translation-event-form #event-description {
    89    width: 30%;
    910}
     11
    1012.translation-event-form input[type="text"],
    1113.translation-event-form input[type="date"] {
    1214    width: 13%;
    1315}
     16
    1417.translation-event-form div {
    1518    margin-top: 1em;
    1619}
     20
    1721.translation-event-form #submit-event {
    1822    margin-left: 10%;
     
    2125    display: block;
    2226}
    23 .event-page-title {
    24     border-bottom: #d9d8d8 thin solid;
    25     padding-bottom: .5em;
    26 }
     27
    2728.event-details-page {
    2829    border-bottom: #cfd4d4 thin solid;
     
    3031    margin: 0 auto;
    3132}
     33
    3234.event-details-left {
    3335    width: 77%;
     
    3739    padding-right: 1em;
    3840}
     41
    3942.event-details-right {
    4043    width: 22%;
     
    4245    padding-top: 1em;
    4346}
     47
    4448.event-details-stats {
    4549    clear: both;
    46     margin: 1em;
     50    margin: 0;
     51}
     52
     53.event-details-stats table {
     54    margin: 1rem;
    4755}
    4856.event-details-stats table {
     
    5058    table-layout: fixed;
    5159}
    52 .event-details-stats table th, .event-details-stats table td {
     60
     61.event-details-stats table th,
     62.event-details-stats table td {
    5363    padding: 1em;
    5464    text-align: center;
    5565}
     66
    5667.event-details-stats table td:first-child {
    5768    text-align: left;
    5869    word-break: break-all;
    5970}
     71
    6072.event-details-stats table tr {
    6173    border-bottom: thin solid #f0f0f0;
    6274}
     75
    6376.event-details-stats table th {
    6477    background-color: #e9e9e9;
     
    6679    word-break: break-all;
    6780}
     81
    6882.event-details-stats-totals td {
    6983    font-weight: bold;
    7084}
    71 .event-contributors li {
     85
     86.event-contributors li, .event-attendees li {
    7287    display: inline-block;
    7388    list-style-type: none;
     
    7691    width: 15em;
    7792}
    78 .event-contributors li .avatar {
     93
     94.event-contributors li .avatar, .event-attendees li .avatar {
    7995    border-radius: 50%;
    8096    vertical-align: middle;
    8197}
     98
     99.event-attendees h2 {
     100    margin-bottom: 0;
     101}
     102
     103span.event-creator, span.event-you {
     104    display: block;
     105    margin-left: 55px;
     106    margin-top: 1em;
     107    font-size: smaller;
     108}
    82109.hide-event-url {
    83110    display: none;
    84111}
     112
    85113.translation-event-form button.save-draft {
    86114    background: #fff;
    87115    color: var(--gp-color-btn-primary-bg);
    88116}
     117
    89118#event-url span {
    90119    width: 10%;
    91120    display: inline-block;
    92121}
     122
    93123span.event-list-date {
    94124    display: block;
     
    97127    font-weight: normal;
    98128}
     129
    99130span.event-list-date.events-i-am-attending {
    100131    font-size: .8em;
    101132    font-weight: normal;
    102133}
    103 .event-list-item a{
     134
     135.event-list-item a {
    104136    font-weight: bold;
    105137    font-size: 1.2em;
    106138}
     139
    107140li.event-list-item {
    108141    list-style-type: none;
    109 }
     142    margin-bottom: .5em;
     143}
     144
    110145li.event-list-item p {
    111146    margin-top: 0;
     
    113148    font-size: .95em;
    114149}
     150
    115151h2.event_page_title {
    116152    border-bottom: #e0e0e0 thin solid;
     
    118154    font-weight: bold;
    119155}
     156
    120157.event-list-top-bar {
    121     display: block;
    122158    width: 80%;
    123159    margin: 0 auto;
    124     text-align: right;
    125 }
     160    display: grid;
     161    grid-template-columns: 1fr auto;
     162    align-items: center;
     163    border-bottom: #d9d8d8 thin solid;
     164    padding-bottom: .5em;
     165}
     166
    126167.event-list-nav li {
    127168    display: inline;
    128169    margin: 0 0.1rem;
    129170}
     171
    130172.event-list-nav li a {
    131173    text-decoration: none;
    132174    font-weight: bold;
    133175}
     176
    134177.event-list-nav li:not(:first-child):not(:last-child):before {
    135178    content: " | ";
    136179}
     180
    137181.event-left-col {
    138182    width: 75%;
     
    140184    border-right: var(--gp-color-secondary-400) thin solid;
    141185}
     186
    142187.event-right-col {
    143188    width: 25%;
     
    145190    padding-left: 1em;
    146191}
     192
    147193.event-attending-list {
    148194    list-style-type: none;
    149195    padding: 0;
    150196}
     197
    151198.event-attending-list li {
    152199    margin-bottom: 1em;
    153200}
     201
    154202.event-list-nav li a.button {
    155203    background-color: var(--gp-color-btn-primary-bg) !important;
    156204    color: #fff !important;
    157205}
     206
    158207a.event-link-draft {
    159     color:#80807f;
    160 }
    161 span.event-label-draft, span.event-details-join-expired, span.active-events-before-translation-table {
     208    color: #80807f;
     209}
     210
     211span.event-label-draft,
     212span.event-details-join-expired,
     213span.active-events-before-translation-table {
    162214    font-weight: 500;
    163     color: var( --gp-color-bubble-inactive-project-text );
    164     border: 1px solid var( --gp-color-bubble-inactive-project-text );
     215    color: var(--gp-color-bubble-inactive-project-text);
     216    border: 1px solid var(--gp-color-bubble-inactive-project-text);
    165217    font-size: .7em;
    166218    margin-right: 0.3em;
     
    171223    text-transform: capitalize;
    172224}
     225
    173226span.active-events-before-translation-table a {
    174     color: var( --gp-color-bubble-inactive-project-text );
    175     text-decoration: none;
    176 }
     227    color: var(--gp-color-bubble-inactive-project-text);
     228    text-decoration: none;
     229}
     230
    177231.active-events-before-translation-table {
    178232    width: 100%;
    179     border: 1px solid var( --gp-color-border-default );
    180     background: var( --gp-color-status-waiting-subtle );
     233    border: 1px solid var(--gp-color-border-default);
     234    background: var(--gp-color-status-waiting-subtle);
    181235    margin: 1rem 0;
    182236    padding: 0.3rem 0.8rem;
    183237}
     238
    184239.event-page-wrapper {
    185240    margin: 0 auto;
    186241    width: 80%;
    187242}
     243
    188244.event-list {
    189245    margin: 0;
    190246    padding: 0;
    191247}
    192 .event-details-head {
    193     border-bottom: var(--gp-color-secondary-400) thin solid;
    194 }
     248
     249input[type="submit"].remove-as-host, input[type="submit"].convert-to-host {
     250    text-align: center;
     251    display: inline;
     252    margin-top: 1em;
     253    font-weight: bold;
     254}
     255
    195256input[type="submit"].attend-btn {
    196257    width: 100%;
     
    200261    font-weight: bold;
    201262}
    202 input[type="submit"].attending-btn, a.button.is-primary.attend-btn {
     263
     264input[type="submit"].attending-btn,
     265a.button.is-primary.attend-btn {
    203266    width: 100%;
    204267    text-align: center;
     
    206269    margin-top: 1em;
    207270}
     271
    208272.event-details-date {
    209273    background: #f4f4f4;
    210274    color: #606161;
    211     padding: .5em;
     275    padding: 0.1em .5em;
    212276    font-size: .9em;
    213277    line-height: 1.8em;
    214278    font-weight: 500;
    215 }
     279    margin-bottom: 1em;
     280}
     281
    216282.event-page-edit-link {
    217283    float: right;
    218284    text-decoration: none;
    219 }
     285    justify-self: end;
     286    grid-column: 2;
     287}
     288
    220289.event-stats-summary {
    221290    margin-top: 1em;
    222291}
     292
    223293.event-stats-summary summary {
    224294    background: #f8f8f8;
     
    229299    font-size: .9em;
    230300}
     301
    231302.event-stats-summary p.event-stats-text {
    232303    margin: 0;
     
    236307    border-top: thin solid #e0e0e0;
    237308}
    238 time.event-utc-time {
    239     display: block;
     309
     310.event-details-date time {
    240311    font-size: .95em;
    241 }
    242 .event-utc-time:first-of-type{
     312    font-weight: normal;
     313}
     314
     315.event-details-date time:last-child {
     316    margin-bottom: 0;
     317}
     318
     319.event-details-date time.full-time {
     320    display: block;
    243321    border-bottom: #cdcdcd thin solid;
    244322    padding-bottom: 0.5em;
    245323    margin-bottom: 0.5em;
    246324}
     325
    247326span.event-details-date-label {
    248327    font-weight: bold;
    249328    color: #5a5a5a;
    250329    display: block;
     330}
     331
     332.event-host {
     333    display: flex;
     334    grid-column: 1;
     335}
     336
     337p.event-sub-head {
     338    grid-column: 1 / -1;
     339    margin-top: 0;
     340}
     341
     342a.event-page-edit-link {
     343    font-size: .9em;
     344    text-decoration: none;
     345}
     346
     347a.event-page-edit-link:hover {
     348    border-bottom: var(--gp-color-btn-primary-bg) thin solid;
     349    text-decoration: none;
     350}
     351ul.text-snippets {
     352    padding: 0;
     353    margin-left: 160px;
     354}
     355.first-time-contributor-tada::after {
     356    content: ' 🎉';
    251357}
    252358
    253359/* show the event-details-right below instead of on the right on mobile */
    254360@media (max-width: 768px) {
    255     .event-page-wrapper, .event-details-right, .event-details-left {
     361
     362    .event-page-wrapper,
     363    .event-details-right,
     364    .event-details-left {
    256365        width: 100%;
    257366        float: none;
    258367    }
     368
    259369    .event-details-left {
    260370        border-right: none;
     
    262372        padding: 0;
    263373    }
    264 
    265     .event-details-stats {
    266         margin: 0;
    267     }
    268 }
     374}
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/assets/js/translation-events.js

    r13268 r13529  
    1010                validateEventDates();
    1111                convertToUserLocalTime();
     12                setInterval( convertToUserLocalTime, 10000 );
    1213
    1314                $( '.submit-event' ).on(
     
    2829                    }
    2930                );
     31
     32                $( '.text-snippet' ).on(
     33                    'click',
     34                    function ( e ) {
     35                        e.preventDefault();
     36                        var textArea        = $( this ).closest( 'div' ).find( 'textarea' );
     37                        var textAreaContent = textArea.val();
     38                        textArea.val( textAreaContent + $( this ).data( 'snippet' ) );
     39                    }
     40                );
    3041            }
    3142        );
     
    3849         */
    3950        function handleSubmit( eventStatus, isDraft ) {
     51            const $form = $( '.translation-event-form' );
     52            if ( ! $form[0].reportValidity() ) {
     53                return;
     54            }
     55
     56            if ( '' === $( '#event-title' ).val() ) {
     57                $gp.notices.error( 'Event title must be set.' );
     58                return;
     59            }
    4060            if ( '' === $( '#event-start' ).val() ) {
    4161                $gp.notices.error( 'Event start date and time must be set.' );
     
    5777            }
    5878            $( '#event-form-action' ).val( eventStatus );
    59             const $form        = $( '.translation-event-form' );
    6079            const $is_creation = $( '#form-name' ).val() === 'create_event';
    6180
     
    149168            timeElements.forEach(
    150169                function ( timeEl ) {
    151                     const eventDateObj         = new Date( timeEl.getAttribute( 'datetime' ) );
     170                    const datetime = timeEl.getAttribute( 'datetime' );
     171                    if ( ! datetime ) {
     172                        return;
     173                    }
     174                    const eventDateObj = new Date( datetime );
     175                    timeEl.title       = eventDateObj.toUTCString();
     176
    152177                    const userTimezoneOffset   = new Date().getTimezoneOffset();
    153178                    const userTimezoneOffsetMs = userTimezoneOffset * 60 * 1000;
    154179                    const userLocalDateTime    = new Date( eventDateObj.getTime() - userTimezoneOffsetMs );
    155180
    156                     const options      = {
     181                    if ( timeEl.classList.contains( 'relative' ) ) {
     182                        // Display the relative time.
     183                        const now    = new Date();
     184                        let diff     = userLocalDateTime - now;
     185                        let in_text  = 'in ';
     186                        let ago_text = '';
     187                        if ( diff < 0 ) {
     188                            if ( timeEl.classList.contains( 'future' ) ) {
     189                                // If an event transitions from future to past, reload the page to move it from active to past events and vice versa.
     190                                location.reload();
     191                            }
     192                            in_text  = '';
     193                            ago_text = ' ago';
     194                            diff     = - diff;
     195                        }
     196
     197                        const seconds    = Math.floor( diff / 1000 );
     198                        const minutes    = Math.floor( seconds / 60 );
     199                        const hours      = Math.floor( minutes / 60 );
     200                        const days       = Math.floor( hours / 24 );
     201                        const weeks      = Math.floor( days / 7 );
     202                        const months     = Math.floor( days / 30 );
     203                        const years      = Math.floor( days / 365.25 );
     204                        let relativeTime = '';
     205                        if ( years > 1 ) {
     206                            if ( ! timeEl.classList.contains( 'hide-if-too-far' ) ) {
     207                                relativeTime = years + ' year' + ( years > 1 ? 's' : '' );
     208                            } else {
     209                                in_text = '';
     210                            }
     211                        } else if ( months > 1 ) {
     212                            if ( ! timeEl.classList.contains( 'hide-if-too-far' ) ) {
     213                                relativeTime = months + ' month' + ( months > 1 ? 's' : '' );
     214                            } else {
     215                                in_text = '';
     216                            }
     217                        } else if ( weeks > 1 ) {
     218                            if ( ! timeEl.classList.contains( 'hide-if-too-far' ) || weeks < 3 ) {
     219                                relativeTime = weeks + ' week' + ( weeks > 1 ? 's' : '' );
     220                            } else {
     221                                in_text = '';
     222                            }
     223                        } else if ( days > 0 ) {
     224                            relativeTime = days + ' day' + ( days > 1 ? 's' : '' );
     225                        } else if ( hours > 0 ) {
     226                            relativeTime = hours + ' hour' + ( hours > 1 ? 's' : '' );
     227                        } else if ( minutes > 0 ) {
     228                            relativeTime = minutes + ' minute' + ( minutes > 1 ? 's' : '' );
     229                        } else {
     230                            relativeTime = 'less than a minute';
     231                        }
     232                        timeEl.textContent = in_text + relativeTime + ago_text;
     233                        return;
     234                    }
     235
     236                    const options = {
    157237                        weekday: 'short',
    158238                        year: 'numeric',
     
    163243                        timeZoneName: 'short'
    164244                    };
     245                    if ( timeEl.dataset.format ) {
     246                        if ( timeEl.dataset.format.includes( 'l' ) ) {
     247                            options.weekday = 'long';
     248                        } else if ( ! timeEl.dataset.format.includes( 'D' ) ) {
     249                            delete options.weekday;
     250                        }
     251                        if ( timeEl.dataset.format.includes( 'F' ) ) {
     252                            options.month = 'long';
     253                        } else if ( timeEl.dataset.format.includes( 'm' ) || timeEl.dataset.format.includes( 'n' ) ) {
     254                            options.month = 'numeric';
     255                        }
     256                    }
    165257                    timeEl.textContent = userLocalDateTime.toLocaleTimeString( navigator.language, options );
    166258                }
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/create.php

    r13298 r13529  
    33namespace Wporg\TranslationEvents\Routes\Event;
    44
     5use Wporg\TranslationEvents\Event\Event_End_Date;
     6use Wporg\TranslationEvents\Event\Event_Start_Date;
    57use Wporg\TranslationEvents\Routes\Route;
    68
     
    1517            exit;
    1618        }
    17         $event_form_title         = 'Create Event';
     19        $event_page_title         = 'Create Event';
    1820        $event_form_name          = 'create_event';
    1921        $css_show_url             = 'hide-event-url';
     
    2123        $event_title              = '';
    2224        $event_description        = '';
    23         $event_timezone           = '';
    24         $event_start              = '';
    25         $event_end                = '';
    2625        $event_url                = '';
    2726        $create_delete_button     = true;
    2827        $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' ) );
    2931
    3032        $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

    r13298 r13529  
    55use Exception;
    66use GP;
     7use Wporg\TranslationEvents\Attendee\Attendee;
     8use Wporg\TranslationEvents\Attendee\Attendee_Repository;
     9use Wporg\TranslationEvents\Event\Event_Repository_Interface;
    710use Wporg\TranslationEvents\Routes\Route;
    811use Wporg\TranslationEvents\Stats_Calculator;
     
    1316 */
    1417class Details_Route extends Route {
     18    private Event_Repository_Interface $event_repository;
     19    private Attendee_Repository $attendee_repository;
     20
     21    public function __construct() {
     22        parent::__construct();
     23        $this->event_repository    = Translation_Events::get_event_repository();
     24        $this->attendee_repository = Translation_Events::get_attendee_repository();
     25    }
     26
    1527    public function handle( string $event_slug ): void {
    1628        $user  = wp_get_current_user();
    1729        $event = get_page_by_path( $event_slug, OBJECT, Translation_Events::CPT );
     30        if ( ! $event ) {
     31            $this->die_with_404();
     32        }
     33        $event = $this->event_repository->get_event( $event->ID );
    1834        if ( ! $event ) {
    1935            $this->die_with_404();
     
    2642         */
    2743        $can_crud_event = apply_filters( 'gp_translation_events_can_crud_event', GP::$permission->current_user_can( 'admin' ) );
    28         if ( 'publish' !== $event->post_status && ! $can_crud_event ) {
     44        if ( 'publish' !== $event->status() && ! $can_crud_event ) {
    2945            $this->die_with_error( esc_html__( 'You are not authorized to view this page.', 'gp-translation-events' ), 403 );
    3046        }
    3147
    32         $event_id            = $event->ID;
    33         $event_title         = $event->post_title;
    34         $event_description   = $event->post_content;
    35         $event_start         = get_post_meta( $event->ID, '_event_start', true ) ?: '';
    36         $event_end           = get_post_meta( $event->ID, '_event_end', true ) ?: '';
    37         $attending_event_ids = get_user_meta( $user->ID, Translation_Events::USER_META_KEY_ATTENDING, true ) ?: array();
    38         $user_is_attending   = isset( $attending_event_ids[ $event_id ] );
     48        $event_id          = $event->id();
     49        $event_title       = $event->title();
     50        $event_description = $event->description();
     51        $event_start       = $event->start();
     52        $event_end         = $event->end();
     53
     54        $attendee          = $this->attendee_repository->get_attendee( $event->id(), $user->ID );
     55        $user_is_attending = $attendee instanceof Attendee;
    3956
    4057        $stats_calculator = new Stats_Calculator();
    4158        try {
    42             $event_stats  = $stats_calculator->for_event( $event );
    43             $contributors = $stats_calculator->get_contributors( $event );
     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() );
    4465        } catch ( Exception $e ) {
    4566            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
     
    4869        }
    4970
     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
    5076        $this->tmpl( 'event', get_defined_vars() );
    5177    }
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/edit.php

    r13298 r13529  
    33namespace Wporg\TranslationEvents\Routes\Event;
    44
    5 use DateTime;
    6 use DateTimeZone;
    7 use Exception;
     5use Wporg\TranslationEvents\Attendee\Attendee;
     6use Wporg\TranslationEvents\Attendee\Attendee_Repository;
     7use Wporg\TranslationEvents\Event\Event_Repository_Interface;
    88use Wporg\TranslationEvents\Routes\Route;
    99use Wporg\TranslationEvents\Stats_Calculator;
     
    1414 */
    1515class Edit_Route extends Route {
     16    private Event_Repository_Interface $event_repository;
     17    private Attendee_Repository $attendee_repository;
     18
     19    public function __construct() {
     20        parent::__construct();
     21        $this->event_repository    = Translation_Events::get_event_repository();
     22        $this->attendee_repository = Translation_Events::get_attendee_repository();
     23    }
     24
    1625    public function handle( int $event_id ): void {
    1726        global $wp;
     
    2029            exit;
    2130        }
    22         $event = get_post( $event_id );
    23         if ( ! $event || Translation_Events::CPT !== $event->post_type || ! ( current_user_can( 'edit_post', $event->ID ) || intval( $event->post_author ) === get_current_user_id() ) ) {
     31        $event    = $this->event_repository->get_event( $event_id );
     32        $attendee = $this->attendee_repository->get_attendee( $event->id(), get_current_user_id() );
     33
     34        if ( ! $event || ! ( ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'edit_post', $event->id() ) || $event->author_id() === get_current_user_id() ) ) {
    2435            $this->die_with_error( esc_html__( 'Event does not exist, or you do not have permission to edit it.', 'gp-translation-events' ), 403 );
    2536        }
    26         if ( 'trash' === $event->post_status ) {
     37        if ( 'trash' === $event->status() ) {
    2738            $this->die_with_error( esc_html__( 'You cannot edit a trashed event', 'gp-translation-events' ), 403 );
    2839        }
    2940
    3041        include ABSPATH . 'wp-admin/includes/post.php';
    31         $event_form_title              = 'Edit Event';
     42        $event_page_title              = 'Edit Event';
    3243        $event_form_name               = 'edit_event';
    3344        $css_show_url                  = '';
    34         $event_title                   = $event->post_title;
    35         $event_description             = $event->post_content;
    36         $event_status                  = $event->post_status;
    37         list( $permalink, $post_name ) = get_sample_permalink( $event_id );
     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() );
    3849        $permalink                     = str_replace( '%pagename%', $post_name, $permalink );
    3950        $event_url                     = get_site_url() . gp_url( wp_make_link_relative( $permalink ) );
    40         $event_timezone                = get_post_meta( $event_id, '_event_timezone', true ) ?: '';
     51        $event_timezone                = $event->timezone();
     52        $event_start                   = $event->start();
     53        $event_end                     = $event->end();
    4154        $create_delete_button          = false;
    4255        $visibility_delete_button      = 'inline-flex';
    4356
     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
    4461        $stats_calculator = new Stats_Calculator();
    45         if ( ! $stats_calculator->event_has_stats( $event ) ) {
     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() ) ) {
    4668            $current_user = wp_get_current_user();
    47             if ( $current_user->ID === $event->post_author || current_user_can( 'manage_options' ) ) {
     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() ) {
    4870                $create_delete_button = true;
    4971            }
    5072        }
    5173
    52         try {
    53             $event_start = self::convertToTimezone( get_post_meta( $event_id, '_event_start', true ), $event_timezone );
    54             $event_end   = self::convertToTimezone( get_post_meta( $event_id, '_event_end', true ), $event_timezone );
    55         } catch ( Exception $e ) {
    56             // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
    57             error_log( $e );
    58             $this->die_with_error( esc_html__( 'Something is wrong.', 'gp-translation-events' ) );
    59         }
    60 
    6174        $this->tmpl( 'events-form', get_defined_vars() );
    6275    }
    63 
    64     /**
    65      * Convert date time stored in UTC to a date time in a time zone.
    66      *
    67      * @param string $date_time The date time in UTC.
    68      * @param string $time_zone The time zone.
    69      *
    70      * @return string The date time in the time zone.
    71      * @throws Exception When date is invalid.
    72      */
    73     private static function convertToTimezone( string $date_time, string $time_zone ): string {
    74         return ( new DateTime( $date_time, new DateTimeZone( 'UTC' ) ) )->setTimezone( new DateTimeZone( $time_zone ) )->format( 'Y-m-d H:i:s' );
    75     }
    7676}
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/list.php

    r13298 r13529  
    77use Exception;
    88use WP_Query;
     9use Wporg\TranslationEvents\Event\Event_Repository_Interface;
    910use Wporg\TranslationEvents\Routes\Route;
    1011use Wporg\TranslationEvents\Translation_Events;
     
    1415 */
    1516class List_Route extends Route {
     17    private Event_Repository_Interface $event_repository;
     18
     19    public function __construct() {
     20        parent::__construct();
     21        $this->event_repository = Translation_Events::get_event_repository();
     22    }
     23
    1624    public function handle(): void {
    1725        $current_datetime_utc = null;
     
    5664        // phpcs:enable
    5765
    58         $current_events_args  = array(
    59             'post_type'      => Translation_Events::CPT,
    60             'posts_per_page' => 10,
    61             'paged'          => $_current_events_paged,
    62             'post_status'    => 'publish',
    63             // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
    64             'meta_query'     => array(
    65                 array(
    66                     'key'     => '_event_start',
    67                     'value'   => $current_datetime_utc,
    68                     'compare' => '<=',
    69                     'type'    => 'DATETIME',
    70                 ),
    71                 array(
    72                     'key'     => '_event_end',
    73                     'value'   => $current_datetime_utc,
    74                     'compare' => '>=',
    75                     'type'    => 'DATETIME',
    76                 ),
    77             ),
    78             'orderby'        => 'meta_value',
    79             'order'          => 'ASC',
    80         );
    81         $current_events_query = new WP_Query( $current_events_args );
    82 
    83         $upcoming_events_args  = array(
    84             'post_type'      => Translation_Events::CPT,
    85             'posts_per_page' => 10,
    86             'paged'          => $_upcoming_events_paged,
    87             'post_status'    => 'publish',
    88             // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
    89             'meta_query'     => array(
    90                 array(
    91                     'key'     => '_event_start',
    92                     'value'   => $current_datetime_utc,
    93                     'compare' => '>=',
    94                     'type'    => 'DATETIME',
    95                 ),
    96             ),
    97             'orderby'        => 'meta_value',
    98             'order'          => 'ASC',
    99         );
    100         $upcoming_events_query = new WP_Query( $upcoming_events_args );
    101 
    102         $past_events_args  = array(
    103             'post_type'      => Translation_Events::CPT,
    104             'posts_per_page' => 10,
    105             'paged'          => $_past_events_paged,
    106             'post_status'    => 'publish',
    107             // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
    108             'meta_query'     => array(
    109                 array(
    110                     'key'     => '_event_end',
    111                     'value'   => $current_datetime_utc,
    112                     'compare' => '<',
    113                     'type'    => 'DATETIME',
    114                 ),
    115             ),
    116             'orderby'        => 'meta_value',
    117             'order'          => 'ASC',
    118         );
    119         $past_events_query = new WP_Query( $past_events_args );
    120 
    121         $user_attending_events      = get_user_meta( get_current_user_id(), Translation_Events::USER_META_KEY_ATTENDING, true ) ?: array( 0 );
    122         $user_attending_events_args = array(
    123             'post_type'      => Translation_Events::CPT,
    124             'post__in'       => array_keys( $user_attending_events ),
    125             'posts_per_page' => 10,
    126             'paged'          => $_user_attending_events_paged,
    127             'post_status'    => 'publish',
    128             // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
    129             'meta_query'     => array(
    130                 array(
    131                     'key'     => '_event_end',
    132                     'value'   => $current_datetime_utc,
    133                     'compare' => '>',
    134                     'type'    => 'DATETIME',
    135                 ),
    136             ),
    137             // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
    138             'meta_key'       => '_event_start',
    139             'orderby'        => 'meta_value',
    140             'order'          => 'ASC',
    141         );
    142         $user_attending_events_query = new WP_Query( $user_attending_events_args );
     66        $current_events_query        = $this->event_repository->get_current_events( $_current_events_paged, 10 );
     67        $upcoming_events_query       = $this->event_repository->get_upcoming_events( $_upcoming_events_paged, 10 );
     68        $past_events_query           = $this->event_repository->get_past_events( $_past_events_paged, 10 );
     69        $user_attending_events_query = $this->event_repository->get_current_and_upcoming_events_for_user( get_current_user_id(), $_user_attending_events_paged, 10 );
    14370
    14471        $this->tmpl( 'events-list', get_defined_vars() );
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/user/attend-event.php

    r13298 r13529  
    33namespace Wporg\TranslationEvents\Routes\User;
    44
     5use Wporg\TranslationEvents\Attendee\Attendee;
     6use Wporg\TranslationEvents\Attendee\Attendee_Repository;
     7use Wporg\TranslationEvents\Event\Event_Repository_Interface;
    58use Wporg\TranslationEvents\Routes\Route;
    69use Wporg\TranslationEvents\Translation_Events;
     
    1215 */
    1316class Attend_Event_Route extends Route {
     17    private Event_Repository_Interface $event_repository;
     18    private Attendee_Repository $attendee_repository;
     19
     20    public function __construct() {
     21        parent::__construct();
     22        $this->event_repository    = Translation_Events::get_event_repository();
     23        $this->attendee_repository = Translation_Events::get_attendee_repository();
     24    }
     25
    1426    public function handle( int $event_id ): void {
    1527        $user = wp_get_current_user();
     
    1830        }
    1931
    20         $event = get_post( $event_id );
    21 
     32        $event = $this->event_repository->get_event( $event_id );
    2233        if ( ! $event ) {
    2334            $this->die_with_404();
    2435        }
    2536
    26         $event_ids = get_user_meta( $user->ID, Translation_Events::USER_META_KEY_ATTENDING, true ) ?? array();
    27         if ( ! $event_ids ) {
    28             $event_ids = array();
     37        $attendee = $this->attendee_repository->get_attendee( $event->id(), $user->ID );
     38        if ( $attendee instanceof Attendee && $attendee->is_host() && ( 1 === count( $this->attendee_repository->get_hosts( $event_id ) ) ) ) {
     39            $this->die_with_error( esc_html__( 'The event needs a host. Add a new host before stopping to attend the event.', 'gp-translation-events' ), 403 );
     40        }
     41        if ( $attendee instanceof Attendee ) {
     42            $this->attendee_repository->remove_attendee( $event->id(), $user->ID );
     43        } else {
     44            $attendee = new Attendee( $event->id(), $user->ID );
     45            $this->attendee_repository->insert_attendee( $attendee );
    2946        }
    3047
    31         if ( ! isset( $event_ids[ $event_id ] ) ) {
    32             // Not yet attending, mark as attending.
    33             $event_ids[ $event_id ] = true;
    34         } else {
    35             // Currently attending, mark as not attending.
    36             unset( $event_ids[ $event_id ] );
    37         }
    38 
    39         update_user_meta( $user->ID, Translation_Events::USER_META_KEY_ATTENDING, $event_ids );
    40 
    41         wp_safe_redirect( gp_url( "/events/$event->post_name" ) );
     48        wp_safe_redirect( gp_url( "/events/{$event->slug()}" ) );
    4249        exit;
    4350    }
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/user/my-events.php

    r13298 r13529  
    33namespace Wporg\TranslationEvents\Routes\User;
    44
    5 use DateTime;
    6 use DateTimeZone;
    7 use WP_Query;
     5use Wporg\TranslationEvents\Event\Event_Repository_Interface;
    86use Wporg\TranslationEvents\Routes\Route;
    97use Wporg\TranslationEvents\Translation_Events;
     
    1311 */
    1412class My_Events_Route extends Route {
     13    private Event_Repository_Interface $event_repository;
     14
     15    public function __construct() {
     16        parent::__construct();
     17        $this->event_repository = Translation_Events::get_event_repository();
     18    }
     19
    1520    public function handle(): void {
    1621        global $wp;
     
    2227
    2328        $_events_i_created_paged  = 1;
     29        $_events_i_hosted_paged   = 1;
    2430        $_events_i_attended_paged = 1;
    2531
     
    2935            if ( is_numeric( $value ) ) {
    3036                $_events_i_created_paged = (int) $value;
     37            }
     38        }
     39        if ( isset( $_GET['events_i_hosted_paged'] ) ) {
     40            $value = sanitize_text_field( wp_unslash( $_GET['events_i_hosted_paged'] ) );
     41            if ( is_numeric( $value ) ) {
     42                $_events_i_hosted_paged = (int) $value;
    3143            }
    3244        }
     
    3951        // phpcs:enable
    4052
    41         $user_id              = get_current_user_id();
    42         $events               = get_user_meta( $user_id, Translation_Events::USER_META_KEY_ATTENDING, true ) ?: array();
    43         $events               = array_keys( $events );
    44         $current_datetime_utc = ( new DateTime( 'now', new DateTimeZone( 'UTC' ) ) )->format( 'Y-m-d H:i:s' );
    45         $args                 = array(
    46             'post_type'              => Translation_Events::CPT,
    47             'posts_per_page'         => 10,
    48             'events_i_created_paged' => $_events_i_created_paged,
    49             'paged'                  => $_events_i_created_paged,
    50             'post_status'            => array( 'publish', 'draft' ),
    51             'author'                 => $user_id,
    52             // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
    53             'meta_key'               => '_event_start',
    54             'orderby'                => 'meta_value',
    55             'order'                  => 'DESC',
    56         );
    57         $events_i_created_query = new WP_Query( $args );
    58 
    59         $args = array(
    60             'post_type'               => Translation_Events::CPT,
    61             'posts_per_page'          => 10,
    62             'events_i_attended_paged' => $_events_i_attended_paged,
    63             'paged'                   => $_events_i_attended_paged,
    64             'post_status'             => 'publish',
    65             'post__in'                => $events,
    66             // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
    67             'meta_query'              => array(
    68                 array(
    69                     'key'     => '_event_end',
    70                     'value'   => $current_datetime_utc,
    71                     'compare' => '<',
    72                     'type'    => 'DATETIME',
    73                 ),
    74             ),
    75             // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
    76             'meta_key'                => '_event_end',
    77             'orderby'                 => 'meta_value',
    78             'order'                   => 'DESC',
    79         );
    80         $events_i_attended_query = new WP_Query( $args );
     53        $events_i_created_query  = $this->event_repository->get_events_created_by_user( get_current_user_id(), $_events_i_created_paged, 10 );
     54        $events_i_host_query     = $this->event_repository->get_events_hosted_by_user( get_current_user_id(), $_events_i_hosted_paged, 10 );
     55        $events_i_attended_query = $this->event_repository->get_past_events_for_user( get_current_user_id(), $_events_i_attended_paged, 10 );
    8156
    8257        $this->tmpl( 'events-my-events', get_defined_vars() );
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/stats-calculator.php

    r13298 r13529  
    66use WP_Post;
    77use WP_User;
     8use GP;
    89use GP_Locale;
    910use GP_Locales;
     11use DateTimeImmutable;
     12use DateTimeZone;
    1013
    1114class Stats_Row {
     
    8689     * @throws Exception When stats calculation failed.
    8790     */
    88     public function for_event( WP_Post $event ): Event_Stats {
     91    public function for_event( int $event_id ): Event_Stats {
    8992        $stats = new Event_Stats();
    9093        global $wpdb, $gp_table_prefix;
     
    99102                "
    100103                select locale,
    101                        sum(action = 'create') as created,
    102                        count(*) as total,
    103                        count(distinct user_id) as users
     104                    sum(action = 'create') as created,
     105                    count(*) as total,
     106                    count(distinct user_id) as users
    104107                from {$gp_table_prefix}event_actions
    105108                where event_id = %d
     
    107110            ",
    108111                array(
    109                     $event->ID,
     112                    $event_id,
    110113                )
    111114            )
     
    148151     * Get contributors for an event.
    149152     */
    150     public function get_contributors( WP_Post $event ): array {
     153    public function get_contributors( int $event_id ): array {
    151154        global $wpdb, $gp_table_prefix;
    152155
     
    165168            ",
    166169                array(
    167                     $event->ID,
     170                    $event_id,
    168171                )
    169172            )
     
    189192
    190193    /**
     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;
     285    }
     286
     287    /**
    191288     * Check if an event has stats.
    192289     *
    193      * @param WP_Post $event The event to check.
     290     * @param int $event_id The id of the event to check.
    194291     *
    195292     * @return bool True if the event has stats, false otherwise.
    196293     */
    197     public function event_has_stats( WP_Post $event ): bool {
     294    public function event_has_stats( int $event_id ): bool {
    198295        try {
    199             $stats = $this->for_event( $event );
     296            $stats = $this->for_event( $event_id );
    200297        } catch ( Exception $e ) {
    201298            return false;
     
    204301        return ! empty( $stats->rows() );
    205302    }
     303
     304    /**
     305     * Check if a user is a first time contributor.
     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 first time contributor, false otherwise.
     311     */
     312    public function is_first_time_contributor( $event_start, $user_id ) {
     313        global $wpdb, $gp_table_prefix;
     314
     315        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
     316        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
     317        // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
     318        // phpcs:disable WordPress.DB.DirectDatabaseQuery.SchemaChange
     319        $users_first_translation_date = $wpdb->get_var(
     320            $wpdb->prepare(
     321                "
     322            select min(date_added) from {$gp_table_prefix}translations where user_id = %d
     323        ",
     324                array(
     325                    $user_id,
     326                )
     327            )
     328        );
     329
     330        if ( get_userdata( $user_id ) && ! $users_first_translation_date ) {
     331            return true;
     332        }
     333        $event_start_date_time  = new DateTimeImmutable( $event_start->__toString(), new DateTimeZone( 'UTC' ) );
     334        $first_translation_date = new DateTimeImmutable( $users_first_translation_date, new DateTimeZone( 'UTC' ) );
     335        // A first time contributor is someone whose first translation was made not earlier than 24 hours before the event.
     336        $event_start_date_time = $event_start_date_time->modify( '-1 day' );
     337        return $event_start_date_time <= $first_translation_date;
     338    }
    206339}
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/stats-listener.php

    r13298 r13529  
    88use GP_Translation;
    99use GP_Translation_Set;
     10use Wporg\TranslationEvents\Attendee\Attendee_Repository;
     11use Wporg\TranslationEvents\Event\Event;
     12use Wporg\TranslationEvents\Event\Event_Repository_Interface;
    1013
    1114class Stats_Listener {
     
    1518    const ACTION_REQUEST_CHANGES = 'request_changes';
    1619
    17     private Active_Events_Cache $active_events_cache;
     20    private Attendee_Repository $attendee_repository;
     21    private Event_Repository_Interface $event_repository;
    1822
    19     public function __construct( Active_Events_Cache $active_events_cache ) {
    20         $this->active_events_cache = $active_events_cache;
     23    public function __construct(
     24        Event_Repository_Interface $event_repository,
     25        Attendee_Repository $attendee_repository
     26    ) {
     27        $this->event_repository    = $event_repository;
     28        $this->attendee_repository = $attendee_repository;
    2129    }
    2230
     
    7078        try {
    7179            // Get events that are active when the action happened, for which the user is registered for.
    72             $active_events = $this->get_active_events( $happened_at );
    73             $events        = $this->select_events_user_is_registered_for( $active_events, $user_id );
     80            $active_events = $this->event_repository->get_current_events();
     81            $events        = $this->select_events_user_is_registered_for( $active_events->events, $user_id );
    7482
    7583            // phpcs:ignore Generic.Commenting.DocComment.MissingShort
     
    108116
    109117    /**
    110      * Get active events at a given time.
    111      *
    112      * @return Event[]
    113      * @throws Exception When it fails to get active events.
    114      */
    115     private function get_active_events( DateTimeImmutable $at ): array {
    116         $events = $this->active_events_cache->get();
    117         if ( null === $events ) {
    118             $cache_duration = Active_Events_Cache::CACHE_DURATION;
    119             $boundary_start = $at;
    120             $boundary_end   = $at->modify( "+$cache_duration seconds" );
    121 
    122             // Get events for which start is before $boundary_end AND end is after $boundary_start.
    123             $event_ids = get_posts(
    124                 array(
    125                     'post_type'      => Translation_Events::CPT,
    126                     'post_status'    => 'publish',
    127                     'posts_per_page' => - 1,
    128                     'fields'         => 'ids',
    129                     'meta_query'     => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
    130                         array(
    131                             'key'     => '_event_start',
    132                             'value'   => $boundary_end->format( 'Y-m-d H:i:s' ),
    133                             'compare' => '<',
    134                             'type'    => 'DATETIME',
    135                         ),
    136                         array(
    137                             'key'     => '_event_end',
    138                             'value'   => $boundary_start->format( 'Y-m-d H:i:s' ),
    139                             'compare' => '>',
    140                             'type'    => 'DATETIME',
    141                         ),
    142                     ),
    143                 ),
    144             );
    145 
    146             $events = array();
    147             foreach ( $event_ids as $event_id ) {
    148                 $meta     = get_post_meta( $event_id );
    149                 $events[] = Event::from_post_meta( $event_id, $meta );
    150             }
    151 
    152             $this->active_events_cache->cache( $events );
    153         }
    154 
    155         // Filter out events that aren't actually active at $at.
    156         return array_filter(
    157             $events,
    158             function ( $event ) use ( $at ) {
    159                 return $event->start() <= $at && $at <= $event->end();
    160             }
    161         );
    162     }
    163 
    164     /**
    165118     * Filter an array of events so that it only includes events the given user is attending.
    166119     *
     
    172125    // phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.Found
    173126    private function select_events_user_is_registered_for( array $events, int $user_id ): array {
    174         $attending_event_ids = get_user_meta( $user_id, Translation_Events::USER_META_KEY_ATTENDING, true );
     127        $attending_event_ids = $this->attendee_repository->get_events_for_user( $user_id );
    175128        return array_filter(
    176129            $events,
    177130            function ( Event $event ) use ( $attending_event_ids ) {
    178                 return isset( $attending_event_ids[ $event->id() ] );
     131                return in_array( $event->id(), $attending_event_ids, true );
    179132            }
    180133        );
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/templates/event.php

    r13298 r13529  
    66namespace Wporg\TranslationEvents;
    77
    8 use WP_Post;
    9 
    10 /** @var WP_Post $event */
     8use WP_User;
     9use Wporg\TranslationEvents\Attendee\Attendee;
     10use Wporg\TranslationEvents\Attendee\Attendee_Repository;
     11use Wporg\TranslationEvents\Event\Event;
     12use Wporg\TranslationEvents\Event\Event_End_Date;
     13use Wporg\TranslationEvents\Event\Event_Start_Date;
     14
     15/** @var Attendee_Repository $attendee_repo */
     16/** @var Attendee $attendee */
     17/** @var Event $event */
    1118/** @var int $event_id */
    1219/** @var string $event_title */
    1320/** @var string $event_description */
    14 /** @var string $event_start */
    15 /** @var string $event_end */
    16 /** @var bool $user_is_attending */
     21/** @var Event_Start_Date $event_start */
     22/** @var Event_End_Date $event_end */
    1723/** @var Event_Stats $event_stats */
     24/** @var array $projects */
     25/** @var WP_User $user */
    1826
    1927/* translators: %s: Event title. */
     
    2129gp_breadcrumb_translation_events( array( esc_html( $event_title ) ) );
    2230gp_tmpl_header();
     31$event_page_title = $event_title;
    2332gp_tmpl_load( 'events-header', get_defined_vars(), __DIR__ );
    2433?>
    2534
    2635<div class="event-page-wrapper">
    27     <div class="event-details-head">
    28         <h1>
    29             <?php echo esc_html( $event_title ); ?>
    30             <?php if ( 'draft' === $event->post_status ) : ?>
    31                 <span class="event-label-draft"><?php echo esc_html( $event->post_status ); ?></span>
    32             <?php endif; ?>
    33         </h1>
    34         <p>
    35             Host: <a href="<?php echo esc_attr( get_author_posts_url( $event->post_author ) ); ?>"><?php echo esc_html( get_the_author_meta( 'display_name', $event->post_author ) ); ?></a>
    36             <?php if ( current_user_can( 'edit_post', $event_id ) ) : ?>
    37                 <a class="event-page-edit-link button" href="<?php echo esc_url( gp_url( 'events/edit/' . $event_id ) ); ?>"><span class="dashicons dashicons-edit"></span>Edit event</a>
    38             <?php endif ?>
    39         </p>
    40     </div>
    4136    <div class="event-details-left">
    4237        <div class="event-page-content">
     
    4540            ?>
    4641        </div>
     42        <?php if ( ! empty( $contributors ) ) : ?>
     43            <div class="event-contributors">
     44                <h2><?php esc_html_e( 'Contributors', 'gp-translation-events' ); ?></h2>
     45                <ul>
     46                    <?php foreach ( $contributors as $contributor ) : ?>
     47                        <li class="event-contributor" title="<?php echo esc_html( implode( ', ', $contributor->locales ) ); ?>">
     48                            <a href="<?php echo esc_url( get_author_posts_url( $contributor->ID ) ); ?>"><?php echo get_avatar( $contributor->ID, 48 ); ?></a>
     49                            <a href="<?php echo esc_url( get_author_posts_url( $contributor->ID ) ); ?>"><?php echo esc_html( get_the_author_meta( 'display_name', $contributor->ID ) ); ?></a>
     50                            <?php if ( $stats_calculator->is_first_time_contributor( $event_start, $contributor->ID ) ) : ?>
     51                                <span class="first-time-contributor-tada"></span>
     52                            <?php endif; ?>
     53                            <?php
     54                            if ( ! $event->end()->is_in_the_past() ) :
     55                                if ( ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'manage_options' ) ) :
     56                                    if ( $user->ID !== $contributor->ID ) :
     57                                        $_attendee = $attendee_repo->get_attendee( $event_id, $contributor->ID );
     58                                        if ( $_attendee instanceof Attendee ) :
     59                                            echo '<form class="add-remove-user-as-host" method="post" action="' . esc_url( gp_url( "/events/host/$event_id/$contributor->ID" ) ) . '">';
     60                                            if ( $_attendee->is_host() ) :
     61                                                if ( 1 === count( $attendee_repo->get_hosts( $event_id ) ) ) :
     62                                                    echo '<input type="submit" class="button is-primary remove-as-host" disabled value="Remove as host"/>';
     63                                                else :
     64                                                    echo '<input type="submit" class="button is-primary remove-as-host" value="Remove as host"/>';
     65                                                endif;
     66                                            else :
     67                                                echo '<input type="submit" class="button is-secondary convert-to-host" value="Make co-host"/>';
     68                                            endif;
     69                                            echo '</form>';
     70                                        endif;
     71                                    elseif ( ( $attendee instanceof Attendee && $attendee->is_host() ) ) :
     72                                            echo '<span class="event-you">' . esc_html__( 'You (host)', 'gp-translation-events' ) . '</span>';
     73                                        else :
     74                                            echo '<span class="event-you">' . esc_html__( 'You (event creator)', 'gp-translation-events' ) . '</span>';
     75                                    endif;
     76                                endif;
     77                            endif;
     78                            ?>
     79                        </li>
     80                    <?php endforeach; ?>
     81                </ul>
     82            </div>
     83        <?php endif; ?>
     84        <?php if ( ! empty( $attendees ) && ( ! $event->end()->is_in_the_past() || ( ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'manage_options' ) ) ) ) : ?>
     85            <div class="event-attendees">
     86                <h2><?php esc_html_e( 'Attendees', 'gp-translation-events' ); ?></h2>
     87                <ul>
     88                    <?php foreach ( $attendees as $_user ) : ?>
     89                        <li class="event-attendee">
     90                            <a href="<?php echo esc_url( get_author_posts_url( $_user->ID ) ); ?>"><?php echo get_avatar( $_user->ID, 48 ); ?></a>
     91                            <a href="<?php echo esc_url( get_author_posts_url( $_user->ID ) ); ?>"><?php echo esc_html( get_the_author_meta( 'display_name', $_user->ID ) ); ?></a>
     92                            <?php if ( $stats_calculator->is_first_time_contributor( $event_start, $_user->ID ) ) : ?>
     93                                <span class="first-time-contributor-tada"></span>
     94                            <?php endif; ?>
     95                            <?php
     96                            if ( ! $event->end()->is_in_the_past() ) :
     97                                if ( ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'manage_options' ) ) :
     98                                    if ( $user->ID !== $_user->ID ) :
     99                                        $_attendee = $attendee_repo->get_attendee( $event_id, $_user->ID );
     100                                        if ( $_attendee instanceof Attendee ) :
     101                                            echo '<form class="add-remove-user-as-host" method="post" action="' . esc_url( gp_url( "/events/host/$event_id/$_user->ID" ) ) . '">';
     102                                            if ( $_attendee->is_host() ) :
     103                                                if ( 1 === count( $attendee_repo->get_hosts( $event_id ) ) ) :
     104                                                    echo '<input type="submit" class="button is-primary remove-as-host" disabled value="Remove as host"/>';
     105                                                else :
     106                                                    echo '<input type="submit" class="button is-primary remove-as-host" value="Remove as host"/>';
     107                                                endif;
     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                                    elseif ( ( $attendee instanceof Attendee && $attendee->is_host() ) ) :
     114                                            echo '<span class="event-you">' . esc_html__( 'You (host)', 'gp-translation-events' ) . '</span>';
     115                                        else :
     116                                            echo '<span class="event-you">' . esc_html__( 'You (event creator)', 'gp-translation-events' ) . '</span>';
     117                                    endif;
     118                                endif;
     119                            endif;
     120                            ?>
     121                        </li>
     122                    <?php endforeach; ?>
     123                </ul>
     124            </div>
     125        <?php endif; ?>
    47126        <?php if ( ! empty( $event_stats->rows() ) ) : ?>
    48     <div class="event-details-stats">
    49         <h2><?php esc_html_e( 'Stats', 'gp-translation-events' ); ?></h2>
    50         <table>
    51             <thead>
    52             <tr>
    53                 <th scope="col">Locale</th>
    54                 <th scope="col">Translations created</th>
    55                 <th scope="col">Translations reviewed</th>
    56                 <th scope="col">Contributors</th>
    57             </tr>
    58             </thead>
    59             <tbody>
    60             <?php /** @var $row Stats_Row */ ?>
    61             <?php foreach ( $event_stats->rows() as $_locale => $row ) : ?>
    62             <tr>
    63                 <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>
    64                 <td><?php echo esc_html( $row->created ); ?></td>
    65                 <td><?php echo esc_html( $row->reviewed ); ?></td>
    66                 <td><?php echo esc_html( $row->users ); ?></td>
    67             </tr>
    68         <?php endforeach ?>
    69             <tr class="event-details-stats-totals">
    70                 <td>Total</td>
    71                 <td><?php echo esc_html( $event_stats->totals()->created ); ?></td>
    72                 <td><?php echo esc_html( $event_stats->totals()->reviewed ); ?></td>
    73                 <td><?php echo esc_html( $event_stats->totals()->users ); ?></td>
    74             </tr>
    75             </tbody>
    76         </table>
    77     </div>
    78     <div class="event-contributors">
    79         <h2><?php esc_html_e( 'Contributors', 'gp-translation-events' ); ?></h2>
    80         <ul>
    81             <?php foreach ( $contributors as $contributor ) : ?>
    82             <li class="event-contributor" title="<?php echo esc_html( implode( ', ', $contributor->locales ) ); ?>"
    83                 <a href="<?php echo esc_url( get_author_posts_url( $contributor->ID ) ); ?>"><?php echo get_avatar( $contributor->ID, 48 ); ?></a>
    84                 <a href="<?php echo esc_url( get_author_posts_url( $contributor->ID ) ); ?>"><?php echo esc_html( get_the_author_meta( 'display_name', $contributor->ID ) ); ?></a>
    85             </li>
    86         <?php endforeach; ?>
    87         </ul>
    88     </div>
    89     <details class="event-stats-summary">
    90         <summary>View stats summary in text </summary>
    91         <p class="event-stats-text">
    92             <?php
    93             echo wp_kses(
    94                 sprintf(
    95                     // translators: %1$s: Event title, %2$d: Number of contributors, %3$d: Number of languages, %4$s: List of languages, %5$d: Number of strings translated, %6$d: Number of strings reviewed.
    96                     __( 'At the <strong>%1$s</strong> event, %2$d people contributed in %3$d languages (%4$s), translated %5$d strings and reviewed %6$d strings.', 'gp-translation-events' ),
    97                     esc_html( $event_title ),
    98                     esc_html( $event_stats->totals()->users ),
    99                     count( $event_stats->rows() ),
    100                     esc_html(
    101                         implode(
    102                             ', ',
    103                             array_map(
    104                                 function ( $row ) {
    105                                     return $row->language->english_name;
    106                                 },
    107                                 $event_stats->rows()
     127            <div class="event-details-stats">
     128                <h2><?php esc_html_e( 'Stats', 'gp-translation-events' ); ?></h2>
     129                <table>
     130                    <thead>
     131                    <tr>
     132                        <th scope="col">Locale</th>
     133                        <th scope="col">Translations created</th>
     134                        <th scope="col">Translations reviewed</th>
     135                        <th scope="col">Contributors</th>
     136                    </tr>
     137                    </thead>
     138                    <tbody>
     139                    <?php /** @var $row Stats_Row */ ?>
     140                    <?php foreach ( $event_stats->rows() as $_locale => $row ) : ?>
     141                    <tr>
     142                        <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>
     143                        <td><?php echo esc_html( $row->created ); ?></td>
     144                        <td><?php echo esc_html( $row->reviewed ); ?></td>
     145                        <td><?php echo esc_html( $row->users ); ?></td>
     146                    </tr>
     147                <?php endforeach ?>
     148                    <tr class="event-details-stats-totals">
     149                        <td>Total</td>
     150                        <td><?php echo esc_html( $event_stats->totals()->created ); ?></td>
     151                        <td><?php echo esc_html( $event_stats->totals()->reviewed ); ?></td>
     152                        <td><?php echo esc_html( $event_stats->totals()->users ); ?></td>
     153                    </tr>
     154                    </tbody>
     155                </table>
     156            </div>
     157            <div class="event-projects">
     158                <h2><?php esc_html_e( 'Projects', 'gp-translation-events' ); ?></h2>
     159                <ul>
     160                    <?php foreach ( $projects as $project_name => $row ) : ?>
     161                    <li class="event-project" title="<?php echo esc_html( str_replace( ',', ', ', $row->locales ) ); ?>">
     162                        <a href="<?php echo esc_url( gp_url_project( $row->project ) ); ?>"><?php echo esc_html( $project_name ); ?></a> <small> to
     163                        <?php
     164                        foreach ( explode( ',', $row->locales ) as $_locale ) {
     165                            $_locale = \GP_Locales::by_slug( $_locale );
     166                            ?>
     167                            <a href="<?php echo esc_url( gp_url_project_locale( $row->project, $_locale, 'default' ) ); ?>"><?php echo esc_html( $_locale->english_name ); ?></a>
     168                            <?php
     169                        }
     170                        // translators: %d: Number of contributors.
     171                        echo esc_html( sprintf( _n( 'by %d contributor', 'by %d contributors', $row->users, 'gp-translation-events' ), $row->users ) );
     172                        ?>
     173                        </small>
     174                    </li>
     175                <?php endforeach; ?>
     176                </ul>
     177            </div>
     178            <details class="event-stats-summary">
     179                <summary>View stats summary in text </summary>
     180                <p class="event-stats-text">
     181                    <?php
     182                    echo wp_kses(
     183                        sprintf(
     184                            // translators: %1$s: Event title, %2$d: Number of contributors, %3$d: Number of languages, %4$s: List of languages, %5$d: Number of strings translated, %6$d: Number of strings reviewed.
     185                            __( 'At the <strong>%1$s</strong> event, %2$d people contributed in %3$d languages (%4$s), translated %5$d strings and reviewed %6$d strings.', 'gp-translation-events' ),
     186                            esc_html( $event_title ),
     187                            esc_html( $event_stats->totals()->users ),
     188                            count( $event_stats->rows() ),
     189                            esc_html(
     190                                implode(
     191                                    ', ',
     192                                    array_map(
     193                                        function ( $row ) {
     194                                            return $row->language->english_name;
     195                                        },
     196                                        $event_stats->rows()
     197                                    )
     198                                )
     199                            ),
     200                            esc_html( $event_stats->totals()->created ),
     201                            esc_html( $event_stats->totals()->reviewed )
     202                        ),
     203                        array(
     204                            'strong' => array(),
     205                        )
     206                    );
     207                    ?>
     208                    <?php
     209                    echo wp_kses(
     210                        sprintf(
     211                        // translators: %s the contributors.
     212                            esc_html__( 'Contributors were %s.', 'gp-translation-events' ),
     213                            implode(
     214                                ', ',
     215                                array_map(
     216                                    function ( $contributor ) use ( $stats_calculator, $event_start ) {
     217                                        $append_tada = $stats_calculator->is_first_time_contributor( $event_start, $contributor->ID ) ? '<span class="first-time-contributor-tada"></span>' : '';
     218                                        return '@' . $contributor->user_login . $append_tada;
     219                                    },
     220                                    $contributors
     221                                )
    108222                            )
     223                        ),
     224                        array(
     225                            'span' => array(
     226                                'class' => array(),
     227                            ),
    109228                        )
    110                     ),
    111                     esc_html( $event_stats->totals()->created ),
    112                     esc_html( $event_stats->totals()->reviewed )
    113                 ),
    114                 array(
    115                     'strong' => array(),
    116                 )
    117             );
    118             ?>
    119             <?php
    120             echo esc_html(
    121                 sprintf(
    122                     // translators: %s the contributors.
    123                     __( 'Contributors were %s.', 'gp-translation-events' ),
    124                     esc_html(
    125                         implode(
    126                             ', ',
    127                             array_map(
    128                                 function ( $contributor ) {
    129                                     return '@' . $contributor->user_login;
    130                                 },
    131                                 $contributors
    132                             )
    133                         )
    134                     )
    135                 )
    136             );
    137             ?>
     229                    );
     230                    ?>
    138231            </p>
    139232    </details>
    140 
    141     <?php endif; ?>
     233        <?php endif; ?>
    142234    </div>
    143235    <div class="event-details-right">
    144236        <div class="event-details-date">
    145237            <p>
    146                 <span class="event-details-date-label">Starts:</span> <time class="event-utc-time" datetime="<?php echo esc_attr( $event_start ); ?>"></time>
    147                 <span class="event-details-date-label">Ends:</span><time class="event-utc-time" datetime="<?php echo esc_attr( $event_end ); ?>"></time>
     238                <span class="event-details-date-label">
     239                    <?php echo esc_html( $event_start->is_in_the_past() ? __( 'Started', 'gp-translation-events' ) : __( 'Starts', 'gp-translation-events' ) ); ?>:
     240                    <?php $event_start->print_relative_time_html(); ?>
     241                </span>
     242                <?php $event_start->print_time_html(); ?>
     243                <span class="event-details-date-label">
     244                    <?php echo esc_html( $event_end->is_in_the_past() ? __( 'Ended', 'gp-translation-events' ) : __( 'Ends', 'gp-translation-events' ) ); ?>:
     245                    <?php $event_end->print_relative_time_html(); ?>
     246
     247                </span>
     248                <?php $event_end->print_time_html(); ?>
    148249            </p>
    149250        </div>
    150251        <?php if ( is_user_logged_in() ) : ?>
    151252        <div class="event-details-join">
    152             <?php
    153             $current_time = gmdate( 'Y-m-d H:i:s' );
    154             if ( strtotime( $current_time ) > strtotime( $event_end ) ) :
    155                 ?>
    156                 <?php if ( $user_is_attending ) : ?>
    157                     <span class="event-details-join-expired"><?php esc_html_e( 'You attended', 'gp-translation-events' ); ?></span>
    158                 <?php endif ?>
     253            <?php if ( $event_end->is_in_the_past() ) : ?>
     254                <?php if ( $attendee instanceof Attendee ) : ?>
     255                    <button disabled="disabled" class="button is-primary attend-btn"><?php esc_html_e( 'You attended', 'gp-translation-events' ); ?></button>
     256                <?php endif; ?>
    159257            <?php else : ?>
    160258                <form class="event-details-attend" method="post" action="<?php echo esc_url( gp_url( "/events/attend/$event_id" ) ); ?>">
    161                     <?php if ( ! $user_is_attending ) : ?>
     259                    <?php if ( $attendee instanceof Attendee ) : ?>
     260                        <?php if ( $attendee->is_host() && ( 1 === count( $attendee_repo->get_hosts( $event_id ) ) ) ) : ?>
     261                            <input type="submit" class="button is-secondary attending-btn" disabled value="You're attending" />
     262                        <?php else : ?>
     263                            <input type="submit" class="button is-secondary attending-btn" value="You're attending" />
     264                        <?php endif; ?>
     265                    <?php else : ?>
    162266                        <input type="submit" class="button is-primary attend-btn" value="Attend Event"/>
    163                     <?php else : ?>
    164                         <input type="submit" class="button is-secondary attending-btn" value="You're attending"/>
    165                     <?php endif ?>
     267                    <?php endif; ?>
    166268                </form>
    167             <?php endif ?>
     269            <?php endif; ?>
    168270        </div>
    169271        <?php else : ?>
    170272        <div class="event-details-join">
    171273            <p>
    172                 <?php global $wp; ?>
    173                 <a href="<?php echo esc_url( wp_login_url( home_url( $wp->request ) ) ); ?>" class="button is-primary attend-btn"><?php esc_html_e( 'Login to attend', 'gp-translation-events' ); ?></a>
     274                <?php if ( ! $event_end->is_in_the_past() ) : ?>
     275                    <a href="<?php echo esc_url( wp_login_url() ); ?>" class="button is-primary attend-btn"><?php esc_html_e( 'Login to attend', 'gp-translation-events' ); ?></a>
     276                <?php else : ?>
     277                    <button disabled="disabled" class="button is-primary attend-btn"><?php esc_html_e( 'Event is over', 'gp-translation-events' ); ?></button>
     278                <?php endif; ?>
    174279            </p>
    175280        </div>
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/templates/events-form.php

    r13268 r13529  
    66namespace Wporg\TranslationEvents;
    77
    8 /** @var string $event_form_title */
     8use DateTimeZone;
     9use Wporg\TranslationEvents\Event\Event_End_Date;
     10use Wporg\TranslationEvents\Event\Event_Start_Date;
     11
     12/** @var string $event_page_title */
    913/** @var string $event_form_name */
    10 /** @var int $event_id */
     14/** @var int    $event_id */
    1115/** @var string $event_title */
    1216/** @var string $event_description */
    13 /** @var string $event_start */
    14 /** @var string $event_end */
    15 /** @var string $event_timezone */
     17/** @var Event_Start_Date $event_start */
     18/** @var Event_End_Date $event_end */
     19/** @var DateTimeZone|null $event_timezone */
    1620/** @var string $event_url */
    1721/** @var string $css_show_url */
    1822
    19 gp_title( __( 'Translation Events' ) . ' - ' . esc_html( $event_form_title . ' - ' . $event_title ) );
    20 gp_breadcrumb_translation_events( array( esc_html( $event_form_title ) ) );
     23gp_title( __( 'Translation Events' ) . ' - ' . esc_html( $event_page_title . ' - ' . $event_title ) );
     24gp_breadcrumb_translation_events( array( esc_html( $event_page_title ) ) );
    2125gp_tmpl_header();
    2226gp_tmpl_load( 'events-header', get_defined_vars(), __DIR__ );
    2327?>
    2428<div class="event-page-wrapper">
    25 <h2 class="event-page-title"><?php echo esc_html( $event_form_title ); ?></h2>
    2629<form class="translation-event-form" action="" method="post">
    2730    <?php wp_nonce_field( '_event_nonce', '_event_nonce' ); ?>
     
    4144        <label for="event-description">Event Description</label>
    4245        <textarea id="event-description" name="event_description" rows="4" required><?php echo esc_html( $event_description ); ?></textarea>
    43     </div>
    44     <div>
     46        <?php
     47        echo wp_kses(
     48            Event_Text_Snippet::get_snippet_links(),
     49            array(
     50                'a'  => array(
     51                    'href'         => array(),
     52                    'data-snippet' => array(),
     53                    'class'        => array(),
     54                ),
     55                'ul' => array( 'class' => array() ),
     56                'li' => array(),
     57            )
     58        );
     59        ?>
     60            <div>
    4561        <label for="event-start">Start Date</label>
    46         <input type="datetime-local" id="event-start" name="event_start" value="<?php echo esc_attr( $event_start ); ?>" required>
     62        <input type="datetime-local" id="event-start" name="event_start" value="<?php echo esc_attr( $event_start->format( 'Y-m-d H:i' ) ); ?>" required>
    4763    </div>
    4864    <div>
    4965        <label for="event-end">End Date</label>
    50         <input type="datetime-local" id="event-end" name="event_end" value="<?php echo esc_attr( $event_end ); ?>" required>
     66        <input type="datetime-local" id="event-end" name="event_end" value="<?php echo esc_attr( $event_end->format( 'Y-m-d H:i' ) ); ?>" required>
    5167    </div>
    5268    <div>
    5369        <label for="event-timezone">Event Timezone</label>
    54         <select id="event-timezone" name="event_timezone"  required>
     70        <select id="event-timezone" name="event_timezone" required>
    5571            <?php
    5672            echo wp_kses(
    57                 wp_timezone_choice( $event_timezone, get_user_locale() ),
     73                wp_timezone_choice( $event_timezone ? $event_timezone->getName() : null, get_user_locale() ),
    5874                array(
    5975                    'optgroup' => array( 'label' => array() ),
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/templates/events-header.php

    r13268 r13529  
    11<?php
     2
    23namespace Wporg\TranslationEvents;
    34
    45use GP;
     6use Wporg\TranslationEvents\Attendee\Attendee;
     7use Wporg\TranslationEvents\Event\Event;
     8
     9/** @var Attendee $attendee */
     10/** @var Event  $event */
     11/** @var string $event_page_title */
     12/** @var bool   $is_editable_event */
    513?>
    614
    715<div class="event-list-top-bar">
     16<h2 class="event-page-title">
     17    <?php echo esc_html( $event_page_title ); ?>
     18    <?php if ( isset( $event ) && 'draft' === $event->status() ) : ?>
     19                <span class="event-label-draft"><?php echo esc_html( $event->status() ); ?></span>
     20            <?php endif; ?>
     21</h2>
    822    <ul class="event-list-nav">
    923        <?php if ( is_user_logged_in() ) : ?>
     
    2236        <?php endif; ?>
    2337    </ul>
     38    <?php if ( isset( $event ) && ! isset( $event_form_name ) ) : ?>
     39    <p class="event-sub-head">
     40            <span class="event-host">
     41                <?php
     42                if ( 1 === count( $hosts ) ) :
     43                    esc_html_e( 'Host:', 'gp-translation-events' );
     44                else :
     45                    esc_html_e( 'Hosts:', 'gp-translation-events' );
     46                endif;
     47                ?>
     48                <?php foreach ( $hosts as $host ) : ?>
     49                    <?php $user = get_userdata( $host->user_id() ); ?>
     50                    &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>
     51                    <?php if ( end( $hosts ) !== $host ) : ?>
     52                        ,
     53                    <?php endif; ?>
     54                <?php endforeach; ?>
     55            .</span>
     56            <?php $show_edit_button = ( ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'edit_post', $event->id() ) ) && $is_editable_event; ?>
     57            <?php if ( $show_edit_button ) : ?>
     58                <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>
     59            <?php endif ?>
     60        </p>
     61        <?php endif; ?>
     62
    2463</div>
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/templates/events-list.php

    r13268 r13529  
    88use DateTime;
    99use WP_Query;
     10use Wporg\TranslationEvents\Event\Event;
     11use Wporg\TranslationEvents\Event\Events_Query_Result;
    1012
    11 /** @var WP_Query $current_events_query */
    12 /** @var WP_Query $upcoming_events_query */
    13 /** @var WP_Query $past_events_query */
     13/** @var Events_Query_Result $current_events_query */
     14/** @var Events_Query_Result $upcoming_events_query */
     15/** @var Events_Query_Result $past_events_query */
     16/** @var Events_Query_Result $user_attending_events_query */
    1417
    1518gp_title( __( 'Translation Events', 'gp-translation-events' ) );
    1619gp_breadcrumb_translation_events();
    1720gp_tmpl_header();
     21$event_page_title = __( 'Translation Events', 'gp-translation-events' );
    1822gp_tmpl_load( 'events-header', get_defined_vars(), __DIR__ );
    1923?>
    2024
    2125<div class="event-page-wrapper">
    22     <h1 class="event_page_title"><?php esc_html_e( 'Translation Events', 'gp-translation-events' ); ?></h1>
    2326<div class="event-left-col">
    2427<?php
    25 if ( $current_events_query->have_posts() ) :
     28if ( ! empty( $current_events_query->events ) ) :
    2629    ?>
    2730    <h2><?php esc_html_e( 'Current events', 'gp-translation-events' ); ?></h2>
    2831    <ul class="event-list">
    2932        <?php
    30         while ( $current_events_query->have_posts() ) :
    31             $current_events_query->the_post();
    32             $event_end = Event::get_end_date_text( get_post_meta( get_the_ID(), '_event_end', true ) );
    33             $event_url = gp_url( wp_make_link_relative( get_the_permalink() ) );
     33        foreach ( $current_events_query->events as $event ) :
     34            $event_url = gp_url( wp_make_link_relative( get_the_permalink( $event->id() ) ) );
    3435            ?>
    3536            <li class="event-list-item">
    36                 <a href="<?php echo esc_url( $event_url ); ?>"><?php the_title(); ?></a>
    37                 <span class="event-list-date"><?php echo esc_html( $event_end ); ?></span>
    38                 <?php the_excerpt(); ?>
     37                <a href="<?php echo esc_url( $event_url ); ?>"><?php echo esc_html( $event->title() ); ?></a>
     38                <span class="event-list-date">ends <?php $event->end()->print_relative_time_html(); ?></time></span>
     39                <?php echo esc_html( get_the_excerpt( $event->id() ) ); ?>
    3940            </li>
    4041            <?php
    41         endwhile;
     42        endforeach;
    4243        ?>
    4344    </ul>
     
    4748        paginate_links(
    4849            array(
    49                 'total'     => $current_events_query->max_num_pages,
    50                 'current'   => max( 1, $current_events_query->query_vars['paged'] ),
     50                'total'     => $current_events_query->page_count,
     51                'current'   => $current_events_query->current_page,
    5152                'format'    => '?current_events_paged=%#%',
    5253                'prev_text' => '&laquo; Previous',
     
    5859    wp_reset_postdata();
    5960endif;
    60 if ( $upcoming_events_query->have_posts() ) :
     61
     62if ( ! empty( $upcoming_events_query->events ) ) :
    6163    ?>
    6264    <h2><?php esc_html_e( 'Upcoming events', 'gp-translation-events' ); ?></h2>
    6365    <ul class="event-list">
    6466        <?php
    65         while ( $upcoming_events_query->have_posts() ) :
    66             $upcoming_events_query->the_post();
    67             $event_start = ( new DateTime( get_post_meta( get_the_ID(), '_event_start', true ) ) )->format( 'l, F j, Y' );
     67        foreach ( $upcoming_events_query->events as $event ) :
     68            $event_url = gp_url( wp_make_link_relative( get_the_permalink( $event->id() ) ) );
    6869            ?>
    6970            <li class="event-list-item">
    70                 <a href="<?php echo esc_url( gp_url( wp_make_link_relative( get_the_permalink() ) ) ); ?>"><?php the_title(); ?></a>
    71                 <span class="event-list-date"><?php echo esc_html( $event_start ); ?></span>
    72                 <?php the_excerpt(); ?>
     71                <a href="<?php echo esc_url( $event_url ); ?>"><?php echo esc_html( $event->title() ); ?></a>
     72                <span class="event-list-date">starts <?php $event->start()->print_relative_time_html(); ?></span>
     73                <?php echo esc_html( get_the_excerpt( $event->id() ) ); ?>
    7374            </li>
    7475            <?php
    75         endwhile;
     76        endforeach;
    7677        ?>
    7778    </ul>
     
    8182        paginate_links(
    8283            array(
    83                 'total'     => $upcoming_events_query->max_num_pages,
    84                 'current'   => max( 1, $upcoming_events_query->query_vars['paged'] ),
     84                'total'     => $upcoming_events_query->page_count,
     85                'current'   => $upcoming_events_query->current_page,
    8586                'format'    => '?upcoming_events_paged=%#%',
    8687                'prev_text' => '&laquo; Previous',
     
    9293    wp_reset_postdata();
    9394endif;
    94 if ( $past_events_query->have_posts() ) :
     95if ( ! empty( $past_events_query->events ) ) :
    9596    ?>
    9697    <h2><?php esc_html_e( 'Past events', 'gp-translation-events' ); ?></h2>
    9798    <ul class="event-list">
    9899        <?php
    99         while ( $past_events_query->have_posts() ) :
    100             $past_events_query->the_post();
    101             $event_start = ( new DateTime( get_post_meta( get_the_ID(), '_event_start', true ) ) )->format( 'M j, Y' );
    102             $event_end   = ( new DateTime( get_post_meta( get_the_ID(), '_event_end', true ) ) )->format( 'M j, Y' );
     100        foreach ( $past_events_query->events as $event ) :
     101            $event_url = gp_url( wp_make_link_relative( get_the_permalink( $event->id() ) ) );
    103102            ?>
    104103            <li class="event-list-item">
    105                 <a href="<?php echo esc_url( gp_url( wp_make_link_relative( get_the_permalink() ) ) ); ?>"><?php the_title(); ?></a>
    106                 <?php if ( $event_start === $event_end ) : ?>
    107                     <span class="event-list-date"><?php echo esc_html( $event_start ); ?></span>
    108                 <?php else : ?>
    109                     <span class="event-list-date"><?php echo esc_html( $event_start ); ?> - <?php echo esc_html( $event_end ); ?></span>
    110                 <?php endif; ?>
    111                 <?php the_excerpt(); ?>
     104                <a href="<?php echo esc_url( $event_url ); ?>"><?php echo esc_html( $event->title() ); ?></a>
     105                <span class="event-list-date">ended <?php $event->end()->print_relative_time_html( 'F j, Y H:i T' ); ?></span>
     106                <?php esc_html( get_the_excerpt( $event->id() ) ); ?>
    112107            </li>
    113108            <?php
    114         endwhile;
     109        endforeach;
    115110        ?>
    116111    </ul>
     
    120115        paginate_links(
    121116            array(
    122                 'total'     => $past_events_query->max_num_pages,
    123                 'current'   => max( 1, $past_events_query->query_vars['paged'] ),
     117                'total'     => $past_events_query->page_count,
     118                'current'   => $past_events_query->current_page,
    124119                'format'    => '?past_events_paged=%#%',
    125120                'prev_text' => '&laquo; Previous',
     
    132127endif;
    133128
    134 if ( 0 === $current_events_query->post_count && 0 === $upcoming_events_query->post_count && 0 === $past_events_query->post_count ) :
     129if ( empty( $current_events_query->events ) && empty( $upcoming_events_query->events ) && empty( $past_events_query->post_count ) ) :
    135130    esc_html_e( 'No events found.', 'gp-translation-events' );
    136131endif;
     
    139134<?php if ( is_user_logged_in() ) : ?>
    140135    <div class="event-right-col">
    141         <h3 class="">Events I'm Attending</h3>
    142         <?php if ( ! $user_attending_events_query->have_posts() ) : ?>
     136        <h2>Events I'm Attending</h2>
     137        <?php if ( empty( $user_attending_events_query->events ) ) : ?>
    143138            <p>You don't have any events to attend.</p>
    144139        <?php else : ?>
    145140            <ul class="event-attending-list">
    146141                <?php
    147                 while ( $user_attending_events_query->have_posts() ) :
    148                     $user_attending_events_query->the_post();
    149                     $event_start = ( new DateTime( get_post_meta( get_the_ID(), '_event_start', true ) ) )->format( 'M j, Y' );
    150                     $event_end   = ( new DateTime( get_post_meta( get_the_ID(), '_event_end', true ) ) )->format( 'M j, Y' );
     142                foreach ( $user_attending_events_query->events as $event ) :
     143                    $event_url = gp_url( wp_make_link_relative( get_the_permalink( $event->id() ) ) );
    151144                    ?>
    152145                    <li class="event-list-item">
    153                         <a href="<?php echo esc_url( gp_url( wp_make_link_relative( get_the_permalink() ) ) ); ?>"><?php the_title(); ?></a>
    154                         <?php if ( $event_start === $event_end ) : ?>
    155                             <span class="event-list-date events-i-am-attending"><?php echo esc_html( $event_start ); ?></span>
     146                        <a href="<?php echo esc_url( $event_url ); ?>"><?php echo esc_html( $event->title() ); ?></a>
     147                        <?php if ( $event->start() === $event->end() ) : ?>
     148                            <span class="event-list-date events-i-am-attending"><?php $event->start()->print_time_html( 'F j, Y H:i T' ); ?></span>
    156149                        <?php else : ?>
    157                             <span class="event-list-date events-i-am-attending"><?php echo esc_html( $event_start ); ?> - <?php echo esc_html( $event_end ); ?></span>
     150                            <span class="event-list-date events-i-am-attending"><?php $event->start()->print_time_html( 'F j, Y H:i T' ); ?> - <?php $event->end()->print_time_html( 'F j, Y H:i T' ); ?></span>
    158151                        <?php endif; ?>
    159152                    </li>
    160153                    <?php
    161                 endwhile;
     154                endforeach;
    162155                ?>
    163156            </ul>
     
    166159                    paginate_links(
    167160                        array(
    168                             'total'     => $user_attending_events_query->max_num_pages,
    169                             'current'   => max( 1, $user_attending_events_query->query_vars['paged'] ),
     161                            'total'     => $user_attending_events_query->page_count,
     162                            'current'   => $user_attending_events_query->current_page,
    170163                            'format'    => '?user_attending_events_paged=%#%',
    171164                            'prev_text' => '&laquo; Previous',
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/templates/events-my-events.php

    r13268 r13529  
    66namespace Wporg\TranslationEvents;
    77
    8 use DateTime;
    9 use WP_Query;
     8use Wporg\TranslationEvents\Event\Events_Query_Result;
    109
    11 /** @var WP_Query $events_i_created_query */
    12 /** @var WP_Query $events_i_attended_query */
     10/** @var Events_Query_Result $events_i_created_query */
     11/** @var Events_Query_Result $events_i_host_query */
     12/** @var Events_Query_Result $events_i_attended_query */
    1313
    1414gp_title( esc_html__( 'Translation Events', 'gp-translation-events' ) . ' - ' . esc_html__( 'My Events', 'gp-translation-events' ) );
    1515gp_breadcrumb_translation_events( array( esc_html__( 'My Events', 'gp-translation-events' ) ) );
    1616gp_tmpl_header();
     17$event_page_title = __( 'My Events', 'gp-translation-events' );
    1718gp_tmpl_load( 'events-header', get_defined_vars(), __DIR__ );
    1819?>
    1920
    2021<div class="event-page-wrapper">
    21     <h1 class="event_page_title"><?php esc_html_e( 'My Events', 'gp-translation-events' ); ?> </h1>
    22     <h2 class="event_page_title"><?php esc_html_e( 'Events I have created', 'gp-translation-events' ); ?> </h2>
    23     <?php if ( $events_i_created_query->have_posts() ) : ?>
     22    <?php if ( ! empty( $events_i_host_query->events ) ) : ?>
     23        <h2><?php esc_html_e( 'Events I host', 'gp-translation-events' ); ?> </h2>
    2424        <ul>
    2525        <?php
    26         while ( $events_i_created_query->have_posts() ) :
    27             $events_i_created_query->the_post();
    28             $event_id                      = get_the_ID();
    29             $event_start                   = get_post_meta( $event_id, '_event_start', true );
    30             list( $permalink, $post_name ) = get_sample_permalink( $event_id );
     26        foreach ( $events_i_host_query->events as $event ) :
     27            list( $permalink, $post_name ) = get_sample_permalink( $event->id() );
    3128            $permalink                     = str_replace( '%pagename%', $post_name, $permalink );
    3229            $event_url                     = gp_url( wp_make_link_relative( $permalink ) );
    33             $event_edit_url                = gp_url( 'events/edit/' . $event_id );
    34             $event_status                  = get_post_status( $event_id );
    35             $event_start                   = ( new DateTime( get_post_meta( get_the_ID(), '_event_start', true ) ) )->format( 'M j, Y' );
    36             $event_end                     = ( new DateTime( get_post_meta( get_the_ID(), '_event_end', true ) ) )->format( 'M j, Y' );
     30            $event_edit_url                = gp_url( 'events/edit/' . $event->id() );
     31            $stats_calculator              = new Stats_Calculator();
     32            $has_stats                     = $stats_calculator->event_has_stats( $event->id() );
    3733            ?>
    3834            <li class="event-list-item">
    39                 <a class="event-link-<?php echo esc_attr( $event_status ); ?>" href="<?php echo esc_url( $event_url ); ?>"><?php the_title(); ?></a>
    40                 <a href="<?php echo esc_url( $event_edit_url ); ?>" class="button is-small action edit">Edit</a>
    41                 <?php if ( 'draft' === $event_status ) : ?>
    42                     <span class="event-label-<?php echo esc_attr( $event_status ); ?>"><?php echo esc_html( $event_status ); ?></span>
     35                <a class="event-link-<?php echo esc_attr( $event->status() ); ?>" href="<?php echo esc_url( $event_url ); ?>"><?php echo esc_html( $event->title() ); ?></a>
     36                <?php if ( ! $event->end()->is_in_the_past() && ! $has_stats ) : ?>
     37                    <a href="<?php echo esc_url( $event_edit_url ); ?>" class="button is-small action edit">Edit</a>
    4338                <?php endif; ?>
    44                 <?php if ( $event_start === $event_end ) : ?>
    45                     <span class="event-list-date events-i-am-attending"><?php echo esc_html( $event_start ); ?></span>
     39                <?php if ( 'draft' === $event->status() ) : ?>
     40                    <span class="event-label-<?php echo esc_attr( $event->status() ); ?>"><?php echo esc_html( $event->status() ); ?></span>
     41                <?php endif; ?>
     42                <?php if ( $event->start()->format( 'Y-m-d' ) === $event->end()->format( 'Y-m-d' ) ) : ?>
     43                    <span class="event-list-date events-i-am-attending"><?php $event->start()->print_time_html(); ?></span>
    4644                <?php else : ?>
    47                     <span class="event-list-date events-i-am-attending"><?php echo esc_html( $event_start ); ?> - <?php echo esc_html( $event_end ); ?></span>
     45                    <span class="event-list-date events-i-am-attending"><?php $event->start()->print_time_html(); ?> - <?php $event->end()->print_time_html(); ?></span>
    4846                <?php endif; ?>
    49                 <p><?php the_excerpt(); ?></p>
     47                <p><?php echo esc_html( get_the_excerpt( $event->id() ) ); ?></p>
    5048            </li>
    51         <?php endwhile; ?>
     49        <?php endforeach; ?>
    5250        </ul>
    5351
     
    5654            paginate_links(
    5755                array(
    58                     'total'     => $events_i_created_query->max_num_pages,
    59                     'current'   => max( 1, $events_i_created_query->query_vars['events_i_created_paged'] ),
     56                    'total'     => $events_i_host_query->page_count,
     57                    'current'   => $events_i_host_query->current_page,
     58                    'format'    => '?events_i_hosted_paged=%#%',
     59                    'prev_text' => '&laquo; Previous',
     60                    'next_text' => 'Next &raquo;',
     61                )
     62            ) ?? ''
     63        );
     64
     65        wp_reset_postdata();
     66    endif;
     67    ?>
     68
     69    <?php if ( ! empty( $events_i_created_query->events ) ) : ?>
     70        <h2><?php esc_html_e( 'Events I have created', 'gp-translation-events' ); ?> </h2>
     71        <ul>
     72            <?php
     73            foreach ( $events_i_created_query->events as $event ) :
     74                list( $permalink, $post_name ) = get_sample_permalink( $event->id() );
     75                $permalink                     = str_replace( '%pagename%', $post_name, $permalink );
     76                $event_url                     = gp_url( wp_make_link_relative( $permalink ) );
     77                $event_edit_url                = gp_url( 'events/edit/' . $event->id() );
     78                $stats_calculator              = new Stats_Calculator();
     79                $has_stats                     = $stats_calculator->event_has_stats( $event->id() );
     80                ?>
     81                <li class="event-list-item">
     82                    <a class="event-link-<?php echo esc_attr( $event->status() ); ?>" href="<?php echo esc_url( $event_url ); ?>"><?php echo esc_html( $event->title() ); ?></a>
     83                    <?php if ( ! $event->end()->is_in_the_past() && ! $has_stats ) : ?>
     84                        <a href="<?php echo esc_url( $event_edit_url ); ?>" class="button is-small action edit">Edit</a>
     85                    <?php endif; ?>
     86                    <?php if ( 'draft' === $event->status() ) : ?>
     87                        <span class="event-label-<?php echo esc_attr( $event->status() ); ?>"><?php echo esc_html( $event->status() ); ?></span>
     88                    <?php endif; ?>
     89                    <?php if ( $event->start()->format( 'Y-m-d' ) === $event->end()->format( 'Y-m-d' ) ) : ?>
     90                        <span class="event-list-date events-i-am-attending"><?php $event->start()->print_time_html(); ?></span>
     91                    <?php else : ?>
     92                        <span class="event-list-date events-i-am-attending"><?php $event->start()->print_time_html(); ?> - <?php $event->end()->print_time_html(); ?></span>
     93                    <?php endif; ?>
     94                    <p><?php echo esc_html( get_the_excerpt( $event->id() ) ); ?></p>
     95                </li>
     96            <?php endforeach; ?>
     97        </ul>
     98
     99        <?php
     100        echo wp_kses_post(
     101            paginate_links(
     102                array(
     103                    'total'     => $events_i_created_query->page_count,
     104                    'current'   => $events_i_created_query->current_page,
    60105                    'format'    => '?events_i_created_paged=%#%',
    61106                    'prev_text' => '&laquo; Previous',
     
    66111
    67112        wp_reset_postdata();
    68     else :
    69         echo 'No events found.';
    70113    endif;
    71114    ?>
    72115
    73     <h2 class="event_page_title"><?php esc_html_e( 'Events I attended', 'gp-translation-events' ); ?> </h2>
    74     <?php if ( $events_i_attended_query->have_posts() ) : ?>
     116    <h2><?php esc_html_e( 'Events I attended', 'gp-translation-events' ); ?> </h2>
     117    <?php if ( ! empty( $events_i_attended_query->events ) ) : ?>
    75118        <ul>
    76119        <?php
    77         while ( $events_i_attended_query->have_posts() ) :
    78             $events_i_attended_query->the_post();
    79             $event_id                      = get_the_ID();
    80             $event_start                   = get_post_meta( $event_id, '_event_start', true );
    81             list( $permalink, $post_name ) = get_sample_permalink( $event_id );
     120        foreach ( $events_i_attended_query->events as $event ) :
     121            list( $permalink, $post_name ) = get_sample_permalink( $event->id() );
    82122            $permalink                     = str_replace( '%pagename%', $post_name, $permalink );
    83123            $event_url                     = gp_url( wp_make_link_relative( $permalink ) );
    84             $event_edit_url                = gp_url( 'events/edit/' . $event_id );
    85             $event_status                  = get_post_status( $event_id );
    86             $event_start                   = ( new DateTime( get_post_meta( get_the_ID(), '_event_start', true ) ) )->format( 'M j, Y' );
    87             $event_end                     = ( new DateTime( get_post_meta( get_the_ID(), '_event_end', true ) ) )->format( 'M j, Y' );
    88124            ?>
    89125            <li class="event-list-item">
    90                 <a class="event-link-<?php echo esc_attr( $event_status ); ?>" href="<?php echo esc_url( $event_url ); ?>"><?php the_title(); ?></a>
    91                 <?php if ( $event_start === $event_end ) : ?>
    92                     <span class="event-list-date events-i-am-attending"><?php echo esc_html( $event_start ); ?></span>
     126                <a class="event-link-<?php echo esc_attr( $event->status() ); ?>" href="<?php echo esc_url( $event_url ); ?>"><?php echo esc_html( $event->title() ); ?></a>
     127                <?php if ( $event->start() === $event->end() ) : ?>
     128                    <span class="event-list-date events-i-am-attending"><?php $event->start()->print_time_html(); ?></span>
    93129                <?php else : ?>
    94                     <span class="event-list-date events-i-am-attending"><?php echo esc_html( $event_start ); ?> - <?php echo esc_html( $event_end ); ?></span>
     130                    <span class="event-list-date events-i-am-attending"><?php $event->start()->print_time_html(); ?> - <?php $event->end()->print_time_html(); ?></span>
    95131                <?php endif; ?>
    96                 <p><?php the_excerpt(); ?></p>
     132                <p><?php echo esc_html( get_the_excerpt( $event->id() ) ); ?></p>
    97133            </li>
    98         <?php endwhile; ?>
     134        <?php endforeach; ?>
    99135        </ul>
    100136
     
    103139            paginate_links(
    104140                array(
    105                     'total'     => $events_i_attended_query->max_num_pages,
    106                     'current'   => max( 1, $events_i_attended_query->query_vars['events_i_attended_paged'] ),
     141                    'total'     => $events_i_attended_query->page_count,
     142                    'current'   => $events_i_attended_query->current_page,
    107143                    'format'    => '?events_i_attended_paged=%#%',
    108144                    'prev_text' => '&laquo; Previous',
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/wporg-gp-translation-events.php

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