<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[13298] sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events: Translate Events: add languages and contributors</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="http://meta.trac.wordpress.org/changeset/13298">13298</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"http://meta.trac.wordpress.org/changeset/13298","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>akirk</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2024-03-07 06:39:00 +0000 (Thu, 07 Mar 2024)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Translate Events: add languages and contributors</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsassetscsstranslationeventscss">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/assets/css/translation-events.css</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesstatscalculatorphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/stats-calculator.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesstatslistenerphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/stats-listener.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventstemplateseventphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/templates/event.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventswporggptranslationeventsphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/wporg-gp-translation-events.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li>sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/</li>
<li>sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/</li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesrouteseventcreatephp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/create.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesrouteseventdetailsphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/details.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesrouteseventeditphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/edit.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesrouteseventlistphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/list.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesroutesroutephp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/route.php</a></li>
<li>sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/user/</li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesroutesuserattendeventphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/user/attend-event.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesroutesusermyeventsphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/user/my-events.php</a></li>
</ul>

<h3>Removed Paths</h3>
<ul>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesroutephp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/route.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsassetscsstranslationeventscss"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/assets/css/translation-events.css</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/assets/css/translation-events.css    2024-03-07 04:39:31 UTC (rev 13297)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/assets/css/translation-events.css      2024-03-07 06:39:00 UTC (rev 13298)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -49,10 +49,13 @@
</span><span class="cx" style="display: block; padding: 0 10px">        width: 100%;
</span><span class="cx" style="display: block; padding: 0 10px">        table-layout: fixed;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-.event-details-stats table th, .event-details-stats table td{
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+.event-details-stats table th, .event-details-stats table td {
</ins><span class="cx" style="display: block; padding: 0 10px">         padding: 1em;
</span><span class="cx" style="display: block; padding: 0 10px">        text-align: center;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+.event-details-stats table td:first-child {
+       text-align: left;
+}
</ins><span class="cx" style="display: block; padding: 0 10px"> .event-details-stats table tr {
</span><span class="cx" style="display: block; padding: 0 10px">        border-bottom: thin solid #f0f0f0;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -59,9 +62,20 @@
</span><span class="cx" style="display: block; padding: 0 10px"> .event-details-stats table th {
</span><span class="cx" style="display: block; padding: 0 10px">        background-color: #e9e9e9;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-.event-details-stats-totals {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+.event-details-stats-totals td {
</ins><span class="cx" style="display: block; padding: 0 10px">         font-weight: bold;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+.event-contributors li {
+       display: inline-block;
+       list-style-type: none;
+       margin-right: 1em;
+       margin-bottom: .5em;
+       width: 15em;
+}
+.event-contributors li .avatar {
+       border-radius: 50%;
+       vertical-align: middle;
+}
</ins><span class="cx" style="display: block; padding: 0 10px"> .hide-event-url {
</span><span class="cx" style="display: block; padding: 0 10px">        display: none;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -217,7 +231,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">        padding: 0.8em;
</span><span class="cx" style="display: block; padding: 0 10px">        font-size: .9em;
</span><span class="cx" style="display: block; padding: 0 10px">        border-top: thin solid #e0e0e0;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        font-family: monospace;
</del><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> time.event-utc-time {
</span><span class="cx" style="display: block; padding: 0 10px">        display: block;
</span></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesroutephp"></a>
<div class="delfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Deleted: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/route.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/route.php   2024-03-07 04:39:31 UTC (rev 13297)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/route.php     2024-03-07 06:39:00 UTC (rev 13298)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1,404 +0,0 @@
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-<?php
-
-namespace Wporg\TranslationEvents;
-
-use DateTime;
-use DateTimeZone;
-use Exception;
-use GP_Route;
-use WP_Query;
-use GP;
-
-class Route extends GP_Route {
-       public const USER_META_KEY_ATTENDING = 'translation-events-attending';
-
-       public function __construct() {
-               parent::__construct();
-               $this->template_path = __DIR__ . '/../templates/';
-       }
-
-       /**
-        * Loads the 'events_list' template.
-        *
-        * @return void
-        */
-       public function events_list() {
-               $current_datetime_utc = null;
-               try {
-                       $current_datetime_utc = ( new DateTime( 'now', new DateTimeZone( 'UTC' ) ) )->format( 'Y-m-d H:i:s' );
-               } catch ( Exception $e ) {
-                       // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
-                       error_log( $e );
-                       $this->die_with_error( esc_html__( 'Something is wrong.', 'gp-translation-events' ) );
-               }
-
-               $_current_events_paged        = 1;
-               $_upcoming_events_paged       = 1;
-               $_past_events_paged           = 1;
-               $_user_attending_events_paged = 1;
-
-               // phpcs:disable WordPress.Security.NonceVerification.Recommended
-               if ( isset( $_GET['current_events_paged'] ) ) {
-                       $value = sanitize_text_field( wp_unslash( $_GET['current_events_paged'] ) );
-                       if ( is_numeric( $value ) ) {
-                               $_current_events_paged = (int) $value;
-                       }
-               }
-               if ( isset( $_GET['upcoming_events_paged'] ) ) {
-                       $value = sanitize_text_field( wp_unslash( $_GET['upcoming_events_paged'] ) );
-                       if ( is_numeric( $value ) ) {
-                               $_upcoming_events_paged = (int) $value;
-                       }
-               }
-               if ( isset( $_GET['past_events_paged'] ) ) {
-                       $value = sanitize_text_field( wp_unslash( $_GET['past_events_paged'] ) );
-                       if ( is_numeric( $value ) ) {
-                               $_past_events_paged = (int) $value;
-                       }
-               }
-               if ( isset( $_GET['user_attending_events_paged'] ) ) {
-                       $value = sanitize_text_field( wp_unslash( $_GET['user_attending_events_paged'] ) );
-                       if ( is_numeric( $value ) ) {
-                               $_user_attending_events_paged = (int) $value;
-                       }
-               }
-               // phpcs:enable
-
-               $current_events_args  = array(
-                       'post_type'      => Translation_Events::CPT,
-                       'posts_per_page' => 10,
-                       'paged'          => $_current_events_paged,
-                       'post_status'    => 'publish',
-                       // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
-                       'meta_query'     => array(
-                               array(
-                                       'key'     => '_event_start',
-                                       'value'   => $current_datetime_utc,
-                                       'compare' => '<=',
-                                       'type'    => 'DATETIME',
-                               ),
-                               array(
-                                       'key'     => '_event_end',
-                                       'value'   => $current_datetime_utc,
-                                       'compare' => '>=',
-                                       'type'    => 'DATETIME',
-                               ),
-                       ),
-                       'orderby'        => 'meta_value',
-                       'order'          => 'ASC',
-               );
-               $current_events_query = new WP_Query( $current_events_args );
-
-               $upcoming_events_args  = array(
-                       'post_type'      => Translation_Events::CPT,
-                       'posts_per_page' => 10,
-                       'paged'          => $_upcoming_events_paged,
-                       'post_status'    => 'publish',
-                       // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
-                       'meta_query'     => array(
-                               array(
-                                       'key'     => '_event_start',
-                                       'value'   => $current_datetime_utc,
-                                       'compare' => '>=',
-                                       'type'    => 'DATETIME',
-                               ),
-                       ),
-                       'orderby'        => 'meta_value',
-                       'order'          => 'ASC',
-               );
-               $upcoming_events_query = new WP_Query( $upcoming_events_args );
-
-               $past_events_args  = array(
-                       'post_type'      => Translation_Events::CPT,
-                       'posts_per_page' => 10,
-                       'paged'          => $_past_events_paged,
-                       'post_status'    => 'publish',
-                       // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
-                       'meta_query'     => array(
-                               array(
-                                       'key'     => '_event_end',
-                                       'value'   => $current_datetime_utc,
-                                       'compare' => '<',
-                                       'type'    => 'DATETIME',
-                               ),
-                       ),
-                       'orderby'        => 'meta_value',
-                       'order'          => 'ASC',
-               );
-               $past_events_query = new WP_Query( $past_events_args );
-
-               $user_attending_events      = get_user_meta( get_current_user_id(), self::USER_META_KEY_ATTENDING, true ) ?: array( 0 );
-               $user_attending_events_args = array(
-                       'post_type'      => Translation_Events::CPT,
-                       'post__in'       => array_keys( $user_attending_events ),
-                       'posts_per_page' => 10,
-                       'paged'          => $_user_attending_events_paged,
-                       'post_status'    => 'publish',
-                       // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
-                       'meta_query'     => array(
-                               array(
-                                       'key'     => '_event_end',
-                                       'value'   => $current_datetime_utc,
-                                       'compare' => '>',
-                                       'type'    => 'DATETIME',
-                               ),
-                       ),
-                       // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
-                       'meta_key'       => '_event_start',
-                       'orderby'        => 'meta_value',
-                       'order'          => 'ASC',
-               );
-               $user_attending_events_query = new WP_Query( $user_attending_events_args );
-
-               $this->tmpl( 'events-list', get_defined_vars() );
-       }
-
-       /**
-        * Loads the 'events_create' template.
-        *
-        * @return void
-        */
-       public function events_create() {
-               global $wp;
-               if ( ! is_user_logged_in() ) {
-                       wp_safe_redirect( wp_login_url( home_url( $wp->request ) ) );
-                       exit;
-               }
-               $event_form_title         = 'Create Event';
-               $event_form_name          = 'create_event';
-               $css_show_url             = 'hide-event-url';
-               $event_id                 = null;
-               $event_title              = '';
-               $event_description        = '';
-               $event_timezone           = '';
-               $event_start              = '';
-               $event_end                = '';
-               $event_url                = '';
-               $create_delete_button     = true;
-               $visibility_delete_button = 'none';
-
-               $this->tmpl( 'events-form', get_defined_vars() );
-       }
-
-       /**
-        * Loads the 'events_edit' template.
-        *
-        * @param int $event_id The event ID.
-        *
-        * @return void
-        */
-       public function events_edit( int $event_id ) {
-               global $wp;
-               if ( ! is_user_logged_in() ) {
-                       wp_safe_redirect( wp_login_url( home_url( $wp->request ) ) );
-                       exit;
-               }
-               $event = get_post( $event_id );
-               if ( ! $event || Translation_Events::CPT !== $event->post_type || ! ( current_user_can( 'edit_post', $event->ID ) || intval( $event->post_author ) === get_current_user_id() ) ) {
-                       $this->die_with_error( esc_html__( 'Event does not exist, or you do not have permission to edit it.', 'gp-translation-events' ), 403 );
-               }
-               if ( 'trash' === $event->post_status ) {
-                       $this->die_with_error( esc_html__( 'You cannot edit a trashed event', 'gp-translation-events' ), 403 );
-               }
-
-               include ABSPATH . 'wp-admin/includes/post.php';
-               $event_form_title              = 'Edit Event';
-               $event_form_name               = 'edit_event';
-               $css_show_url                  = '';
-               $event_title                   = $event->post_title;
-               $event_description             = $event->post_content;
-               $event_status                  = $event->post_status;
-               list( $permalink, $post_name ) = get_sample_permalink( $event_id );
-               $permalink                     = str_replace( '%pagename%', $post_name, $permalink );
-               $event_url                     = get_site_url() . gp_url( wp_make_link_relative( $permalink ) );
-               $event_timezone                = get_post_meta( $event_id, '_event_timezone', true ) ?: '';
-               $create_delete_button          = false;
-               $visibility_delete_button      = 'inline-flex';
-
-               $stats_calculator = new Stats_Calculator();
-               if ( ! $stats_calculator->event_has_stats( $event ) ) {
-                       $current_user = wp_get_current_user();
-                       if ( $current_user->ID === $event->post_author || current_user_can( 'manage_options' ) ) {
-                               $create_delete_button = true;
-                       }
-               }
-
-               try {
-                       $event_start = self::convertToTimezone( get_post_meta( $event_id, '_event_start', true ), $event_timezone );
-                       $event_end   = self::convertToTimezone( get_post_meta( $event_id, '_event_end', true ), $event_timezone );
-               } catch ( Exception $e ) {
-                       // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
-                       error_log( $e );
-                       $this->die_with_error( esc_html__( 'Something is wrong.', 'gp-translation-events' ) );
-               }
-
-               $this->tmpl( 'events-form', get_defined_vars() );
-       }
-
-       /**
-        * Loads the 'event' template.
-        *
-        * @param string $event_slug The event slug.
-        *
-        * @return void
-        */
-       public function events_details( string $event_slug ) {
-               $user  = wp_get_current_user();
-               $event = get_page_by_path( $event_slug, OBJECT, Translation_Events::CPT );
-               if ( ! $event ) {
-                       $this->die_with_404();
-               }
-               /**
-                * Filter the ability to create, edit, or delete an event.
-                *
-                * @param bool $can_crud_event Whether the user can create, edit, or delete an event.
-                */
-               $can_crud_event = apply_filters( 'gp_translation_events_can_crud_event', GP::$permission->current_user_can( 'admin' ) );
-               if ( 'publish' !== $event->post_status && ! $can_crud_event ) {
-                       $this->die_with_error( esc_html__( 'You are not authorized to view this page.', 'gp-translation-events' ), 403 );
-               }
-
-               $event_id            = $event->ID;
-               $event_title         = $event->post_title;
-               $event_description   = $event->post_content;
-               $event_start         = get_post_meta( $event->ID, '_event_start', true ) ?: '';
-               $event_end           = get_post_meta( $event->ID, '_event_end', true ) ?: '';
-               $attending_event_ids = get_user_meta( $user->ID, self::USER_META_KEY_ATTENDING, true ) ?: array();
-               $user_is_attending   = isset( $attending_event_ids[ $event_id ] );
-
-               $stats_calculator = new Stats_Calculator();
-               try {
-                       $event_stats = $stats_calculator->for_event( $event );
-               } catch ( Exception $e ) {
-                       // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
-                       error_log( $e );
-                       $this->die_with_error( esc_html__( 'Failed to calculate event stats', 'gp-translation-events' ) );
-               }
-
-               $this->tmpl( 'event', get_defined_vars() );
-       }
-
-       /**
-        * Toggle whether the current user is attending an event.
-        * If the user is not currently marked as attending, they will be marked as attending.
-        * If the user is currently marked as attending, they will be marked as not attending.
-        */
-       public function events_attend( int $event_id ) {
-               $user = wp_get_current_user();
-               if ( ! $user ) {
-                       $this->die_with_error( esc_html__( 'Only logged-in users can attend events', 'gp-translation-events' ), 403 );
-               }
-
-               $event = get_post( $event_id );
-
-               if ( ! $event ) {
-                       $this->die_with_404();
-               }
-
-               $event_ids = get_user_meta( $user->ID, self::USER_META_KEY_ATTENDING, true ) ?? array();
-               if ( ! $event_ids ) {
-                       $event_ids = array();
-               }
-
-               if ( ! isset( $event_ids[ $event_id ] ) ) {
-                       // Not yet attending, mark as attending.
-                       $event_ids[ $event_id ] = true;
-               } else {
-                       // Currently attending, mark as not attending.
-                       unset( $event_ids[ $event_id ] );
-               }
-
-               update_user_meta( $user->ID, self::USER_META_KEY_ATTENDING, $event_ids );
-
-               wp_safe_redirect( gp_url( "/events/$event->post_name" ) );
-               exit;
-       }
-
-       /**
-        * Loads the 'events_user_created' template.
-        *
-        * @return void
-        */
-       public function my_events() {
-               global $wp;
-               if ( ! is_user_logged_in() ) {
-                       wp_safe_redirect( wp_login_url( home_url( $wp->request ) ) );
-                       exit;
-               }
-               include ABSPATH . 'wp-admin/includes/post.php';
-
-               $_events_i_created_paged  = 1;
-               $_events_i_attended_paged = 1;
-
-               // phpcs:disable WordPress.Security.NonceVerification.Recommended
-               if ( isset( $_GET['events_i_created_paged'] ) ) {
-                       $value = sanitize_text_field( wp_unslash( $_GET['events_i_created_paged'] ) );
-                       if ( is_numeric( $value ) ) {
-                               $_events_i_created_paged = (int) $value;
-                       }
-               }
-               if ( isset( $_GET['events_i_attended_paged'] ) ) {
-                       $value = sanitize_text_field( wp_unslash( $_GET['events_i_attended_paged'] ) );
-                       if ( is_numeric( $value ) ) {
-                               $_events_i_attended_paged = (int) $value;
-                       }
-               }
-               // phpcs:enable
-
-               $user_id              = get_current_user_id();
-               $events               = get_user_meta( $user_id, self::USER_META_KEY_ATTENDING, true ) ?: array();
-               $events               = array_keys( $events );
-               $current_datetime_utc = ( new DateTime( 'now', new DateTimeZone( 'UTC' ) ) )->format( 'Y-m-d H:i:s' );
-               $args                 = array(
-                       'post_type'              => Translation_Events::CPT,
-                       'posts_per_page'         => 10,
-                       'events_i_created_paged' => $_events_i_created_paged,
-                       'paged'                  => $_events_i_created_paged,
-                       'post_status'            => array( 'publish', 'draft' ),
-                       'author'                 => $user_id,
-                       // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
-                       'meta_key'               => '_event_start',
-                       'orderby'                => 'meta_value',
-                       'order'                  => 'DESC',
-               );
-               $events_i_created_query = new WP_Query( $args );
-
-               $args = array(
-                       'post_type'               => Translation_Events::CPT,
-                       'posts_per_page'          => 10,
-                       'events_i_attended_paged' => $_events_i_attended_paged,
-                       'paged'                   => $_events_i_attended_paged,
-                       'post_status'             => 'publish',
-                       'post__in'                => $events,
-                       // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
-                       'meta_query'              => array(
-                               array(
-                                       'key'     => '_event_end',
-                                       'value'   => $current_datetime_utc,
-                                       'compare' => '<',
-                                       'type'    => 'DATETIME',
-                               ),
-                       ),
-                       // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
-                       'meta_key'                => '_event_end',
-                       'orderby'                 => 'meta_value',
-                       'order'                   => 'DESC',
-               );
-               $events_i_attended_query = new WP_Query( $args );
-
-               $this->tmpl( 'events-my-events', get_defined_vars() );
-       }
-
-       /**
-        * Convert date time stored in UTC to a date time in a time zone.
-        *
-        * @param string $date_time The date time in UTC.
-        * @param string $time_zone The time zone.
-        *
-        * @return string The date time in the time zone.
-        * @throws Exception When date is invalid.
-        */
-       public static function convertToTimezone( string $date_time, string $time_zone ): string {
-               return ( new DateTime( $date_time, new DateTimeZone( 'UTC' ) ) )->setTimezone( new DateTimeZone( $time_zone ) )->format( 'Y-m-d H:i:s' );
-       }
-}
</del></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesrouteseventcreatephp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/create.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/create.php                             (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/create.php       2024-03-07 06:39:00 UTC (rev 13298)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,32 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace Wporg\TranslationEvents\Routes\Event;
+
+use Wporg\TranslationEvents\Routes\Route;
+
+/**
+ * Displays the event create page.
+ */
+class Create_Route extends Route {
+       public function handle(): void {
+               global $wp;
+               if ( ! is_user_logged_in() ) {
+                       wp_safe_redirect( wp_login_url( home_url( $wp->request ) ) );
+                       exit;
+               }
+               $event_form_title         = 'Create Event';
+               $event_form_name          = 'create_event';
+               $css_show_url             = 'hide-event-url';
+               $event_id                 = null;
+               $event_title              = '';
+               $event_description        = '';
+               $event_timezone           = '';
+               $event_start              = '';
+               $event_end                = '';
+               $event_url                = '';
+               $create_delete_button     = true;
+               $visibility_delete_button = 'none';
+
+               $this->tmpl( 'events-form', get_defined_vars() );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/create.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesrouteseventdetailsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/details.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/details.php                            (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/details.php      2024-03-07 06:39:00 UTC (rev 13298)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,52 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace Wporg\TranslationEvents\Routes\Event;
+
+use Exception;
+use GP;
+use Wporg\TranslationEvents\Routes\Route;
+use Wporg\TranslationEvents\Stats_Calculator;
+use Wporg\TranslationEvents\Translation_Events;
+
+/**
+ * Displays the event details page.
+ */
+class Details_Route extends Route {
+       public function handle( string $event_slug ): void {
+               $user  = wp_get_current_user();
+               $event = get_page_by_path( $event_slug, OBJECT, Translation_Events::CPT );
+               if ( ! $event ) {
+                       $this->die_with_404();
+               }
+
+               /**
+                * Filter the ability to create, edit, or delete an event.
+                *
+                * @param bool $can_crud_event Whether the user can create, edit, or delete an event.
+                */
+               $can_crud_event = apply_filters( 'gp_translation_events_can_crud_event', GP::$permission->current_user_can( 'admin' ) );
+               if ( 'publish' !== $event->post_status && ! $can_crud_event ) {
+                       $this->die_with_error( esc_html__( 'You are not authorized to view this page.', 'gp-translation-events' ), 403 );
+               }
+
+               $event_id            = $event->ID;
+               $event_title         = $event->post_title;
+               $event_description   = $event->post_content;
+               $event_start         = get_post_meta( $event->ID, '_event_start', true ) ?: '';
+               $event_end           = get_post_meta( $event->ID, '_event_end', true ) ?: '';
+               $attending_event_ids = get_user_meta( $user->ID, Translation_Events::USER_META_KEY_ATTENDING, true ) ?: array();
+               $user_is_attending   = isset( $attending_event_ids[ $event_id ] );
+
+               $stats_calculator = new Stats_Calculator();
+               try {
+                       $event_stats  = $stats_calculator->for_event( $event );
+                       $contributors = $stats_calculator->get_contributors( $event );
+               } catch ( Exception $e ) {
+                       // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
+                       error_log( $e );
+                       $this->die_with_error( esc_html__( 'Failed to calculate event stats', 'gp-translation-events' ) );
+               }
+
+               $this->tmpl( 'event', get_defined_vars() );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/details.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesrouteseventeditphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/edit.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/edit.php                               (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/edit.php 2024-03-07 06:39:00 UTC (rev 13298)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,76 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace Wporg\TranslationEvents\Routes\Event;
+
+use DateTime;
+use DateTimeZone;
+use Exception;
+use Wporg\TranslationEvents\Routes\Route;
+use Wporg\TranslationEvents\Stats_Calculator;
+use Wporg\TranslationEvents\Translation_Events;
+
+/**
+ * Displays the event edit page.
+ */
+class Edit_Route extends Route {
+       public function handle( int $event_id ): void {
+               global $wp;
+               if ( ! is_user_logged_in() ) {
+                       wp_safe_redirect( wp_login_url( home_url( $wp->request ) ) );
+                       exit;
+               }
+               $event = get_post( $event_id );
+               if ( ! $event || Translation_Events::CPT !== $event->post_type || ! ( current_user_can( 'edit_post', $event->ID ) || intval( $event->post_author ) === get_current_user_id() ) ) {
+                       $this->die_with_error( esc_html__( 'Event does not exist, or you do not have permission to edit it.', 'gp-translation-events' ), 403 );
+               }
+               if ( 'trash' === $event->post_status ) {
+                       $this->die_with_error( esc_html__( 'You cannot edit a trashed event', 'gp-translation-events' ), 403 );
+               }
+
+               include ABSPATH . 'wp-admin/includes/post.php';
+               $event_form_title              = 'Edit Event';
+               $event_form_name               = 'edit_event';
+               $css_show_url                  = '';
+               $event_title                   = $event->post_title;
+               $event_description             = $event->post_content;
+               $event_status                  = $event->post_status;
+               list( $permalink, $post_name ) = get_sample_permalink( $event_id );
+               $permalink                     = str_replace( '%pagename%', $post_name, $permalink );
+               $event_url                     = get_site_url() . gp_url( wp_make_link_relative( $permalink ) );
+               $event_timezone                = get_post_meta( $event_id, '_event_timezone', true ) ?: '';
+               $create_delete_button          = false;
+               $visibility_delete_button      = 'inline-flex';
+
+               $stats_calculator = new Stats_Calculator();
+               if ( ! $stats_calculator->event_has_stats( $event ) ) {
+                       $current_user = wp_get_current_user();
+                       if ( $current_user->ID === $event->post_author || current_user_can( 'manage_options' ) ) {
+                               $create_delete_button = true;
+                       }
+               }
+
+               try {
+                       $event_start = self::convertToTimezone( get_post_meta( $event_id, '_event_start', true ), $event_timezone );
+                       $event_end   = self::convertToTimezone( get_post_meta( $event_id, '_event_end', true ), $event_timezone );
+               } catch ( Exception $e ) {
+                       // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
+                       error_log( $e );
+                       $this->die_with_error( esc_html__( 'Something is wrong.', 'gp-translation-events' ) );
+               }
+
+               $this->tmpl( 'events-form', get_defined_vars() );
+       }
+
+       /**
+        * Convert date time stored in UTC to a date time in a time zone.
+        *
+        * @param string $date_time The date time in UTC.
+        * @param string $time_zone The time zone.
+        *
+        * @return string The date time in the time zone.
+        * @throws Exception When date is invalid.
+        */
+       private static function convertToTimezone( string $date_time, string $time_zone ): string {
+               return ( new DateTime( $date_time, new DateTimeZone( 'UTC' ) ) )->setTimezone( new DateTimeZone( $time_zone ) )->format( 'Y-m-d H:i:s' );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/edit.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesrouteseventlistphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/list.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/list.php                               (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/list.php 2024-03-07 06:39:00 UTC (rev 13298)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,146 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace Wporg\TranslationEvents\Routes\Event;
+
+use DateTime;
+use DateTimeZone;
+use Exception;
+use WP_Query;
+use Wporg\TranslationEvents\Routes\Route;
+use Wporg\TranslationEvents\Translation_Events;
+
+/**
+ * Displays the event list page.
+ */
+class List_Route extends Route {
+       public function handle(): void {
+               $current_datetime_utc = null;
+               try {
+                       $current_datetime_utc = ( new DateTime( 'now', new DateTimeZone( 'UTC' ) ) )->format( 'Y-m-d H:i:s' );
+               } catch ( Exception $e ) {
+                       // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
+                       error_log( $e );
+                       $this->die_with_error( esc_html__( 'Something is wrong.', 'gp-translation-events' ) );
+               }
+
+               $_current_events_paged        = 1;
+               $_upcoming_events_paged       = 1;
+               $_past_events_paged           = 1;
+               $_user_attending_events_paged = 1;
+
+               // phpcs:disable WordPress.Security.NonceVerification.Recommended
+               if ( isset( $_GET['current_events_paged'] ) ) {
+                       $value = sanitize_text_field( wp_unslash( $_GET['current_events_paged'] ) );
+                       if ( is_numeric( $value ) ) {
+                               $_current_events_paged = (int) $value;
+                       }
+               }
+               if ( isset( $_GET['upcoming_events_paged'] ) ) {
+                       $value = sanitize_text_field( wp_unslash( $_GET['upcoming_events_paged'] ) );
+                       if ( is_numeric( $value ) ) {
+                               $_upcoming_events_paged = (int) $value;
+                       }
+               }
+               if ( isset( $_GET['past_events_paged'] ) ) {
+                       $value = sanitize_text_field( wp_unslash( $_GET['past_events_paged'] ) );
+                       if ( is_numeric( $value ) ) {
+                               $_past_events_paged = (int) $value;
+                       }
+               }
+               if ( isset( $_GET['user_attending_events_paged'] ) ) {
+                       $value = sanitize_text_field( wp_unslash( $_GET['user_attending_events_paged'] ) );
+                       if ( is_numeric( $value ) ) {
+                               $_user_attending_events_paged = (int) $value;
+                       }
+               }
+               // phpcs:enable
+
+               $current_events_args  = array(
+                       'post_type'      => Translation_Events::CPT,
+                       'posts_per_page' => 10,
+                       'paged'          => $_current_events_paged,
+                       'post_status'    => 'publish',
+                       // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
+                       'meta_query'     => array(
+                               array(
+                                       'key'     => '_event_start',
+                                       'value'   => $current_datetime_utc,
+                                       'compare' => '<=',
+                                       'type'    => 'DATETIME',
+                               ),
+                               array(
+                                       'key'     => '_event_end',
+                                       'value'   => $current_datetime_utc,
+                                       'compare' => '>=',
+                                       'type'    => 'DATETIME',
+                               ),
+                       ),
+                       'orderby'        => 'meta_value',
+                       'order'          => 'ASC',
+               );
+               $current_events_query = new WP_Query( $current_events_args );
+
+               $upcoming_events_args  = array(
+                       'post_type'      => Translation_Events::CPT,
+                       'posts_per_page' => 10,
+                       'paged'          => $_upcoming_events_paged,
+                       'post_status'    => 'publish',
+                       // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
+                       'meta_query'     => array(
+                               array(
+                                       'key'     => '_event_start',
+                                       'value'   => $current_datetime_utc,
+                                       'compare' => '>=',
+                                       'type'    => 'DATETIME',
+                               ),
+                       ),
+                       'orderby'        => 'meta_value',
+                       'order'          => 'ASC',
+               );
+               $upcoming_events_query = new WP_Query( $upcoming_events_args );
+
+               $past_events_args  = array(
+                       'post_type'      => Translation_Events::CPT,
+                       'posts_per_page' => 10,
+                       'paged'          => $_past_events_paged,
+                       'post_status'    => 'publish',
+                       // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
+                       'meta_query'     => array(
+                               array(
+                                       'key'     => '_event_end',
+                                       'value'   => $current_datetime_utc,
+                                       'compare' => '<',
+                                       'type'    => 'DATETIME',
+                               ),
+                       ),
+                       'orderby'        => 'meta_value',
+                       'order'          => 'ASC',
+               );
+               $past_events_query = new WP_Query( $past_events_args );
+
+               $user_attending_events      = get_user_meta( get_current_user_id(), Translation_Events::USER_META_KEY_ATTENDING, true ) ?: array( 0 );
+               $user_attending_events_args = array(
+                       'post_type'      => Translation_Events::CPT,
+                       'post__in'       => array_keys( $user_attending_events ),
+                       'posts_per_page' => 10,
+                       'paged'          => $_user_attending_events_paged,
+                       'post_status'    => 'publish',
+                       // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
+                       'meta_query'     => array(
+                               array(
+                                       'key'     => '_event_end',
+                                       'value'   => $current_datetime_utc,
+                                       'compare' => '>',
+                                       'type'    => 'DATETIME',
+                               ),
+                       ),
+                       // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
+                       'meta_key'       => '_event_start',
+                       'orderby'        => 'meta_value',
+                       'order'          => 'ASC',
+               );
+               $user_attending_events_query = new WP_Query( $user_attending_events_args );
+
+               $this->tmpl( 'events-list', get_defined_vars() );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/event/list.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesroutesroutephp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/route.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/route.php                            (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/route.php      2024-03-07 06:39:00 UTC (rev 13298)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,12 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace Wporg\TranslationEvents\Routes;
+
+use GP_Route;
+
+abstract class Route extends GP_Route {
+       public function __construct() {
+               parent::__construct();
+               $this->template_path = __DIR__ . '/../../templates/';
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/route.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesroutesuserattendeventphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/user/attend-event.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/user/attend-event.php                                (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/user/attend-event.php  2024-03-07 06:39:00 UTC (rev 13298)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,44 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace Wporg\TranslationEvents\Routes\User;
+
+use Wporg\TranslationEvents\Routes\Route;
+use Wporg\TranslationEvents\Translation_Events;
+
+/**
+ * Toggle whether the current user is attending an event.
+ * If the user is not currently marked as attending, they will be marked as attending.
+ * If the user is currently marked as attending, they will be marked as not attending.
+ */
+class Attend_Event_Route extends Route {
+       public function handle( int $event_id ): void {
+               $user = wp_get_current_user();
+               if ( ! $user ) {
+                       $this->die_with_error( esc_html__( 'Only logged-in users can attend events', 'gp-translation-events' ), 403 );
+               }
+
+               $event = get_post( $event_id );
+
+               if ( ! $event ) {
+                       $this->die_with_404();
+               }
+
+               $event_ids = get_user_meta( $user->ID, Translation_Events::USER_META_KEY_ATTENDING, true ) ?? array();
+               if ( ! $event_ids ) {
+                       $event_ids = array();
+               }
+
+               if ( ! isset( $event_ids[ $event_id ] ) ) {
+                       // Not yet attending, mark as attending.
+                       $event_ids[ $event_id ] = true;
+               } else {
+                       // Currently attending, mark as not attending.
+                       unset( $event_ids[ $event_id ] );
+               }
+
+               update_user_meta( $user->ID, Translation_Events::USER_META_KEY_ATTENDING, $event_ids );
+
+               wp_safe_redirect( gp_url( "/events/$event->post_name" ) );
+               exit;
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/user/attend-event.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesroutesusermyeventsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/user/my-events.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/user/my-events.php                           (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/user/my-events.php     2024-03-07 06:39:00 UTC (rev 13298)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,84 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace Wporg\TranslationEvents\Routes\User;
+
+use DateTime;
+use DateTimeZone;
+use WP_Query;
+use Wporg\TranslationEvents\Routes\Route;
+use Wporg\TranslationEvents\Translation_Events;
+
+/**
+ * Displays the My Events page for a user.
+ */
+class My_Events_Route extends Route {
+       public function handle(): void {
+               global $wp;
+               if ( ! is_user_logged_in() ) {
+                       wp_safe_redirect( wp_login_url( home_url( $wp->request ) ) );
+                       exit;
+               }
+               include ABSPATH . 'wp-admin/includes/post.php';
+
+               $_events_i_created_paged  = 1;
+               $_events_i_attended_paged = 1;
+
+               // phpcs:disable WordPress.Security.NonceVerification.Recommended
+               if ( isset( $_GET['events_i_created_paged'] ) ) {
+                       $value = sanitize_text_field( wp_unslash( $_GET['events_i_created_paged'] ) );
+                       if ( is_numeric( $value ) ) {
+                               $_events_i_created_paged = (int) $value;
+                       }
+               }
+               if ( isset( $_GET['events_i_attended_paged'] ) ) {
+                       $value = sanitize_text_field( wp_unslash( $_GET['events_i_attended_paged'] ) );
+                       if ( is_numeric( $value ) ) {
+                               $_events_i_attended_paged = (int) $value;
+                       }
+               }
+               // phpcs:enable
+
+               $user_id              = get_current_user_id();
+               $events               = get_user_meta( $user_id, Translation_Events::USER_META_KEY_ATTENDING, true ) ?: array();
+               $events               = array_keys( $events );
+               $current_datetime_utc = ( new DateTime( 'now', new DateTimeZone( 'UTC' ) ) )->format( 'Y-m-d H:i:s' );
+               $args                 = array(
+                       'post_type'              => Translation_Events::CPT,
+                       'posts_per_page'         => 10,
+                       'events_i_created_paged' => $_events_i_created_paged,
+                       'paged'                  => $_events_i_created_paged,
+                       'post_status'            => array( 'publish', 'draft' ),
+                       'author'                 => $user_id,
+                       // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
+                       'meta_key'               => '_event_start',
+                       'orderby'                => 'meta_value',
+                       'order'                  => 'DESC',
+               );
+               $events_i_created_query = new WP_Query( $args );
+
+               $args = array(
+                       'post_type'               => Translation_Events::CPT,
+                       'posts_per_page'          => 10,
+                       'events_i_attended_paged' => $_events_i_attended_paged,
+                       'paged'                   => $_events_i_attended_paged,
+                       'post_status'             => 'publish',
+                       'post__in'                => $events,
+                       // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
+                       'meta_query'              => array(
+                               array(
+                                       'key'     => '_event_end',
+                                       'value'   => $current_datetime_utc,
+                                       'compare' => '<',
+                                       'type'    => 'DATETIME',
+                               ),
+                       ),
+                       // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
+                       'meta_key'                => '_event_end',
+                       'orderby'                 => 'meta_value',
+                       'order'                   => 'DESC',
+               );
+               $events_i_attended_query = new WP_Query( $args );
+
+               $this->tmpl( 'events-my-events', get_defined_vars() );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/routes/user/my-events.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesstatscalculatorphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/stats-calculator.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/stats-calculator.php        2024-03-07 04:39:31 UTC (rev 13297)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/stats-calculator.php  2024-03-07 06:39:00 UTC (rev 13298)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4,16 +4,21 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> use Exception;
</span><span class="cx" style="display: block; padding: 0 10px"> use WP_Post;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use WP_User;
+use GP_Locale;
+use GP_Locales;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> class Stats_Row {
</span><span class="cx" style="display: block; padding: 0 10px">        public int $created;
</span><span class="cx" style="display: block; padding: 0 10px">        public int $reviewed;
</span><span class="cx" style="display: block; padding: 0 10px">        public int $users;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        public ?GP_Locale $language = null;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        public function __construct( $created, $reviewed, $users ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public function __construct( $created, $reviewed, $users, ?GP_Locale $language = null ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                 $this->created  = $created;
</span><span class="cx" style="display: block; padding: 0 10px">                $this->reviewed = $reviewed;
</span><span class="cx" style="display: block; padding: 0 10px">                $this->users    = $users;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $this->language = $language;
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -49,6 +54,23 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @return Stats_Row[]
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function rows(): array {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                uasort(
+                       $this->rows,
+                       function ( $a, $b ) {
+                               if ( ! $a->language && ! $b->language ) {
+                                       return 0;
+                               }
+                               if ( ! $a->language ) {
+                                       return -1;
+                               }
+                               if ( ! $b->language ) {
+                                       return 1;
+                               }
+
+                               return strcasecmp( $a->language->english_name, $b->language->english_name );
+                       }
+               );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 return $this->rows;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -100,10 +122,16 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                );
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        $lang = GP_Locales::by_slug( $row->locale );
+                       if ( ! $lang ) {
+                               $lang = null;
+                       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                         $stats_row = new Stats_Row(
</span><span class="cx" style="display: block; padding: 0 10px">                                $row->created,
</span><span class="cx" style="display: block; padding: 0 10px">                                $row->total - $row->created,
</span><span class="cx" style="display: block; padding: 0 10px">                                $row->users,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                $lang
</ins><span class="cx" style="display: block; padding: 0 10px">                         );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( ! $is_totals ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -117,6 +145,49 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * Get contributors for an event.
+        */
+       public function get_contributors( WP_Post $event ): array {
+               global $wpdb, $gp_table_prefix;
+
+               // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+               // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
+               // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
+               // phpcs thinks we're doing a schema change but we aren't.
+               // phpcs:disable WordPress.DB.DirectDatabaseQuery.SchemaChange
+               $rows = $wpdb->get_results(
+                       $wpdb->prepare(
+                               "
+                               select user_id, group_concat( distinct locale ) as locales
+                               from {$gp_table_prefix}event_actions
+                               where event_id = %d
+                               group by user_id
+                       ",
+                               array(
+                                       $event->ID,
+                               )
+                       )
+               );
+               // phpcs:enable
+
+               $users = array();
+               foreach ( $rows as $row ) {
+                       $user          = new WP_User( $row->user_id );
+                       $user->locales = explode( ',', $row->locales );
+                       $users[]       = $user;
+               }
+
+               uasort(
+                       $users,
+                       function ( $a, $b ) {
+                               return strcasecmp( $a->display_name, $b->display_name );
+                       }
+               );
+
+               return $users;
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Check if an event has stats.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @param WP_Post $event The event to check.
</span></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventsincludesstatslistenerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/stats-listener.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/stats-listener.php  2024-03-07 04:39:31 UTC (rev 13297)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/includes/stats-listener.php    2024-03-07 06:39:00 UTC (rev 13298)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -25,6 +25,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'gp_translation_created',
</span><span class="cx" style="display: block; padding: 0 10px">                        function ( $translation ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                $happened_at = DateTimeImmutable::createFromFormat( 'Y-m-d H:i:s', $translation->date_added, new DateTimeZone( 'UTC' ) );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                if ( ! $translation->user_id ) {
+                                       return;
+                               }
</ins><span class="cx" style="display: block; padding: 0 10px">                                 $this->handle_action( $translation, $translation->user_id, self::ACTION_CREATE, $happened_at );
</span><span class="cx" style="display: block; padding: 0 10px">                        },
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -54,7 +57,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                                break;
</span><span class="cx" style="display: block; padding: 0 10px">                                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                if ( $action ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         if ( $action && $user_id ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                                         $this->handle_action( $translation, $user_id, $action, $happened_at );
</span><span class="cx" style="display: block; padding: 0 10px">                                }
</span><span class="cx" style="display: block; padding: 0 10px">                        },
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -65,9 +68,6 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        private function handle_action( GP_Translation $translation, int $user_id, string $action, DateTimeImmutable $happened_at ): void {
</span><span class="cx" style="display: block; padding: 0 10px">                try {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        if ( ! $user_id ) {
-                               return;
-                       }
</del><span class="cx" style="display: block; padding: 0 10px">                         // Get events that are active when the action happened, for which the user is registered for.
</span><span class="cx" style="display: block; padding: 0 10px">                        $active_events = $this->get_active_events( $happened_at );
</span><span class="cx" style="display: block; padding: 0 10px">                        $events        = $this->select_events_user_is_registered_for( $active_events, $user_id );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -171,7 +171,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">        // phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
</span><span class="cx" style="display: block; padding: 0 10px">        // phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.Found
</span><span class="cx" style="display: block; padding: 0 10px">        private function select_events_user_is_registered_for( array $events, int $user_id ): array {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $attending_event_ids = get_user_meta( $user_id, Route::USER_META_KEY_ATTENDING, true );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $attending_event_ids = get_user_meta( $user_id, Translation_Events::USER_META_KEY_ATTENDING, true );
</ins><span class="cx" style="display: block; padding: 0 10px">                 return array_filter(
</span><span class="cx" style="display: block; padding: 0 10px">                        $events,
</span><span class="cx" style="display: block; padding: 0 10px">                        function ( Event $event ) use ( $attending_event_ids ) {
</span></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventstemplateseventphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/templates/event.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/templates/event.php  2024-03-07 04:39:31 UTC (rev 13297)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/templates/event.php    2024-03-07 06:39:00 UTC (rev 13298)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -46,7 +46,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                </div>
</span><span class="cx" style="display: block; padding: 0 10px">                <?php if ( ! empty( $event_stats->rows() ) ) : ?>
</span><span class="cx" style="display: block; padding: 0 10px">        <div class="event-details-stats">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                <h2>Stats</h2>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         <h2><?php esc_html_e( 'Stats', 'gp-translation-events' ); ?></h2>
</ins><span class="cx" style="display: block; padding: 0 10px">                 <table>
</span><span class="cx" style="display: block; padding: 0 10px">                        <thead>
</span><span class="cx" style="display: block; padding: 0 10px">                        <tr>
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -58,9 +58,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        </thead>
</span><span class="cx" style="display: block; padding: 0 10px">                        <tbody>
</span><span class="cx" style="display: block; padding: 0 10px">                        <?php /** @var $row Stats_Row */ ?>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        <?php foreach ( $event_stats->rows() as $locale_ => $row ) : ?>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 <?php foreach ( $event_stats->rows() as $_locale => $row ) : ?>
</ins><span class="cx" style="display: block; padding: 0 10px">                         <tr>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                <td><?php echo esc_html( $locale_ ); ?></td>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         <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>
</ins><span class="cx" style="display: block; padding: 0 10px">                                 <td><?php echo esc_html( $row->created ); ?></td>
</span><span class="cx" style="display: block; padding: 0 10px">                                <td><?php echo esc_html( $row->reviewed ); ?></td>
</span><span class="cx" style="display: block; padding: 0 10px">                                <td><?php echo esc_html( $row->users ); ?></td>
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -75,11 +75,70 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        </tbody>
</span><span class="cx" style="display: block; padding: 0 10px">                </table>
</span><span class="cx" style="display: block; padding: 0 10px">        </div>
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        <div class="event-contributors">
+               <h2><?php esc_html_e( 'Contributors', 'gp-translation-events' ); ?></h2>
+               <ul>
+                       <?php foreach ( $contributors as $contributor ) : ?>
+                       <li class="event-contributor" title="<?php echo esc_html( implode( ', ', $contributor->locales ) ); ?>"
+                               <a href="<?php echo esc_url( get_author_posts_url( $contributor->ID ) ); ?>"><?php echo get_avatar( $contributor->ID, 48 ); ?></a>
+                               <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>
+                       </li>
+               <?php endforeach; ?>
+               </ul>
+       </div>
</ins><span class="cx" style="display: block; padding: 0 10px">         <details class="event-stats-summary">
</span><span class="cx" style="display: block; padding: 0 10px">                <summary>View stats summary in text </summary>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                <p class="event-stats-text"><?php echo esc_html( sprintf( 'At the %s event, %d people contributed in %d languages (%s), translated %d strings and reviewed %d strings.', esc_html( $event_title ), esc_html( $event_stats->totals()->users ), count( $event_stats->rows() ), esc_html( implode( ',', array_keys( $event_stats->rows() ) ) ), esc_html( $event_stats->totals()->created ), esc_html( $event_stats->totals()->reviewed ) ) ); ?></p>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         <p class="event-stats-text">
+                       <?php
+                       echo wp_kses(
+                               sprintf(
+                                       // 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.
+                                       __( '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' ),
+                                       esc_html( $event_title ),
+                                       esc_html( $event_stats->totals()->users ),
+                                       count( $event_stats->rows() ),
+                                       esc_html(
+                                               implode(
+                                                       ', ',
+                                                       array_map(
+                                                               function ( $row ) {
+                                                                       return $row->language->english_name;
+                                                               },
+                                                               $event_stats->rows()
+                                                       )
+                                               )
+                                       ),
+                                       esc_html( $event_stats->totals()->created ),
+                                       esc_html( $event_stats->totals()->reviewed )
+                               ),
+                               array(
+                                       'strong' => array(),
+                               )
+                       );
+                       ?>
+                       <?php
+                       echo esc_html(
+                               sprintf(
+                                       // translators: %s the contributors.
+                                       __( 'Contributors were %s.', 'gp-translation-events' ),
+                                       esc_html(
+                                               implode(
+                                                       ', ',
+                                                       array_map(
+                                                               function ( $contributor ) {
+                                                                       return '@' . $contributor->user_login;
+                                                               },
+                                                               $contributors
+                                                       )
+                                               )
+                                       )
+                               )
+                       );
+                       ?>
+                       </p>
</ins><span class="cx" style="display: block; padding: 0 10px">         </details>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        <?php endif ?>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       <?php endif; ?>
</ins><span class="cx" style="display: block; padding: 0 10px">         </div>
</span><span class="cx" style="display: block; padding: 0 10px">        <div class="event-details-right">
</span><span class="cx" style="display: block; padding: 0 10px">                <div class="event-details-date">
</span></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggptranslationeventswporggptranslationeventsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/wporg-gp-translation-events.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/wporg-gp-translation-events.php      2024-03-07 04:39:31 UTC (rev 13297)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-events/wporg-gp-translation-events.php        2024-03-07 06:39:00 UTC (rev 13298)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -26,7 +26,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> use WP_Query;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> class Translation_Events {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        const CPT = 'translation_event';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public const CPT                     = 'translation_event';
+       public const USER_META_KEY_ATTENDING = 'translation-events-attending';
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        public static function get_instance() {
</span><span class="cx" style="display: block; padding: 0 10px">                static $instance = null;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -55,16 +56,22 @@
</span><span class="cx" style="display: block; padding: 0 10px">                require_once __DIR__ . '/templates/helper-functions.php';
</span><span class="cx" style="display: block; padding: 0 10px">                require_once __DIR__ . '/includes/active-events-cache.php';
</span><span class="cx" style="display: block; padding: 0 10px">                require_once __DIR__ . '/includes/event.php';
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                require_once __DIR__ . '/includes/route.php';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         require_once __DIR__ . '/includes/routes/route.php';
+               require_once __DIR__ . '/includes/routes/event/create.php';
+               require_once __DIR__ . '/includes/routes/event/details.php';
+               require_once __DIR__ . '/includes/routes/event/edit.php';
+               require_once __DIR__ . '/includes/routes/event/list.php';
+               require_once __DIR__ . '/includes/routes/user/attend-event.php';
+               require_once __DIR__ . '/includes/routes/user/my-events.php';
</ins><span class="cx" style="display: block; padding: 0 10px">                 require_once __DIR__ . '/includes/stats-calculator.php';
</span><span class="cx" style="display: block; padding: 0 10px">                require_once __DIR__ . '/includes/stats-listener.php';
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                GP::$router->add( '/events?', array( 'Wporg\TranslationEvents\Route', 'events_list' ) );
-               GP::$router->add( '/events/new', array( 'Wporg\TranslationEvents\Route', 'events_create' ) );
-               GP::$router->add( '/events/edit/(\d+)', array( 'Wporg\TranslationEvents\Route', 'events_edit' ) );
-               GP::$router->add( '/events/attend/(\d+)', array( 'Wporg\TranslationEvents\Route', 'events_attend' ), 'post' );
-               GP::$router->add( '/events/my-events', array( 'Wporg\TranslationEvents\Route', 'my_events' ) );
-               GP::$router->add( '/events/([a-z0-9_-]+)', array( 'Wporg\TranslationEvents\Route', 'events_details' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         GP::$router->add( '/events?', array( 'Wporg\TranslationEvents\Routes\Event\List_Route', 'handle' ) );
+               GP::$router->add( '/events/new', array( 'Wporg\TranslationEvents\Routes\Event\Create_Route', 'handle' ) );
+               GP::$router->add( '/events/edit/(\d+)', array( 'Wporg\TranslationEvents\Routes\Event\Edit_Route', 'handle' ) );
+               GP::$router->add( '/events/attend/(\d+)', array( 'Wporg\TranslationEvents\Routes\User\Attend_Event_Route', 'handle' ), 'post' );
+               GP::$router->add( '/events/my-events', array( 'Wporg\TranslationEvents\Routes\User\My_Events_Route', 'handle' ) );
+               GP::$router->add( '/events/([a-z0-9_-]+)', array( 'Wporg\TranslationEvents\Routes\Event\Details_Route', 'handle' ) );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $active_events_cache = new Active_Events_Cache();
</span><span class="cx" style="display: block; padding: 0 10px">                $stats_listener      = new Stats_Listener( $active_events_cache );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -399,12 +406,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px">                if ( 'publish' === $new_status && ( 'new' === $old_status || 'draft' === $old_status ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $current_user_id         = get_current_user_id();
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $user_attending_events   = get_user_meta( $current_user_id, Route::USER_META_KEY_ATTENDING, true ) ?: array();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $user_attending_events   = get_user_meta( $current_user_id, self::USER_META_KEY_ATTENDING, true ) ?: array();
</ins><span class="cx" style="display: block; padding: 0 10px">                         $is_user_attending_event = in_array( $post->ID, $user_attending_events, true );
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( ! $is_user_attending_event ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                $new_user_attending_events              = $user_attending_events;
</span><span class="cx" style="display: block; padding: 0 10px">                                $new_user_attending_events[ $post->ID ] = true;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                update_user_meta( $current_user_id, Route::USER_META_KEY_ATTENDING, $new_user_attending_events, $user_attending_events );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         update_user_meta( $current_user_id, self::USER_META_KEY_ATTENDING, $new_user_attending_events, $user_attending_events );
</ins><span class="cx" style="display: block; padding: 0 10px">                         }
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -461,7 +468,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @return void
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function add_active_events_current_user(): void {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $user_attending_events = get_user_meta( get_current_user_id(), Route::USER_META_KEY_ATTENDING, true ) ?: array();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $user_attending_events = get_user_meta( get_current_user_id(), self::USER_META_KEY_ATTENDING, true ) ?: array();
</ins><span class="cx" style="display: block; padding: 0 10px">                 if ( empty( $user_attending_events ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        return;
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span></span></pre>
</div>
</div>

</body>
</html>